From 489fe8e504306622469bfd2c0f41847ca4960ac4 Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 16 Oct 2026 21:38:40 -0400 Subject: [PATCH] Give a redirect client the CAs its client trusts With set_ca_cert_store() after load_ca_cert_store(), the redirect client trusted the earlier PEM too, though the store had replaced it, and of several load_ca_cert_store() calls it kept only the last. Take the CAs from the context when a store was set, and from every PEM loaded otherwise. --- httplib.h | 14 ++++++++++---- test/test.cc | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/httplib.h b/httplib.h index f2969e8b..b4c9162d 100644 --- a/httplib.h +++ b/httplib.h @@ -16168,17 +16168,19 @@ inline bool ClientImpl::create_redirect_client( redirect_client.system_ca_mode_ = system_ca_mode_; // Transfer CA certificate to redirect client - redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(), - ca_cert_pem_.size()); redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_); if (is_ssl()) { auto &self = static_cast(*this); // A store given to set_ca_cert_store() lives only in this client's - // context, so pass on the CA certificates the context holds. + // context, which then holds all that is trusted. if (self.ca_cert_store_set_) { auto pem = detail::ca_certs_to_pem(self.ctx_); redirect_client.load_ca_cert_store(pem.data(), pem.size()); + redirect_client.ca_cert_store_set_ = true; + } else { + redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(), + ca_cert_pem_.size()); } redirect_client.set_session_verifier(self.session_verifier_); if (self.server_certificate_verifier_) { @@ -19051,7 +19053,11 @@ inline void SSLClient::enable_windows_certificate_verification(bool enabled) { inline void SSLClient::load_ca_cert_store(const char *ca_cert, std::size_t size) { if (ctx_ && ca_cert && size > 0) { - ca_cert_pem_.assign(ca_cert, size); // Store for redirect transfer + // Every PEM loaded, for redirect transfer + if (!ca_cert_pem_.empty() && ca_cert_pem_.back() != '\n') { + ca_cert_pem_ += '\n'; + } + ca_cert_pem_.append(ca_cert, size); tls::load_ca_pem(ctx_, ca_cert, size); } } diff --git a/test/test.cc b/test/test.cc index 93dd77b3..a53ed0dc 100644 --- a/test/test.cc +++ b/test/test.cc @@ -17226,7 +17226,8 @@ TEST(SSLClientRedirectTest, CertFile) { // another port, so the client follows it with a new client. static Result GetThroughTlsRedirectToDifferentPort( const std::function &setup, - const char *target_cert = SERVER_CERT2_FILE) { + const char *target_cert = SERVER_CERT2_FILE, + const char *target_key = SERVER_PRIVATE_KEY_FILE) { auto ssl_port = PORT + 1; SSLServer ssl_svr1(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE); @@ -17235,7 +17236,7 @@ static Result GetThroughTlsRedirectToDifferentPort( "/index"); }); - SSLServer ssl_svr2(target_cert, SERVER_PRIVATE_KEY_FILE); + SSLServer ssl_svr2(target_cert, target_key); ssl_svr2.Get("/index", [&](const Request &, Response &res) { res.set_content("test", "text/plain"); }); @@ -17299,6 +17300,42 @@ TEST(SSLClientRedirectTest, KeepCaCertStore) { ASSERT_TRUE(res) << "Error: " << to_string(res.error()); EXPECT_EQ(StatusCode::OK_200, res->status); } + +// The target's certificate is signed by the root CA, which the first server's +// is not. Its name is not checked, as it carries none of 127.0.0.1. +TEST(SSLClientRedirectTest, KeepEveryCaCertPem) { + auto res = GetThroughTlsRedirectToDifferentPort( + [](SSLClient &cli) { + std::string root_ca, cert; + read_file(CLIENT_CA_CERT_FILE, root_ca); + read_file(SERVER_CERT2_FILE, cert); + cli.load_ca_cert_store(root_ca.data(), root_ca.size()); + cli.load_ca_cert_store(cert.data(), cert.size()); + cli.enable_server_hostname_verification(false); + }, + CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE); + ASSERT_TRUE(res) << "Error: " << to_string(res.error()); + EXPECT_EQ(StatusCode::OK_200, res->status); +} + +#ifndef CPPHTTPLIB_WOLFSSL_SUPPORT +// set_ca_cert_store() replaces the root CA loaded before it, so the target is +// not trusted after the redirect either. (wolfSSL adds the store instead.) +TEST(SSLClientRedirectTest, CaCertStoreReplacesEarlierPem) { + auto res = GetThroughTlsRedirectToDifferentPort( + [](SSLClient &cli) { + std::string root_ca, cert; + read_file(CLIENT_CA_CERT_FILE, root_ca); + read_file(SERVER_CERT2_FILE, cert); + cli.load_ca_cert_store(root_ca.data(), root_ca.size()); + cli.set_ca_cert_store(tls::create_ca_store(cert.c_str(), cert.size())); + cli.enable_server_hostname_verification(false); + }, + CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE); + ASSERT_FALSE(res); + EXPECT_EQ(Error::SSLServerVerification, res.error()); +} +#endif #endif #ifdef CPPHTTPLIB_SSL_ENABLED