xref: /openbmc/bmcweb/test/http/mutual_tls.cpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #include "mutual_tls.hpp"
4 
5 #include "sessions.hpp"
6 
7 extern "C"
8 {
9 #include <openssl/asn1.h>
10 #include <openssl/ec.h>
11 #include <openssl/evp.h>
12 #include <openssl/obj_mac.h>
13 #include <openssl/types.h>
14 #include <openssl/x509.h>
15 #include <openssl/x509_vfy.h>
16 #include <openssl/x509v3.h>
17 }
18 
19 #include <boost/asio/ip/address.hpp>
20 #include <boost/asio/ssl/verify_context.hpp>
21 
22 #include <array>
23 #include <memory>
24 
25 #include <gmock/gmock.h>
26 #include <gtest/gtest.h>
27 
28 using ::testing::IsNull;
29 using ::testing::NotNull;
30 
31 namespace
32 {
33 class OSSLX509
34 {
35     X509* ptr = X509_new();
36 
37   public:
38     OSSLX509& operator=(const OSSLX509&) = delete;
39     OSSLX509& operator=(OSSLX509&&) = delete;
40 
41     OSSLX509(const OSSLX509&) = delete;
42     OSSLX509(OSSLX509&&) = delete;
43 
44     OSSLX509() = default;
45 
setSubjectName()46     void setSubjectName()
47     {
48         X509_NAME* name = X509_get_subject_name(ptr);
49         std::array<unsigned char, 5> user = {'u', 's', 'e', 'r', '\0'};
50         X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, user.data(), -1,
51                                    -1, 0);
52     }
sign()53     void sign()
54     {
55         // Generate test key
56         EVP_PKEY* pkey = nullptr;
57         EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr);
58         ASSERT_EQ(EVP_PKEY_keygen_init(pctx), 1);
59         ASSERT_EQ(
60             EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1),
61             1);
62         ASSERT_EQ(EVP_PKEY_keygen(pctx, &pkey), 1);
63         EVP_PKEY_CTX_free(pctx);
64 
65         // Sign cert with key
66         ASSERT_EQ(X509_set_pubkey(ptr, pkey), 1);
67         ASSERT_GT(X509_sign(ptr, pkey, EVP_sha256()), 0);
68         EVP_PKEY_free(pkey);
69     }
70 
get()71     X509* get()
72     {
73         return ptr;
74     }
~OSSLX509()75     ~OSSLX509()
76     {
77         X509_free(ptr);
78     }
79 };
80 
81 class OSSLX509StoreCTX
82 {
83     X509_STORE_CTX* ptr = X509_STORE_CTX_new();
84 
85   public:
86     OSSLX509StoreCTX& operator=(const OSSLX509StoreCTX&) = delete;
87     OSSLX509StoreCTX& operator=(OSSLX509StoreCTX&&) = delete;
88 
89     OSSLX509StoreCTX(const OSSLX509StoreCTX&) = delete;
90     OSSLX509StoreCTX(OSSLX509StoreCTX&&) = delete;
91 
92     OSSLX509StoreCTX() = default;
get()93     X509_STORE_CTX* get()
94     {
95         return ptr;
96     }
~OSSLX509StoreCTX()97     ~OSSLX509StoreCTX()
98     {
99         X509_STORE_CTX_free(ptr);
100     }
101 };
102 
TEST(MutualTLS,GoodCert)103 TEST(MutualTLS, GoodCert)
104 {
105     OSSLX509 x509;
106 
107     x509.setSubjectName();
108     X509_EXTENSION* ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage,
109                                              "digitalSignature, keyAgreement");
110     ASSERT_THAT(ex, NotNull());
111     ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
112     X509_EXTENSION_free(ex);
113     ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage, "clientAuth");
114     ASSERT_THAT(ex, NotNull());
115     ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
116     X509_EXTENSION_free(ex);
117 
118     x509.sign();
119 
120     OSSLX509StoreCTX x509Store;
121     X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get());
122 
123     boost::asio::ip::address ip;
124     boost::asio::ssl::verify_context ctx(x509Store.get());
125     std::shared_ptr<persistent_data::UserSession> session =
126         verifyMtlsUser(ip, ctx);
127     ASSERT_THAT(session, NotNull());
128     EXPECT_THAT(session->username, "user");
129 }
130 
TEST(MutualTLS,MissingKeyUsage)131 TEST(MutualTLS, MissingKeyUsage)
132 {
133     for (const char* usageString :
134          {"digitalSignature", "keyAgreement", "digitalSignature, keyAgreement"})
135     {
136         OSSLX509 x509;
137         x509.setSubjectName();
138 
139         X509_EXTENSION* ex =
140             X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, usageString);
141 
142         ASSERT_THAT(ex, NotNull());
143         ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
144         X509_EXTENSION_free(ex);
145         ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage,
146                                  "clientAuth");
147         ASSERT_THAT(ex, NotNull());
148         ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
149         X509_EXTENSION_free(ex);
150         x509.sign();
151 
152         OSSLX509StoreCTX x509Store;
153         X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get());
154 
155         boost::asio::ip::address ip;
156         boost::asio::ssl::verify_context ctx(x509Store.get());
157         std::shared_ptr<persistent_data::UserSession> session =
158             verifyMtlsUser(ip, ctx);
159         ASSERT_THAT(session, NotNull());
160     }
161 }
162 
TEST(MutualTLS,MissingCert)163 TEST(MutualTLS, MissingCert)
164 {
165     OSSLX509StoreCTX x509Store;
166 
167     boost::asio::ip::address ip;
168     boost::asio::ssl::verify_context ctx(x509Store.get());
169     std::shared_ptr<persistent_data::UserSession> session =
170         verifyMtlsUser(ip, ctx);
171     ASSERT_THAT(session, IsNull());
172 }
173 } // namespace
174