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