From e79239970c3701f12903e8ac1574b9210b69aebc Mon Sep 17 00:00:00 2001 From: Ed Tanous Date: Sun, 3 Dec 2023 14:14:02 -0800 Subject: Add mutual tls unit test Mutual TLS paths were not tested. Add some unit tests. Because CI doesn't actually compile dependent libraries with ASAN enabled, and these tests call into openssl, we need to add a check for if we're compiling with asan enabled. Tested: unit tests pass. Change-Id: I02dcb69708619cc00fffd840738c608db3ae8bdf Signed-off-by: Ed Tanous --- meson.build | 23 ++++++- subprojects/openssl.wrap | 16 +++++ test/http/mutual_tls.cpp | 174 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 subprojects/openssl.wrap create mode 100644 test/http/mutual_tls.cpp diff --git a/meson.build b/meson.build index 002b94900c..26e8fda16f 100644 --- a/meson.build +++ b/meson.build @@ -136,7 +136,6 @@ add_project_arguments( '-Wcast-align', '-Wconversion', '-Wformat=2', - '-Wold-style-cast', '-Woverloaded-virtual', '-Wsign-conversion', '-Wunused', @@ -260,8 +259,24 @@ bmcweb_dependencies = [] pam = cxx.find_library('pam', required: true) atomic = cxx.find_library('atomic', required: true) -openssl = dependency('openssl', required : true) -bmcweb_dependencies += [pam, atomic, openssl] +bmcweb_dependencies += [pam, atomic] + +openssl = dependency('openssl', required : false, version: '>=3.0.0') +if not openssl.found() or get_option('b_sanitize') != 'none' + openssl_proj = subproject( + 'openssl', + required: true, + default_options: ['warning_level=0', 'werror=false'] + ) + openssl = openssl_proj.get_variable('openssl_dep') + openssl = openssl.as_system('system') +else + # When we build openssl as a subproject, the warnings analyzer starts + # flagging all the C macros as old style casts, so only enable the + # warning for non subprojects. + add_project_arguments('-Wold-style-cast', language: 'cpp') +endif +bmcweb_dependencies += [openssl] nghttp2 = dependency('libnghttp2', version: '>=1.52.0', required : false) if not nghttp2.found() @@ -408,6 +423,7 @@ executable( srcfiles_unittest = files( 'test/http/crow_getroutes_test.cpp', 'test/http/http_connection_test.cpp', + 'test/http/mutual_tls.cpp', 'test/http/router_test.cpp', 'test/http/http_response_test.cpp', 'test/http/utility_test.cpp', @@ -442,6 +458,7 @@ srcfiles_unittest = files( 'test/redfish-core/lib/power_subsystem_test.cpp', ) + if(get_option('tests').allowed()) # generate the test executable foreach test_src : srcfiles_unittest diff --git a/subprojects/openssl.wrap b/subprojects/openssl.wrap new file mode 100644 index 0000000000..b1d64e4035 --- /dev/null +++ b/subprojects/openssl.wrap @@ -0,0 +1,16 @@ +[wrap-file] +directory = openssl-3.0.8 +source_url = https://www.openssl.org/source/openssl-3.0.8.tar.gz +source_filename = openssl-3.0.8.tar.gz +source_hash = 6c13d2bf38fdf31eac3ce2a347073673f5d63263398f1f69d0df4a41253e4b3e +patch_filename = openssl_3.0.8-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.8-2/get_patch +patch_hash = e84b5fe469e681e3318184157a0c7c43d4cbacd078bb88f506e31569f8f75072 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/openssl_3.0.8-2/openssl-3.0.8.tar.gz +wrapdb_version = 3.0.8-2 + +[provide] +libcrypto = libcrypto_dep +libssl = libssl_dep +openssl = openssl_dep + diff --git a/test/http/mutual_tls.cpp b/test/http/mutual_tls.cpp new file mode 100644 index 0000000000..b1b7878586 --- /dev/null +++ b/test/http/mutual_tls.cpp @@ -0,0 +1,174 @@ +#include "mutual_tls.hpp" + +#include +#include + +#include + +#include +#include // IWYU pragma: keep + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace +{ +class OSSLX509 +{ + X509* ptr = X509_new(); + + public: + OSSLX509& operator=(const OSSLX509&) = delete; + OSSLX509& operator=(OSSLX509&&) = delete; + + OSSLX509(const OSSLX509&) = delete; + OSSLX509(OSSLX509&&) = delete; + + OSSLX509() = default; + X509* get() + { + return ptr; + } + ~OSSLX509() + { + X509_free(ptr); + } +}; + +class OSSLX509StoreCTX +{ + X509_STORE_CTX* ptr = X509_STORE_CTX_new(); + + public: + OSSLX509StoreCTX& operator=(const OSSLX509StoreCTX&) = delete; + OSSLX509StoreCTX& operator=(OSSLX509StoreCTX&&) = delete; + + OSSLX509StoreCTX(const OSSLX509StoreCTX&) = delete; + OSSLX509StoreCTX(OSSLX509StoreCTX&&) = delete; + + OSSLX509StoreCTX() = default; + X509_STORE_CTX* get() + { + return ptr; + } + ~OSSLX509StoreCTX() + { + X509_STORE_CTX_free(ptr); + } +}; + +TEST(MutualTLS, GoodCert) +{ + OSSLX509 x509; + + X509_NAME* name = X509_get_subject_name(x509.get()); + std::array user = {'u', 's', 'e', 'r', '\0'}; + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, user.data(), -1, -1, + 0); + + X509_EXTENSION* ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, + "digitalSignature, keyAgreement"); + ASSERT_THAT(ex, NotNull()); + ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); + X509_EXTENSION_free(ex); + ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage, "clientAuth"); + ASSERT_THAT(ex, NotNull()); + ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); + X509_EXTENSION_free(ex); + + OSSLX509StoreCTX x509Store; + X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get()); + + boost::asio::ip::address ip; + boost::asio::ssl::verify_context ctx(x509Store.get()); + std::shared_ptr session = verifyMtlsUser(ip, + ctx); + ASSERT_THAT(session, NotNull()); + EXPECT_THAT(session->username, "user"); +} + +TEST(MutualTLS, MissingSubject) +{ + OSSLX509 x509; + + X509_EXTENSION* ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, + "digitalSignature, keyAgreement"); + ASSERT_THAT(ex, NotNull()); + ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); + X509_EXTENSION_free(ex); + ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage, "clientAuth"); + ASSERT_THAT(ex, NotNull()); + ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); + X509_EXTENSION_free(ex); + + OSSLX509StoreCTX x509Store; + X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get()); + + boost::asio::ip::address ip; + boost::asio::ssl::verify_context ctx(x509Store.get()); + std::shared_ptr session = verifyMtlsUser(ip, + ctx); + ASSERT_THAT(session, IsNull()); +} + +TEST(MutualTLS, MissingKeyUsage) +{ + for (const char* usageString : {"digitalSignature", "keyAgreement"}) + { + OSSLX509 x509; + + X509_EXTENSION* ex = X509V3_EXT_conf_nid(nullptr, nullptr, + NID_key_usage, usageString); + + ASSERT_THAT(ex, NotNull()); + ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); + X509_EXTENSION_free(ex); + ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_ext_key_usage, + "clientAuth"); + ASSERT_THAT(ex, NotNull()); + ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); + X509_EXTENSION_free(ex); + + OSSLX509StoreCTX x509Store; + X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get()); + + boost::asio::ip::address ip; + boost::asio::ssl::verify_context ctx(x509Store.get()); + std::shared_ptr session = + verifyMtlsUser(ip, ctx); + ASSERT_THAT(session, IsNull()); + } +} + +TEST(MutualTLS, MissingExtKeyUsage) +{ + OSSLX509 x509; + + X509_EXTENSION* ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_key_usage, + "digitalSignature, keyAgreement"); + + ASSERT_THAT(ex, NotNull()); + ASSERT_EQ(X509_add_ext(x509.get(), ex, -1), 1); + X509_EXTENSION_free(ex); + + OSSLX509StoreCTX x509Store; + X509_STORE_CTX_set_current_cert(x509Store.get(), x509.get()); + + boost::asio::ip::address ip; + boost::asio::ssl::verify_context ctx(x509Store.get()); + std::shared_ptr session = verifyMtlsUser(ip, + ctx); + ASSERT_THAT(session, IsNull()); +} + +TEST(MutualTLS, MissingCert) +{ + OSSLX509StoreCTX x509Store; + + boost::asio::ip::address ip; + boost::asio::ssl::verify_context ctx(x509Store.get()); + std::shared_ptr session = verifyMtlsUser(ip, + ctx); + ASSERT_THAT(session, IsNull()); +} +} // namespace -- cgit v1.2.3