xref: /openbmc/bmcweb/test/http/mutual_tls.cpp (revision 4d7b5ddb3a2b6cc42b7bbc0c710f297e6df4fd55)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #include "mutual_tls.hpp"
4 
5 #include "mutual_tls_private.hpp"
6 #include "sessions.hpp"
7 
8 #include <cstring>
9 #include <string>
10 
11 extern "C"
12 {
13 #include <openssl/asn1.h>
14 #include <openssl/ec.h>
15 #include <openssl/evp.h>
16 #include <openssl/obj_mac.h>
17 #include <openssl/objects.h>
18 #include <openssl/types.h>
19 #include <openssl/x509.h>
20 #include <openssl/x509_vfy.h>
21 #include <openssl/x509v3.h>
22 }
23 
24 #include <boost/asio/ip/address.hpp>
25 #include <boost/asio/ssl/verify_context.hpp>
26 
27 #include <array>
28 #include <memory>
29 
30 #include <gmock/gmock.h>
31 #include <gtest/gtest.h>
32 
33 using ::testing::IsNull;
34 using ::testing::NotNull;
35 
36 namespace
37 {
38 class OSSLX509
39 {
40     X509* ptr = X509_new();
41 
42   public:
43     OSSLX509& operator=(const OSSLX509&) = delete;
44     OSSLX509& operator=(OSSLX509&&) = delete;
45 
46     OSSLX509(const OSSLX509&) = delete;
47     OSSLX509(OSSLX509&&) = delete;
48 
49     OSSLX509() = default;
50 
setSubjectName()51     void setSubjectName()
52     {
53         X509_NAME* name = X509_get_subject_name(ptr);
54         std::array<unsigned char, 5> user = {'u', 's', 'e', 'r', '\0'};
55         X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, user.data(), -1,
56                                    -1, 0);
57     }
sign()58     void sign()
59     {
60         // Generate test key
61         EVP_PKEY* pkey = nullptr;
62         EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr);
63         ASSERT_EQ(EVP_PKEY_keygen_init(pctx), 1);
64         ASSERT_EQ(
65             EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1),
66             1);
67         ASSERT_EQ(EVP_PKEY_keygen(pctx, &pkey), 1);
68         EVP_PKEY_CTX_free(pctx);
69 
70         // Sign cert with key
71         ASSERT_EQ(X509_set_pubkey(ptr, pkey), 1);
72         ASSERT_GT(X509_sign(ptr, pkey, EVP_sha256()), 0);
73         EVP_PKEY_free(pkey);
74     }
75 
get()76     X509* get()
77     {
78         return ptr;
79     }
~OSSLX509()80     ~OSSLX509()
81     {
82         X509_free(ptr);
83     }
84 };
85 
86 class OSSLX509StoreCTX
87 {
88     X509_STORE_CTX* ptr = X509_STORE_CTX_new();
89 
90   public:
91     OSSLX509StoreCTX& operator=(const OSSLX509StoreCTX&) = delete;
92     OSSLX509StoreCTX& operator=(OSSLX509StoreCTX&&) = delete;
93 
94     OSSLX509StoreCTX(const OSSLX509StoreCTX&) = delete;
95     OSSLX509StoreCTX(OSSLX509StoreCTX&&) = delete;
96 
97     OSSLX509StoreCTX() = default;
get()98     X509_STORE_CTX* get()
99     {
100         return ptr;
101     }
~OSSLX509StoreCTX()102     ~OSSLX509StoreCTX()
103     {
104         X509_STORE_CTX_free(ptr);
105     }
106 };
107 
TEST(MutualTLS,GoodCert)108 TEST(MutualTLS, GoodCert)
109 {
110     OSSLX509 x509;
111 
112     x509.setSubjectName();
113     X509_EXTENSION* ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage,
114                                              "digitalSignature, keyAgreement");
115     ASSERT_THAT(ex, NotNull());
116     ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
117     X509_EXTENSION_free(ex);
118     ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage, "clientAuth");
119     ASSERT_THAT(ex, NotNull());
120     ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
121     X509_EXTENSION_free(ex);
122 
123     x509.sign();
124 
125     OSSLX509StoreCTX x509Store;
126     X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get());
127 
128     boost::asio::ip::address ip;
129     boost::asio::ssl::verify_context ctx(x509Store.get());
130     std::shared_ptr<persistent_data::UserSession> session =
131         verifyMtlsUser(ip, ctx);
132     ASSERT_THAT(session, NotNull());
133     EXPECT_THAT(session->username, "user");
134 }
135 
TEST(MutualTLS,MissingKeyUsage)136 TEST(MutualTLS, MissingKeyUsage)
137 {
138     for (const char* usageString :
139          {"digitalSignature", "keyAgreement", "digitalSignature, keyAgreement"})
140     {
141         OSSLX509 x509;
142         x509.setSubjectName();
143 
144         X509_EXTENSION* ex =
145             X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, usageString);
146 
147         ASSERT_THAT(ex, NotNull());
148         ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
149         X509_EXTENSION_free(ex);
150         ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage,
151                                  "clientAuth");
152         ASSERT_THAT(ex, NotNull());
153         ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1);
154         X509_EXTENSION_free(ex);
155         x509.sign();
156 
157         OSSLX509StoreCTX x509Store;
158         X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get());
159 
160         boost::asio::ip::address ip;
161         boost::asio::ssl::verify_context ctx(x509Store.get());
162         std::shared_ptr<persistent_data::UserSession> session =
163             verifyMtlsUser(ip, ctx);
164         ASSERT_THAT(session, NotNull());
165     }
166 }
167 
TEST(MutualTLS,MissingCert)168 TEST(MutualTLS, MissingCert)
169 {
170     OSSLX509StoreCTX x509Store;
171 
172     boost::asio::ip::address ip;
173     boost::asio::ssl::verify_context ctx(x509Store.get());
174     std::shared_ptr<persistent_data::UserSession> session =
175         verifyMtlsUser(ip, ctx);
176     ASSERT_THAT(session, IsNull());
177 }
178 
TEST(GetCommonNameFromCert,EmptyCommonName)179 TEST(GetCommonNameFromCert, EmptyCommonName)
180 {
181     OSSLX509 x509;
182     std::string commonName = getCommonNameFromCert(x509.get());
183     EXPECT_THAT(commonName, "");
184 }
185 
TEST(GetCommonNameFromCert,ValidCommonName)186 TEST(GetCommonNameFromCert, ValidCommonName)
187 {
188     OSSLX509 x509;
189     x509.setSubjectName();
190     std::string commonName = getCommonNameFromCert(x509.get());
191     EXPECT_THAT(commonName, "user");
192 }
193 
TEST(GetUPNFromCert,EmptySubjectAlternativeName)194 TEST(GetUPNFromCert, EmptySubjectAlternativeName)
195 {
196     OSSLX509 x509;
197     std::string upn = getUPNFromCert(x509.get(), "");
198     EXPECT_THAT(upn, "");
199 }
200 
TEST(GetUPNFromCert,NonOthernameSubjectAlternativeName)201 TEST(GetUPNFromCert, NonOthernameSubjectAlternativeName)
202 {
203     OSSLX509 x509;
204 
205     ASN1_IA5STRING* ia5 = ASN1_IA5STRING_new();
206     ASSERT_THAT(ia5, NotNull());
207 
208     const char* user = "user@domain.com";
209     ASSERT_NE(ASN1_STRING_set(ia5, user, static_cast<int>(strlen(user))), 0);
210 
211     GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null();
212     ASSERT_THAT(gens, NotNull());
213 
214     GENERAL_NAME* gen = GENERAL_NAME_new();
215     ASSERT_THAT(gen, NotNull());
216 
217     GENERAL_NAME_set0_value(gen, GEN_EMAIL, ia5);
218     ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1);
219 
220     ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0),
221               1);
222 
223     std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com");
224     EXPECT_THAT(upn, "");
225 
226     GENERAL_NAME_free(gen);
227     sk_GENERAL_NAME_free(gens);
228 }
229 
TEST(GetUPNFromCert,NonUPNSubjectAlternativeName)230 TEST(GetUPNFromCert, NonUPNSubjectAlternativeName)
231 {
232     OSSLX509 x509;
233 
234     GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null();
235     ASSERT_THAT(gens, NotNull());
236 
237     GENERAL_NAME* gen = GENERAL_NAME_new();
238     ASSERT_THAT(gen, NotNull());
239 
240     ASN1_OBJECT* othType = OBJ_nid2obj(NID_SRVName);
241 
242     ASN1_TYPE* value = ASN1_TYPE_new();
243     ASSERT_THAT(value, NotNull());
244     value->type = V_ASN1_UTF8STRING;
245 
246     // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
247     value->value.utf8string = ASN1_UTF8STRING_new();
248     ASSERT_THAT(value->value.utf8string, NotNull());
249     const char* user = "user@domain.com";
250     ASN1_STRING_set(value->value.utf8string, user,
251                     static_cast<int>(strlen(user)));
252     // NOLINTEND(cppcoreguidelines-pro-type-union-access)
253 
254     ASSERT_EQ(GENERAL_NAME_set0_othername(gen, othType, value), 1);
255     ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1);
256     ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0),
257               1);
258 
259     std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com");
260     EXPECT_THAT(upn, "");
261 
262     sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
263 }
264 
TEST(GetUPNFromCert,NonUTF8UPNSubjectAlternativeName)265 TEST(GetUPNFromCert, NonUTF8UPNSubjectAlternativeName)
266 {
267     OSSLX509 x509;
268 
269     GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null();
270     ASSERT_THAT(gens, NotNull());
271 
272     GENERAL_NAME* gen = GENERAL_NAME_new();
273     ASSERT_THAT(gen, NotNull());
274 
275     ASN1_OBJECT* othType = OBJ_nid2obj(NID_ms_upn);
276 
277     ASN1_TYPE* value = ASN1_TYPE_new();
278     ASSERT_THAT(value, NotNull());
279     value->type = V_ASN1_OCTET_STRING;
280 
281     // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
282     value->value.octet_string = ASN1_OCTET_STRING_new();
283     ASSERT_THAT(value->value.octet_string, NotNull());
284     const char* user = "0123456789";
285     ASN1_STRING_set(value->value.octet_string, user,
286                     static_cast<int>(strlen(user)));
287     // NOLINTEND(cppcoreguidelines-pro-type-union-access)
288 
289     ASSERT_EQ(GENERAL_NAME_set0_othername(gen, othType, value), 1);
290     ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1);
291     ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0),
292               1);
293 
294     std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com");
295     EXPECT_THAT(upn, "");
296 
297     sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
298 }
299 
TEST(GetUPNFromCert,ValidUPN)300 TEST(GetUPNFromCert, ValidUPN)
301 {
302     OSSLX509 x509;
303 
304     GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null();
305     ASSERT_THAT(gens, NotNull());
306 
307     GENERAL_NAME* gen = GENERAL_NAME_new();
308     ASSERT_THAT(gen, NotNull());
309 
310     ASN1_OBJECT* othType = OBJ_nid2obj(NID_ms_upn);
311 
312     ASN1_TYPE* value = ASN1_TYPE_new();
313     ASSERT_THAT(value, NotNull());
314     value->type = V_ASN1_UTF8STRING;
315 
316     // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
317     value->value.utf8string = ASN1_UTF8STRING_new();
318     ASSERT_THAT(value->value.utf8string, NotNull());
319     const char* user = "user@domain.com";
320     ASN1_STRING_set(value->value.utf8string, user,
321                     static_cast<int>(strlen(user)));
322     // NOLINTEND(cppcoreguidelines-pro-type-union-access)
323 
324     ASSERT_EQ(GENERAL_NAME_set0_othername(gen, othType, value), 1);
325     ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1);
326     ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0),
327               1);
328 
329     std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com");
330     EXPECT_THAT(upn, "user");
331 
332     sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
333 }
334 
TEST(IsUPNMatch,MultipleCases)335 TEST(IsUPNMatch, MultipleCases)
336 {
337     EXPECT_FALSE(isUPNMatch("user", "hostname.domain.com"));
338     EXPECT_TRUE(isUPNMatch("user@domain.com", "hostname.domain.com"));
339     EXPECT_FALSE(isUPNMatch("user@domain.com", "hostname.domain.org"));
340     EXPECT_FALSE(isUPNMatch("user@region.com", "hostname.domain.com"));
341     EXPECT_TRUE(isUPNMatch("user@com", "hostname.region.domain.com"));
342 }
343 } // namespace
344