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