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