summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--meson.build23
-rw-r--r--subprojects/openssl.wrap16
-rw-r--r--test/http/mutual_tls.cpp174
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