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