فهرست منبع

Carry the server certificate verifier over to the redirect client (#2634)

* Carry the server certificate verifier over to the redirect client

create_redirect_client() now copies the CA store and the session verifier
to the SSLClient it builds for a cross-host https redirect (#2632), but a
verifier given to set_server_certificate_verifier() was still dropped: the
redirect client's ctx never had the verify callback wired in, so a
pinning verifier never ran on the host the server redirects to.

SSLClient now keeps its own copy of the verifier, since ctx_ cannot report
it back and the backends hold it in a slot shared by every client, and
installs it on the redirect client. The copy also replaces the
Windows-only server_certificate_verifier_set_ flag.

SSLClientRedirectTest.ServerCertificateVerifier fails before this change
and passes after (OpenSSL 3, Mbed TLS 2.28, wolfSSL 5.6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAYpQgXt95LdDQcJYgjB8

* Clarify the server_certificate_verifier_ comment

The backends keep the verify callback in a per-thread slot, not one
shared by every client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAYpQgXt95LdDQcJYgjB8

---------

Co-authored-by: Claude <noreply@anthropic.com>
yhirose 5 روز پیش
والد
کامیت
9f6dcf1230
2فایلهای تغییر یافته به همراه69 افزوده شده و 8 حذف شده
  1. 14 8
      httplib.h
  2. 55 0
      test/test.cc

+ 14 - 8
httplib.h

@@ -3454,11 +3454,13 @@ private:
 
   std::function<SSLVerifierResponse(tls::session_t)> 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<bool>(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<bool>(server_certificate_verifier_);
 #endif
 
   tls::session_t session = nullptr;

+ 55 - 0
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<bool> 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<int> 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