blob: e89a3cfc270391285a8affe296b2f7a455162ed0 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanouse7923992023-12-03 14:14:02 -08003#include "mutual_tls.hpp"
4
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -08005#include "mutual_tls_private.hpp"
Ed Tanousf0b59af2024-03-20 13:38:04 -07006#include "sessions.hpp"
7
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -08008#include <cstring>
9#include <string>
10
Ed Tanous6dbe9be2024-04-14 10:24:20 -070011extern "C"
12{
Ed Tanousf0b59af2024-03-20 13:38:04 -070013#include <openssl/asn1.h>
14#include <openssl/ec.h>
15#include <openssl/evp.h>
16#include <openssl/obj_mac.h>
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080017#include <openssl/objects.h>
Ed Tanousf0b59af2024-03-20 13:38:04 -070018#include <openssl/types.h>
19#include <openssl/x509.h>
20#include <openssl/x509_vfy.h>
21#include <openssl/x509v3.h>
Ed Tanous6dbe9be2024-04-14 10:24:20 -070022}
Ed Tanousf0b59af2024-03-20 13:38:04 -070023
Ed Tanouse7923992023-12-03 14:14:02 -080024#include <boost/asio/ip/address.hpp>
25#include <boost/asio/ssl/verify_context.hpp>
26
Ed Tanousf0b59af2024-03-20 13:38:04 -070027#include <array>
Ed Tanouse7923992023-12-03 14:14:02 -080028#include <memory>
29
30#include <gmock/gmock.h>
Ed Tanous478b7ad2024-07-15 19:11:54 -070031#include <gtest/gtest.h>
Ed Tanouse7923992023-12-03 14:14:02 -080032
33using ::testing::IsNull;
34using ::testing::NotNull;
35
36namespace
37{
38class 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;
Ed Tanous23f1c962023-12-05 15:57:46 -080050
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 }
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
Ed Tanouse7923992023-12-03 14:14:02 -080076 X509* get()
77 {
78 return ptr;
79 }
80 ~OSSLX509()
81 {
82 X509_free(ptr);
83 }
84};
85
86class 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;
98 X509_STORE_CTX* get()
99 {
100 return ptr;
101 }
102 ~OSSLX509StoreCTX()
103 {
104 X509_STORE_CTX_free(ptr);
105 }
106};
107
108TEST(MutualTLS, GoodCert)
109{
110 OSSLX509 x509;
111
Ed Tanous23f1c962023-12-05 15:57:46 -0800112 x509.setSubjectName();
Ed Tanouse7923992023-12-03 14:14:02 -0800113 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
Ed Tanous23f1c962023-12-05 15:57:46 -0800123 x509.sign();
124
Ed Tanouse7923992023-12-03 14:14:02 -0800125 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());
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400130 std::shared_ptr<persistent_data::UserSession> session =
131 verifyMtlsUser(ip, ctx);
Ed Tanouse7923992023-12-03 14:14:02 -0800132 ASSERT_THAT(session, NotNull());
133 EXPECT_THAT(session->username, "user");
134}
135
Ed Tanouse7923992023-12-03 14:14:02 -0800136TEST(MutualTLS, MissingKeyUsage)
137{
Ed Tanous23f1c962023-12-05 15:57:46 -0800138 for (const char* usageString :
139 {"digitalSignature", "keyAgreement", "digitalSignature, keyAgreement"})
Ed Tanouse7923992023-12-03 14:14:02 -0800140 {
141 OSSLX509 x509;
Ed Tanous23f1c962023-12-05 15:57:46 -0800142 x509.setSubjectName();
Ed Tanouse7923992023-12-03 14:14:02 -0800143
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400144 X509_EXTENSION* ex =
145 X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, usageString);
Ed Tanouse7923992023-12-03 14:14:02 -0800146
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);
Ed Tanous23f1c962023-12-05 15:57:46 -0800155 x509.sign();
Ed Tanouse7923992023-12-03 14:14:02 -0800156
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);
Ed Tanous23f1c962023-12-05 15:57:46 -0800164 ASSERT_THAT(session, NotNull());
Ed Tanouse7923992023-12-03 14:14:02 -0800165 }
166}
167
Ed Tanouse7923992023-12-03 14:14:02 -0800168TEST(MutualTLS, MissingCert)
169{
170 OSSLX509StoreCTX x509Store;
171
172 boost::asio::ip::address ip;
173 boost::asio::ssl::verify_context ctx(x509Store.get());
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400174 std::shared_ptr<persistent_data::UserSession> session =
175 verifyMtlsUser(ip, ctx);
Ed Tanouse7923992023-12-03 14:14:02 -0800176 ASSERT_THAT(session, IsNull());
177}
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800178
179TEST(GetCommonNameFromCert, EmptyCommonName)
180{
181 OSSLX509 x509;
182 std::string commonName = getCommonNameFromCert(x509.get());
183 EXPECT_THAT(commonName, "");
184}
185
186TEST(GetCommonNameFromCert, ValidCommonName)
187{
188 OSSLX509 x509;
189 x509.setSubjectName();
190 std::string commonName = getCommonNameFromCert(x509.get());
191 EXPECT_THAT(commonName, "user");
192}
193
194TEST(GetUPNFromCert, EmptySubjectAlternativeName)
195{
196 OSSLX509 x509;
197 std::string upn = getUPNFromCert(x509.get(), "");
198 EXPECT_THAT(upn, "");
199}
200
201TEST(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
230TEST(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
265TEST(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
300TEST(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
335TEST(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}
Ed Tanouse7923992023-12-03 14:14:02 -0800343} // namespace