mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Carry the CA store and session verifier over to the redirect client (#2632)
This commit is contained in:
1 parent
16f9d4a68d
commit
d78a3cbb82
2 files changed
+143
-11
No files matched your search
@@ -10998,6 +10998,28 @@ inline bool load_client_ca_config(tls::ctx_t ctx,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serializes the CA certificates installed in ctx as PEM, so they can be
|
||||||
|
// loaded into another context.
|
||||||
|
inline std::string ca_certs_to_pem(tls::ctx_t ctx) {
|
||||||
|
std::string pem;
|
||||||
|
std::vector<tls::cert_t> certs;
|
||||||
|
tls::get_ca_certs(ctx, certs);
|
||||||
|
for (auto cert : certs) {
|
||||||
|
std::vector<unsigned char> der;
|
||||||
|
if (tls::get_cert_der(cert, der)) {
|
||||||
|
auto b64 = base64_encode(std::string(der.begin(), der.end()));
|
||||||
|
pem += "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
for (size_t i = 0; i < b64.size(); i += 64) {
|
||||||
|
pem += b64.substr(i, 64);
|
||||||
|
pem += '\n';
|
||||||
|
}
|
||||||
|
pem += "-----END CERTIFICATE-----\n";
|
||||||
|
}
|
||||||
|
tls::free_cert(cert);
|
||||||
|
}
|
||||||
|
return pem;
|
||||||
|
}
|
||||||
|
|
||||||
// The parts of session setup that only SSLClient needs, plus the handful
|
// The parts of session setup that only SSLClient needs, plus the handful
|
||||||
// WebSocketClient also exposes; everything else takes the defaults, which is
|
// WebSocketClient also exposes; everything else takes the defaults, which is
|
||||||
// what keeps the two clients on one implementation.
|
// what keeps the two clients on one implementation.
|
||||||
@@ -15903,6 +15925,27 @@ inline bool ClientImpl::create_redirect_client(
|
|||||||
redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
|
redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_ssl()) {
|
||||||
|
auto &self = static_cast<SSLClient &>(*this);
|
||||||
|
|
||||||
|
// A store handed to set_ca_cert_store() is owned by ctx_ and leaves no
|
||||||
|
// PEM behind. Without it the redirect client would fall back to the
|
||||||
|
// system CAs and trust roots this client was configured to exclude.
|
||||||
|
if (self.ca_cert_store_set_) {
|
||||||
|
std::string pem;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(self.ctx_mutex_);
|
||||||
|
pem = detail::ca_certs_to_pem(self.ctx_);
|
||||||
|
}
|
||||||
|
if (!pem.empty()) {
|
||||||
|
redirect_client.load_ca_cert_store(pem.data(), pem.size());
|
||||||
|
}
|
||||||
|
redirect_client.ca_cert_store_set_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect_client.session_verifier_ = self.session_verifier_;
|
||||||
|
}
|
||||||
|
|
||||||
// Client certificates are set through constructor for SSLClient
|
// Client certificates are set through constructor for SSLClient
|
||||||
// NOTE: SSLClient constructor already takes client_cert_path and
|
// NOTE: SSLClient constructor already takes client_cert_path and
|
||||||
// client_key_path so we need to create it properly if client certs are
|
// client_key_path so we need to create it properly if client certs are
|
||||||
@@ -21426,19 +21469,11 @@ inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
|
|||||||
if (!ctx) { return 0; }
|
if (!ctx) { return 0; }
|
||||||
auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
|
auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
|
||||||
|
|
||||||
// Iterate through the CA chain
|
// Iterate through the CA chain. The certificates stay owned by the context,
|
||||||
|
// as free_cert() does not release anything on this backend.
|
||||||
mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
|
mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
|
||||||
while (cert != nullptr && cert->raw.len > 0) {
|
while (cert != nullptr && cert->raw.len > 0) {
|
||||||
// Create a copy of the certificate for the caller
|
certs.push_back(static_cast<cert_t>(cert));
|
||||||
auto *copy = new mbedtls_x509_crt;
|
|
||||||
mbedtls_x509_crt_init(copy);
|
|
||||||
int ret = mbedtls_x509_crt_parse_der(copy, cert->raw.p, cert->raw.len);
|
|
||||||
if (ret == 0) {
|
|
||||||
certs.push_back(static_cast<cert_t>(copy));
|
|
||||||
} else {
|
|
||||||
mbedtls_x509_crt_free(copy);
|
|
||||||
delete copy;
|
|
||||||
}
|
|
||||||
cert = cert->next;
|
cert = cert->next;
|
||||||
}
|
}
|
||||||
return certs.size();
|
return certs.size();
|
||||||
|
|||||||
@@ -15919,6 +15919,103 @@ TEST(SSLClientRedirectTest, CertFile) {
|
|||||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A store set via set_ca_cert_store() must be carried over to the client that
|
||||||
|
// follows a cross-host redirect, rather than replaced by the system CAs.
|
||||||
|
TEST(SSLClientRedirectTest, SetCaCertStore) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
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_follow_location(true);
|
||||||
|
cli.set_connection_timeout(30);
|
||||||
|
|
||||||
|
auto res = cli.Get("/index");
|
||||||
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
|
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The session verifier must also run for the host a redirect leads to.
|
||||||
|
TEST(SSLClientRedirectTest, SessionVerifier) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 first host only.
|
||||||
|
std::atomic<int> sessions{0};
|
||||||
|
cli.set_session_verifier([&](tls::session_t) -> SSLVerifierResponse {
|
||||||
|
return sessions++ == 0 ? SSLVerifierResponse::CertificateAccepted
|
||||||
|
: SSLVerifierResponse::CertificateRejected;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto res = cli.Get("/index");
|
||||||
|
ASSERT_FALSE(res);
|
||||||
|
EXPECT_EQ(Error::SSLServerVerification, res.error());
|
||||||
|
EXPECT_EQ(2, sessions.load());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
|
|||||||
Reference in new issue
Block a user