diff --git a/httplib.h b/httplib.h index 25eade33..4c5073ad 100644 --- a/httplib.h +++ b/httplib.h @@ -20770,6 +20770,8 @@ struct MbedTlsSession { // ever runs the callback of its own context. VerifyCallback verify_callback; + bool is_server = false; + MbedTlsSession() { mbedtls_ssl_init(&ssl); } ~MbedTlsSession() { mbedtls_ssl_free(&ssl); } @@ -20949,7 +20951,7 @@ inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt, // set_sni() disabled hostname verification for this session: ignore the // CN/SAN mismatch flag, as the OpenSSL/wolfSSL backends do. The final - // pass/fail decision still comes from the remaining flags (or, below, from + // pass/fail decision still comes from the remaining flags (and, below, from // the user's own verify callback). if (session->suppress_hostname_mismatch) { mbedtls_clear_cn_mismatch(flags); } @@ -20976,7 +20978,9 @@ inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt, bool accepted = run_verify_callback(callback, verify_ctx); if (accepted) { - *flags = 0; // Clear all error flags + // Overrides the built-in verification only on a server, as on OpenSSL and + // wolfSSL, whose clients check the verify result after the handshake + if (session->is_server) { *flags = 0; } return 0; } return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; @@ -21379,6 +21383,7 @@ inline session_t create_session(ctx_t ctx, socket_t sock) { // may ask for. The session runs its own copy of the user's callback, which // set_verify_callback() cannot replace under a handshake. session->verify_callback = mctx->verify_callback; + session->is_server = mctx->is_server; mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback, session); return static_cast(session); diff --git a/test/test.cc b/test/test.cc index 5884f512..a2a90d09 100644 --- a/test/test.cc +++ b/test/test.cc @@ -17060,6 +17060,45 @@ TEST(VerifyCallbackTest, VerifyContextFields) { EXPECT_TRUE(saw_leaf_cert); } +// A verifier that accepts runs alongside the built-in verification, and is the +// only check once that is turned off. +TEST(VerifyCallbackTest, AcceptingVerifierDoesNotOverrideVerification) { + using namespace httplib::tls; + + SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE); + ASSERT_TRUE(svr.is_valid()); + svr.Get("/test", [](const Request &, Response &res) { + res.set_content("ok", "text/plain"); + }); + + auto port = svr.bind_to_any_port(HOST); + thread t([&]() { svr.listen_after_bind(); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + }); + svr.wait_until_ready(); + + SSLClient cli(HOST, port); + cli.enable_server_hostname_verification(false); + auto calls = 0; + cli.set_server_certificate_verifier([&](const VerifyContext &) { + calls++; + return true; + }); + + // cert.pem is not trusted + auto res = cli.Get("/test"); + ASSERT_FALSE(res); + EXPECT_EQ(Error::SSLServerVerification, res.error()); + EXPECT_GT(calls, 0); + + cli.enable_server_certificate_verification(false); + res = cli.Get("/test"); + ASSERT_TRUE(res) << "Error: " << to_string(res.error()); + EXPECT_EQ("ok", res->body); +} + // A verifier belongs to the client that set it, not to the thread that set it. TEST(VerifyCallbackTest, VerifierStaysWithItsClient) { using namespace httplib::tls;