diff options
-rw-r--r-- | meson.build | 23 | ||||
-rw-r--r-- | subprojects/openssl.wrap | 16 | ||||
-rw-r--r-- | test/http/mutual_tls.cpp | 174 |
3 files changed, 210 insertions, 3 deletions
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 <boost/asio/ip/address.hpp> +#include <boost/asio/ssl/verify_context.hpp> + +#include <memory> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> // 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<unsigned char, 5> 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<persistent_data::UserSession> 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<persistent_data::UserSession> 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<persistent_data::UserSession> 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<persistent_data::UserSession> 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<persistent_data::UserSession> session = verifyMtlsUser(ip, + ctx); + ASSERT_THAT(session, IsNull()); +} +} // namespace |