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