diff --git a/httplib.h b/httplib.h index 2b365e16..fa589b26 100644 --- a/httplib.h +++ b/httplib.h @@ -3454,11 +3454,13 @@ private: std::function session_verifier_; + // The verifier given to set_server_certificate_verifier(), kept for the + // redirect client: ctx_ cannot report it back, and the backends keep it in + // a per-thread slot that any other client may overwrite. + tls::VerifyCallback server_certificate_verifier_; + #ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE bool enable_windows_cert_verification_ = true; - // Like ca_cert_store_set_, tracks what ctx_ cannot report back: whether - // set_server_certificate_verifier() installed a verifier. - bool server_certificate_verifier_set_ = false; #endif friend class ClientImpl; @@ -15944,6 +15946,11 @@ inline bool ClientImpl::create_redirect_client( } redirect_client.session_verifier_ = self.session_verifier_; + + if (self.server_certificate_verifier_) { + redirect_client.set_server_certificate_verifier( + self.server_certificate_verifier_); + } } // Client certificates are set through constructor for SSLClient @@ -18751,10 +18758,8 @@ inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) { inline void SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) { if (!ctx_) { return; } -#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE - server_certificate_verifier_set_ = static_cast(verifier); -#endif - tls::set_verify_callback(ctx_, verifier); + server_certificate_verifier_ = verifier; + tls::set_verify_callback(ctx_, std::move(verifier)); } inline void SSLClient::set_session_verifier( @@ -18817,7 +18822,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_; // Only a verifier set through set_server_certificate_verifier() is seen // here, not one installed with tls::set_verify_callback() directly. - options.server_certificate_verifier_set = server_certificate_verifier_set_; + options.server_certificate_verifier_set = + static_cast(server_certificate_verifier_); #endif tls::session_t session = nullptr; diff --git a/test/test.cc b/test/test.cc index b54771e9..1d8ea604 100644 --- a/test/test.cc +++ b/test/test.cc @@ -16016,6 +16016,61 @@ TEST(SSLClientRedirectTest, SessionVerifier) { EXPECT_EQ(Error::SSLServerVerification, res.error()); EXPECT_EQ(2, sessions.load()); } + +// So must the verifier given to set_server_certificate_verifier(). +TEST(SSLClientRedirectTest, ServerCertificateVerifier) { + auto ssl_port = PORT + 1; + std::atomic redirected{false}; + + SSLServer ssl_svr1(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE); + ASSERT_TRUE(ssl_svr1.is_valid()); + ssl_svr1.Get("/index", [&](const Request &, Response &res) { + redirected = true; + res.set_redirect("https://127.0.0.1:" + std::to_string(ssl_port) + + "/index"); + }); + + SSLServer ssl_svr2(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE); + ASSERT_TRUE(ssl_svr2.is_valid()); + ssl_svr2.Get("/index", [&](const Request &, Response &res) { + res.set_content("test", "text/plain"); + }); + + thread t = thread([&]() { ASSERT_TRUE(ssl_svr1.listen("127.0.0.1", PORT)); }); + thread t2 = + thread([&]() { ASSERT_TRUE(ssl_svr2.listen("127.0.0.1", ssl_port)); }); + auto se = detail::scope_exit([&] { + ssl_svr2.stop(); + ssl_svr1.stop(); + t2.join(); + t.join(); + ASSERT_FALSE(ssl_svr1.is_running()); + }); + + ssl_svr1.wait_until_ready(); + ssl_svr2.wait_until_ready(); + + SSLClient cli("127.0.0.1", PORT); + std::string cert; + read_file(SERVER_CERT2_FILE, cert); + cli.load_ca_cert_store(cert.c_str(), cert.size()); + cli.enable_server_certificate_verification(true); + cli.set_follow_location(true); + cli.set_connection_timeout(30); + + // Accept the host that redirects only; the verifier runs once per + // certificate in the chain, so count the calls made after the redirect. + std::atomic calls_after_redirect{0}; + cli.set_server_certificate_verifier([&](const tls::VerifyContext &) { + if (!redirected) { return true; } + calls_after_redirect++; + return false; + }); + + auto res = cli.Get("/index"); + ASSERT_FALSE(res); + EXPECT_LT(0, calls_after_redirect.load()); +} #endif #ifdef CPPHTTPLIB_SSL_ENABLED