mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-17 10:59:39 +00:00
Fix set_ca_cert_store() breaking CA exclusivity and redirect CA transfer
Since the TLS abstraction layer was introduced, SSLClient::set_ca_cert_store() handed the store to the TLS context without leaving any trace on the client. As a result: - load_certs() merged system CA certs into the user-provided store, silently broadening the trust set (a custom store used to suppress system CA loading) - Client::load_ca_cert_store() went through the native store path, bypassing the PEM retention used for redirect transfer, so CA certs were not carried over to clients created for HTTPS redirects - The Windows Schannel verification skip for custom CA certs did not trigger Track custom store assignment with a flag checked by load_certs() and the Schannel path, and route Client::load_ca_cert_store() through the PEM-based SSLClient path so the CA data survives redirects.
This commit is contained in:
1 parent
78ff94e604
commit
e7e7bf7b44
2 files changed
+116
-3
No files matched your search
+103
@@ -51,6 +51,16 @@ inline std::string u8_to_string(const char8_t *s) {
|
||||
#define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem"
|
||||
#define SERVER_ENCRYPTED_PRIVATE_KEY_PASS "test123!"
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
namespace httplib {
|
||||
namespace tls {
|
||||
// Declared here for the split build, where the TLS abstraction declarations
|
||||
// live below the split border; the definition is linked from httplib.cc.
|
||||
ca_store_t create_ca_store(const char *pem, size_t len);
|
||||
} // namespace tls
|
||||
} // namespace httplib
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace httplib;
|
||||
|
||||
@@ -11625,6 +11635,99 @@ TEST(SSLClientTest, SetCaCertStoreSkipsSystemCerts_Online) {
|
||||
EXPECT_EQ(Error::SSLServerVerification, res.error());
|
||||
}
|
||||
|
||||
// Same as above, but through the native store handle path. Regression test:
|
||||
// set_ca_cert_store() used to leave no trace on the client, so load_certs()
|
||||
// merged system certs into the user-provided store.
|
||||
TEST(SSLClientTest, SetCaCertStoreNativeSkipsSystemCerts_Online) {
|
||||
std::string cert;
|
||||
read_file(SERVER_CERT2_FILE, cert);
|
||||
|
||||
SSLClient cli("google.com");
|
||||
cli.set_ca_cert_store(tls::create_ca_store(cert.c_str(), cert.size()));
|
||||
cli.enable_server_certificate_verification(true);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::SSLServerVerification, res.error());
|
||||
}
|
||||
|
||||
// A custom store set via the native handle must still verify a server whose
|
||||
// cert it does contain.
|
||||
TEST(SSLClientTest, SetCaCertStoreNativeVerifiesLocalServer) {
|
||||
SSLServer svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
svr.Get("/test", [&](const Request &, Response &res) {
|
||||
res.set_content("test", "text/plain");
|
||||
});
|
||||
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
SSLClient cli("127.0.0.1", PORT);
|
||||
std::string cert;
|
||||
read_file(SERVER_CERT2_FILE, cert);
|
||||
cli.set_ca_cert_store(tls::create_ca_store(cert.c_str(), cert.size()));
|
||||
cli.enable_server_certificate_verification(true);
|
||||
cli.set_connection_timeout(30);
|
||||
|
||||
auto res = cli.Get("/test");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
// CA certs loaded through the universal Client must be transferred to the
|
||||
// client created internally for following an HTTPS redirect. Regression test:
|
||||
// Client::load_ca_cert_store() used to bypass the PEM-based path that stores
|
||||
// the CA data for redirect transfer.
|
||||
TEST(UniversalClientRedirectTest, LoadCaCertStore) {
|
||||
auto ssl_port = PORT + 1;
|
||||
|
||||
SSLServer ssl_svr1(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(ssl_svr1.is_valid());
|
||||
ssl_svr1.Get("/index", [&](const Request &, Response &res) {
|
||||
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();
|
||||
|
||||
Client cli("https://127.0.0.1:" + std::to_string(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);
|
||||
|
||||
auto res = cli.Get("/index");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, LargeData) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
|
||||
Reference in new issue
Block a user