diff --git a/httplib.h b/httplib.h index 888f78d4..e0d85e26 100644 --- a/httplib.h +++ b/httplib.h @@ -4013,6 +4013,8 @@ struct MbedTlsContext { mbedtls_x509_crt own_cert; mbedtls_pk_context own_key; bool is_server = false; + // Whether the peer's certificate is required; set by + // tls::set_verify_client(). A client verifies the server by default. bool verify_client = false; // Set by tls::set_verify_callback(). VerifyCallback verify_callback; @@ -4038,6 +4040,8 @@ namespace impl { struct WolfSSLContext { WOLFSSL_CTX *ctx = nullptr; bool is_server = false; + // Whether the peer's certificate is required; set by + // tls::set_verify_client(). A client verifies the server by default. bool verify_client = false; // Set by tls::set_verify_callback(). VerifyCallback verify_callback; @@ -19307,6 +19311,17 @@ inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { return (*callback)(verify_ctx) ? 1 : 0; } +// The verify mode follows from whether the peer's certificate is required and +// whether a verify callback is set, however the two were last changed. +inline void apply_verify_mode(SSL_CTX *ssl_ctx, bool require) { + auto callback = get_openssl_verify_callback(ssl_ctx); + auto has_callback = callback && *callback; + auto mode = require ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) + : (has_callback ? SSL_VERIFY_PEER : SSL_VERIFY_NONE); + SSL_CTX_set_verify(ssl_ctx, mode, + has_callback ? openssl_verify_callback : nullptr); +} + // X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not // thread-safe; X509_STORE_get1_objects (OpenSSL 3.3+) returns a snapshot // that must be released with release_store_objects @@ -19537,11 +19552,7 @@ inline ctx_t create_server_context() { inline void set_verify_client(ctx_t ctx, bool require) { if (!ctx) return; - SSL_CTX_set_verify(static_cast(ctx), - require - ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) - : SSL_VERIFY_NONE, - nullptr); + impl::apply_verify_mode(static_cast(ctx), require); } inline session_t create_session(ctx_t ctx, socket_t sock) { @@ -20192,11 +20203,10 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) { } delete old; - if (stored) { - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback); - } else { - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); - } + // Keep requiring the peer's certificate if set_verify_client() asked for it + auto require = + (SSL_CTX_get_verify_mode(ssl_ctx) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) != 0; + impl::apply_verify_mode(ssl_ctx, require); return true; } @@ -20464,6 +20474,20 @@ inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt, return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; } +// The authentication mode follows from whether the peer's certificate is +// required and whether a verify callback is set, however the two were last +// changed. OPTIONAL makes sure the callback runs even if verification is not +// required (matching OpenSSL, where SSL_VERIFY_PEER is set). +inline void apply_authmode(MbedTlsContext &mctx) { + auto mode = MBEDTLS_SSL_VERIFY_NONE; + if (mctx.verify_client) { + mode = MBEDTLS_SSL_VERIFY_REQUIRED; + } else if (mctx.verify_callback) { + mode = MBEDTLS_SSL_VERIFY_OPTIONAL; + } + mbedtls_ssl_conf_authmode(&mctx.conf, mode); +} + } // namespace impl inline ctx_t create_client_context() { @@ -20508,7 +20532,8 @@ inline ctx_t create_client_context() { #endif // Default: verify peer certificate - mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); + ctx->verify_client = true; + impl::apply_authmode(*ctx); // Set minimum TLS version to 1.2 #ifdef CPPHTTPLIB_MBEDTLS_V3 @@ -20563,7 +20588,7 @@ inline ctx_t create_server_context() { #endif // Default: don't verify client - mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE); + impl::apply_authmode(*ctx); // Set minimum TLS version to 1.2 #ifdef CPPHTTPLIB_MBEDTLS_V3 @@ -20812,15 +20837,7 @@ inline void set_verify_client(ctx_t ctx, bool require) { if (!ctx) { return; } auto mctx = static_cast(ctx); mctx->verify_client = require; - if (require) { - mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); - } else { - // If a verify callback is set, use OPTIONAL mode to ensure the callback - // is called (matching OpenSSL behavior). Otherwise use NONE. - mbedtls_ssl_conf_authmode(&mctx->conf, mctx->verify_callback - ? MBEDTLS_SSL_VERIFY_OPTIONAL - : MBEDTLS_SSL_VERIFY_NONE); - } + impl::apply_authmode(*mctx); } inline session_t create_session(ctx_t ctx, socket_t sock) { @@ -21558,14 +21575,9 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) { if (!ctx) { return false; } auto *mbed_ctx = static_cast(ctx); + // The callback itself is wired per session in create_session() mbed_ctx->verify_callback = std::move(callback); - - if (mbed_ctx->verify_callback) { - // Set OPTIONAL mode to ensure callback is called even when verification - // is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set). - // The callback itself is wired per session in create_session(). - mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL); - } + impl::apply_authmode(*mbed_ctx); return true; } @@ -21722,6 +21734,17 @@ inline int wolfssl_verify_callback(int preverify_ok, return accepted ? 1 : 0; } +// The verify mode follows from whether the peer's certificate is required and +// whether a verify callback is set, however the two were last changed. +inline void apply_verify_mode(WolfSSLContext &wctx) { + auto has_callback = static_cast(wctx.verify_callback); + auto mode = wctx.verify_client + ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) + : (has_callback ? SSL_VERIFY_PEER : SSL_VERIFY_NONE); + wolfSSL_CTX_set_verify(wctx.ctx, mode, + has_callback ? wolfssl_verify_callback : nullptr); +} + inline void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) { wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast(password)); wolfSSL_CTX_set_default_passwd_cb( @@ -21756,7 +21779,8 @@ inline ctx_t create_client_context() { } // Default: verify peer certificate - wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr); + ctx->verify_client = true; + impl::apply_verify_mode(*ctx); return static_cast(ctx); } @@ -21780,7 +21804,7 @@ inline ctx_t create_server_context() { } // Default: don't verify client - wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr); + impl::apply_verify_mode(*ctx); // Enable SNI on server wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME, @@ -21953,18 +21977,7 @@ inline void set_verify_client(ctx_t ctx, bool require) { if (!ctx) { return; } auto wctx = static_cast(ctx); wctx->verify_client = require; - if (require) { - wolfSSL_CTX_set_verify( - wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, - wctx->verify_callback ? impl::wolfssl_verify_callback : nullptr); - } else { - if (wctx->verify_callback) { - wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER, - impl::wolfssl_verify_callback); - } else { - wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr); - } - } + impl::apply_verify_mode(*wctx); } inline session_t create_session(ctx_t ctx, socket_t sock) { @@ -22716,18 +22729,7 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) { auto *wctx = static_cast(ctx); wctx->verify_callback = std::move(callback); - - if (wctx->verify_callback) { - wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER, - impl::wolfssl_verify_callback); - } else { - wolfSSL_CTX_set_verify( - wctx->ctx, - wctx->verify_client - ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) - : SSL_VERIFY_NONE, - nullptr); - } + impl::apply_verify_mode(*wctx); return true; } diff --git a/test/test.cc b/test/test.cc index 090b9cb1..8e55411b 100644 --- a/test/test.cc +++ b/test/test.cc @@ -14571,6 +14571,59 @@ TEST(SSLClientServerTest, ClientEncryptedCertPresent) { CLIENT_ENCRYPTED_PRIVATE_KEY_PASS); } +// A server that requires client certificates must keep requiring them after a +// verify callback is set on its context, or set and then cleared again. +void ClientCertStillRequired(bool clear_callback) { + using namespace httplib::tls; + + SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE, + CLIENT_CA_CERT_DIR); + ASSERT_TRUE(svr.is_valid()); + svr.Get("/test", [](const Request &, Response &res) { + res.set_content("test", "text/plain"); + }); + + ASSERT_TRUE(set_verify_callback(svr.tls_context(), + [](const VerifyContext &) { return true; })); + if (clear_callback) { + ASSERT_TRUE(set_verify_callback(svr.tls_context(), nullptr)); + } + + auto port = svr.bind_to_any_port(HOST); + ASSERT_LT(0, port); + thread t = thread([&]() { svr.listen_after_bind(); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + }); + svr.wait_until_ready(); + + { + SSLClient cli(HOST, port); + cli.enable_server_certificate_verification(false); + cli.set_connection_timeout(30); + EXPECT_FALSE(cli.Get("/test")); + } + + { + SSLClient cli(HOST, port, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE); + cli.enable_server_certificate_verification(false); + cli.set_connection_timeout(30); + auto res = cli.Get("/test"); + ASSERT_TRUE(res) << "Error: " << to_string(res.error()); + EXPECT_EQ(StatusCode::OK_200, res->status); + } +} + +TEST(SSLClientServerTest, ClientCertStillRequiredAfterVerifyCallbackSet) { + ClientCertStillRequired(false); +} + +TEST(SSLClientServerTest, ClientCertStillRequiredAfterVerifyCallbackCleared) { + ClientCertStillRequired(true); +} + // PEM memory-based constructor tests (works with all TLS backends) void PemMemoryClientCertPresent( const std::string &client_cert_file,