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