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 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 } 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 71 X509* get() 72 { 73 return ptr; 74 } 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; 93 X509_STORE_CTX* get() 94 { 95 return ptr; 96 } 97 ~OSSLX509StoreCTX() 98 { 99 X509_STORE_CTX_free(ptr); 100 } 101 }; 102 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 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 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