1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #include "mutual_tls.hpp" 4 5 #include "mutual_tls_private.hpp" 6 #include "sessions.hpp" 7 8 #include <cstring> 9 #include <string> 10 11 extern "C" 12 { 13 #include <openssl/asn1.h> 14 #include <openssl/ec.h> 15 #include <openssl/evp.h> 16 #include <openssl/obj_mac.h> 17 #include <openssl/objects.h> 18 #include <openssl/types.h> 19 #include <openssl/x509.h> 20 #include <openssl/x509_vfy.h> 21 #include <openssl/x509v3.h> 22 } 23 24 #include <boost/asio/ip/address.hpp> 25 #include <boost/asio/ssl/verify_context.hpp> 26 27 #include <array> 28 #include <memory> 29 30 #include <gmock/gmock.h> 31 #include <gtest/gtest.h> 32 33 using ::testing::IsNull; 34 using ::testing::NotNull; 35 36 namespace 37 { 38 class OSSLX509 39 { 40 X509* ptr = X509_new(); 41 42 public: 43 OSSLX509& operator=(const OSSLX509&) = delete; 44 OSSLX509& operator=(OSSLX509&&) = delete; 45 46 OSSLX509(const OSSLX509&) = delete; 47 OSSLX509(OSSLX509&&) = delete; 48 49 OSSLX509() = default; 50 51 void setSubjectName() 52 { 53 X509_NAME* name = X509_get_subject_name(ptr); 54 std::array<unsigned char, 5> user = {'u', 's', 'e', 'r', '\0'}; 55 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, user.data(), -1, 56 -1, 0); 57 } 58 void sign() 59 { 60 // Generate test key 61 EVP_PKEY* pkey = nullptr; 62 EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr); 63 ASSERT_EQ(EVP_PKEY_keygen_init(pctx), 1); 64 ASSERT_EQ( 65 EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1), 66 1); 67 ASSERT_EQ(EVP_PKEY_keygen(pctx, &pkey), 1); 68 EVP_PKEY_CTX_free(pctx); 69 70 // Sign cert with key 71 ASSERT_EQ(X509_set_pubkey(ptr, pkey), 1); 72 ASSERT_GT(X509_sign(ptr, pkey, EVP_sha256()), 0); 73 EVP_PKEY_free(pkey); 74 } 75 76 X509* get() 77 { 78 return ptr; 79 } 80 ~OSSLX509() 81 { 82 X509_free(ptr); 83 } 84 }; 85 86 class OSSLX509StoreCTX 87 { 88 X509_STORE_CTX* ptr = X509_STORE_CTX_new(); 89 90 public: 91 OSSLX509StoreCTX& operator=(const OSSLX509StoreCTX&) = delete; 92 OSSLX509StoreCTX& operator=(OSSLX509StoreCTX&&) = delete; 93 94 OSSLX509StoreCTX(const OSSLX509StoreCTX&) = delete; 95 OSSLX509StoreCTX(OSSLX509StoreCTX&&) = delete; 96 97 OSSLX509StoreCTX() = default; 98 X509_STORE_CTX* get() 99 { 100 return ptr; 101 } 102 ~OSSLX509StoreCTX() 103 { 104 X509_STORE_CTX_free(ptr); 105 } 106 }; 107 108 TEST(MutualTLS, GoodCert) 109 { 110 OSSLX509 x509; 111 112 x509.setSubjectName(); 113 X509_EXTENSION* ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, 114 "digitalSignature, keyAgreement"); 115 ASSERT_THAT(ex, NotNull()); 116 ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); 117 X509_EXTENSION_free(ex); 118 ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage, "clientAuth"); 119 ASSERT_THAT(ex, NotNull()); 120 ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); 121 X509_EXTENSION_free(ex); 122 123 x509.sign(); 124 125 OSSLX509StoreCTX x509Store; 126 X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get()); 127 128 boost::asio::ip::address ip; 129 boost::asio::ssl::verify_context ctx(x509Store.get()); 130 std::shared_ptr<persistent_data::UserSession> session = 131 verifyMtlsUser(ip, ctx); 132 ASSERT_THAT(session, NotNull()); 133 EXPECT_THAT(session->username, "user"); 134 } 135 136 TEST(MutualTLS, MissingKeyUsage) 137 { 138 for (const char* usageString : 139 {"digitalSignature", "keyAgreement", "digitalSignature, keyAgreement"}) 140 { 141 OSSLX509 x509; 142 x509.setSubjectName(); 143 144 X509_EXTENSION* ex = 145 X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, usageString); 146 147 ASSERT_THAT(ex, NotNull()); 148 ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); 149 X509_EXTENSION_free(ex); 150 ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage, 151 "clientAuth"); 152 ASSERT_THAT(ex, NotNull()); 153 ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); 154 X509_EXTENSION_free(ex); 155 x509.sign(); 156 157 OSSLX509StoreCTX x509Store; 158 X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get()); 159 160 boost::asio::ip::address ip; 161 boost::asio::ssl::verify_context ctx(x509Store.get()); 162 std::shared_ptr<persistent_data::UserSession> session = 163 verifyMtlsUser(ip, ctx); 164 ASSERT_THAT(session, NotNull()); 165 } 166 } 167 168 TEST(MutualTLS, MissingCert) 169 { 170 OSSLX509StoreCTX x509Store; 171 172 boost::asio::ip::address ip; 173 boost::asio::ssl::verify_context ctx(x509Store.get()); 174 std::shared_ptr<persistent_data::UserSession> session = 175 verifyMtlsUser(ip, ctx); 176 ASSERT_THAT(session, IsNull()); 177 } 178 179 TEST(GetCommonNameFromCert, EmptyCommonName) 180 { 181 OSSLX509 x509; 182 std::string commonName = getCommonNameFromCert(x509.get()); 183 EXPECT_THAT(commonName, ""); 184 } 185 186 TEST(GetCommonNameFromCert, ValidCommonName) 187 { 188 OSSLX509 x509; 189 x509.setSubjectName(); 190 std::string commonName = getCommonNameFromCert(x509.get()); 191 EXPECT_THAT(commonName, "user"); 192 } 193 194 TEST(GetUPNFromCert, EmptySubjectAlternativeName) 195 { 196 OSSLX509 x509; 197 std::string upn = getUPNFromCert(x509.get(), ""); 198 EXPECT_THAT(upn, ""); 199 } 200 201 TEST(GetUPNFromCert, NonOthernameSubjectAlternativeName) 202 { 203 OSSLX509 x509; 204 205 ASN1_IA5STRING* ia5 = ASN1_IA5STRING_new(); 206 ASSERT_THAT(ia5, NotNull()); 207 208 const char* user = "user@domain.com"; 209 ASSERT_NE(ASN1_STRING_set(ia5, user, static_cast<int>(strlen(user))), 0); 210 211 GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null(); 212 ASSERT_THAT(gens, NotNull()); 213 214 GENERAL_NAME* gen = GENERAL_NAME_new(); 215 ASSERT_THAT(gen, NotNull()); 216 217 GENERAL_NAME_set0_value(gen, GEN_EMAIL, ia5); 218 ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1); 219 220 ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0), 221 1); 222 223 std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com"); 224 EXPECT_THAT(upn, ""); 225 226 GENERAL_NAME_free(gen); 227 sk_GENERAL_NAME_free(gens); 228 } 229 230 TEST(GetUPNFromCert, NonUPNSubjectAlternativeName) 231 { 232 OSSLX509 x509; 233 234 GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null(); 235 ASSERT_THAT(gens, NotNull()); 236 237 GENERAL_NAME* gen = GENERAL_NAME_new(); 238 ASSERT_THAT(gen, NotNull()); 239 240 ASN1_OBJECT* othType = OBJ_nid2obj(NID_SRVName); 241 242 ASN1_TYPE* value = ASN1_TYPE_new(); 243 ASSERT_THAT(value, NotNull()); 244 value->type = V_ASN1_UTF8STRING; 245 246 // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) 247 value->value.utf8string = ASN1_UTF8STRING_new(); 248 ASSERT_THAT(value->value.utf8string, NotNull()); 249 const char* user = "user@domain.com"; 250 ASN1_STRING_set(value->value.utf8string, user, 251 static_cast<int>(strlen(user))); 252 // NOLINTEND(cppcoreguidelines-pro-type-union-access) 253 254 ASSERT_EQ(GENERAL_NAME_set0_othername(gen, othType, value), 1); 255 ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1); 256 ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0), 257 1); 258 259 std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com"); 260 EXPECT_THAT(upn, ""); 261 262 sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); 263 } 264 265 TEST(GetUPNFromCert, NonUTF8UPNSubjectAlternativeName) 266 { 267 OSSLX509 x509; 268 269 GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null(); 270 ASSERT_THAT(gens, NotNull()); 271 272 GENERAL_NAME* gen = GENERAL_NAME_new(); 273 ASSERT_THAT(gen, NotNull()); 274 275 ASN1_OBJECT* othType = OBJ_nid2obj(NID_ms_upn); 276 277 ASN1_TYPE* value = ASN1_TYPE_new(); 278 ASSERT_THAT(value, NotNull()); 279 value->type = V_ASN1_OCTET_STRING; 280 281 // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) 282 value->value.octet_string = ASN1_OCTET_STRING_new(); 283 ASSERT_THAT(value->value.octet_string, NotNull()); 284 const char* user = "0123456789"; 285 ASN1_STRING_set(value->value.octet_string, user, 286 static_cast<int>(strlen(user))); 287 // NOLINTEND(cppcoreguidelines-pro-type-union-access) 288 289 ASSERT_EQ(GENERAL_NAME_set0_othername(gen, othType, value), 1); 290 ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1); 291 ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0), 292 1); 293 294 std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com"); 295 EXPECT_THAT(upn, ""); 296 297 sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); 298 } 299 300 TEST(GetUPNFromCert, ValidUPN) 301 { 302 OSSLX509 x509; 303 304 GENERAL_NAMES* gens = sk_GENERAL_NAME_new_null(); 305 ASSERT_THAT(gens, NotNull()); 306 307 GENERAL_NAME* gen = GENERAL_NAME_new(); 308 ASSERT_THAT(gen, NotNull()); 309 310 ASN1_OBJECT* othType = OBJ_nid2obj(NID_ms_upn); 311 312 ASN1_TYPE* value = ASN1_TYPE_new(); 313 ASSERT_THAT(value, NotNull()); 314 value->type = V_ASN1_UTF8STRING; 315 316 // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) 317 value->value.utf8string = ASN1_UTF8STRING_new(); 318 ASSERT_THAT(value->value.utf8string, NotNull()); 319 const char* user = "user@domain.com"; 320 ASN1_STRING_set(value->value.utf8string, user, 321 static_cast<int>(strlen(user))); 322 // NOLINTEND(cppcoreguidelines-pro-type-union-access) 323 324 ASSERT_EQ(GENERAL_NAME_set0_othername(gen, othType, value), 1); 325 ASSERT_EQ(sk_GENERAL_NAME_push(gens, gen), 1); 326 ASSERT_EQ(X509_add1_ext_i2d(x509.get(), NID_subject_alt_name, gens, 0, 0), 327 1); 328 329 std::string upn = getUPNFromCert(x509.get(), "hostname.domain.com"); 330 EXPECT_THAT(upn, "user"); 331 332 sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); 333 } 334 335 TEST(IsUPNMatch, MultipleCases) 336 { 337 EXPECT_FALSE(isUPNMatch("user", "hostname.domain.com")); 338 EXPECT_TRUE(isUPNMatch("user@domain.com", "hostname.domain.com")); 339 EXPECT_FALSE(isUPNMatch("user@domain.com", "hostname.domain.org")); 340 EXPECT_FALSE(isUPNMatch("user@region.com", "hostname.domain.com")); 341 EXPECT_TRUE(isUPNMatch("user@com", "hostname.region.domain.com")); 342 } 343 } // namespace 344