mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-28 12:40:54 +00:00
Build the client-cert-still-required tests on the existing helpers
ClientCertPresent() and ClientCertMissing() now take a setup step for the server, bind to any port instead of the legacy PORT, and the two new tests are just calls to them. ClientCertMissing also no longer checks the server certificate: with it checked, the request failed because cert.pem is not trusted, whether or not the server asked for a client certificate, so the test never exercised the client certificate requirement. A rejected client is reported as SSLConnection, or as Read on TLS 1.3, where the client only notices on its first read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGAYpQgXt95LdDQcJYgjB8
This commit is contained in:
1 file changed
+45
-64
+45
-64
@@ -14522,15 +14522,19 @@ TEST(SSLClientTest, SetInterfaceWithINET6) {
|
|||||||
|
|
||||||
// ClientCertPresent uses get_peer_cert() - works with all TLS backends
|
// ClientCertPresent uses get_peer_cert() - works with all TLS backends
|
||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
|
using ClientCertServerSetup = std::function<void(SSLServer &)>;
|
||||||
|
|
||||||
void ClientCertPresent(
|
void ClientCertPresent(
|
||||||
const std::string &client_cert_file,
|
const std::string &client_cert_file,
|
||||||
const std::string &client_private_key_file,
|
const std::string &client_private_key_file,
|
||||||
const std::string &client_encrypted_private_key_pass = std::string()) {
|
const std::string &client_encrypted_private_key_pass = std::string(),
|
||||||
|
const ClientCertServerSetup &setup = nullptr) {
|
||||||
using namespace httplib::tls;
|
using namespace httplib::tls;
|
||||||
|
|
||||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||||
CLIENT_CA_CERT_DIR);
|
CLIENT_CA_CERT_DIR);
|
||||||
ASSERT_TRUE(svr.is_valid());
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
if (setup) { setup(svr); }
|
||||||
|
|
||||||
svr.Get("/test", [&](const Request &req, Response &res) {
|
svr.Get("/test", [&](const Request &req, Response &res) {
|
||||||
res.set_content("test", "text/plain");
|
res.set_content("test", "text/plain");
|
||||||
@@ -14542,7 +14546,9 @@ void ClientCertPresent(
|
|||||||
EXPECT_EQ("Common Name", common_name);
|
EXPECT_EQ("Common Name", common_name);
|
||||||
});
|
});
|
||||||
|
|
||||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
auto port = svr.bind_to_any_port(HOST);
|
||||||
|
ASSERT_LT(0, port);
|
||||||
|
thread t = thread([&]() { svr.listen_after_bind(); });
|
||||||
auto se = detail::scope_exit([&] {
|
auto se = detail::scope_exit([&] {
|
||||||
svr.stop();
|
svr.stop();
|
||||||
t.join();
|
t.join();
|
||||||
@@ -14551,7 +14557,7 @@ void ClientCertPresent(
|
|||||||
|
|
||||||
svr.wait_until_ready();
|
svr.wait_until_ready();
|
||||||
|
|
||||||
SSLClient cli(HOST, PORT, client_cert_file, client_private_key_file,
|
SSLClient cli(HOST, port, client_cert_file, client_private_key_file,
|
||||||
client_encrypted_private_key_pass);
|
client_encrypted_private_key_pass);
|
||||||
cli.enable_server_certificate_verification(false);
|
cli.enable_server_certificate_verification(false);
|
||||||
cli.set_connection_timeout(30);
|
cli.set_connection_timeout(30);
|
||||||
@@ -14571,23 +14577,13 @@ TEST(SSLClientServerTest, ClientEncryptedCertPresent) {
|
|||||||
CLIENT_ENCRYPTED_PRIVATE_KEY_PASS);
|
CLIENT_ENCRYPTED_PRIVATE_KEY_PASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A server that requires client certificates must keep requiring them after a
|
void ClientCertMissing(const ClientCertServerSetup &setup = nullptr) {
|
||||||
// verify callback is set on its context, or set and then cleared again.
|
|
||||||
void ClientCertStillRequired(bool clear_callback) {
|
|
||||||
using namespace httplib::tls;
|
|
||||||
|
|
||||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||||
CLIENT_CA_CERT_DIR);
|
CLIENT_CA_CERT_DIR);
|
||||||
ASSERT_TRUE(svr.is_valid());
|
ASSERT_TRUE(svr.is_valid());
|
||||||
svr.Get("/test", [](const Request &, Response &res) {
|
if (setup) { setup(svr); }
|
||||||
res.set_content("test", "text/plain");
|
|
||||||
});
|
|
||||||
|
|
||||||
ASSERT_TRUE(set_verify_callback(svr.tls_context(),
|
svr.Get("/test", [&](const Request &, Response &) { ASSERT_TRUE(false); });
|
||||||
[](const VerifyContext &) { return true; }));
|
|
||||||
if (clear_callback) {
|
|
||||||
ASSERT_TRUE(set_verify_callback(svr.tls_context(), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto port = svr.bind_to_any_port(HOST);
|
auto port = svr.bind_to_any_port(HOST);
|
||||||
ASSERT_LT(0, port);
|
ASSERT_LT(0, port);
|
||||||
@@ -14597,25 +14593,45 @@ void ClientCertStillRequired(bool clear_callback) {
|
|||||||
t.join();
|
t.join();
|
||||||
ASSERT_FALSE(svr.is_running());
|
ASSERT_FALSE(svr.is_running());
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.wait_until_ready();
|
svr.wait_until_ready();
|
||||||
|
|
||||||
{
|
SSLClient cli(HOST, port);
|
||||||
SSLClient cli(HOST, port);
|
cli.set_connection_timeout(30);
|
||||||
cli.enable_server_certificate_verification(false);
|
// Skip the server certificate check, so that the request fails because the
|
||||||
cli.set_connection_timeout(30);
|
// client certificate is missing rather than because cert.pem is not trusted
|
||||||
EXPECT_FALSE(cli.Get("/test"));
|
cli.enable_server_certificate_verification(false);
|
||||||
}
|
|
||||||
|
|
||||||
{
|
auto res = cli.Get("/test");
|
||||||
SSLClient cli(HOST, port, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
|
ASSERT_TRUE(!res);
|
||||||
cli.enable_server_certificate_verification(false);
|
// The server rejects the client during the handshake, or on TLS 1.3 right
|
||||||
cli.set_connection_timeout(30);
|
// after it, which the client only notices on its first read
|
||||||
auto res = cli.Get("/test");
|
EXPECT_TRUE(res.error() == Error::SSLConnection || res.error() == Error::Read)
|
||||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
<< to_string(res.error());
|
||||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
|
||||||
|
// Verify backend error is captured
|
||||||
|
if (res.error() == Error::SSLConnection) {
|
||||||
|
EXPECT_NE(0UL, res.ssl_backend_error());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(SSLClientServerTest, ClientCertMissing) { ClientCertMissing(); }
|
||||||
|
|
||||||
|
// A server that requires client certificates must keep requiring them after a
|
||||||
|
// verify callback is set on its context, or set and then cleared again.
|
||||||
|
void ClientCertStillRequired(bool clear_callback) {
|
||||||
|
auto setup = [clear_callback](SSLServer &svr) {
|
||||||
|
ASSERT_TRUE(tls::set_verify_callback(
|
||||||
|
svr.tls_context(), [](const tls::VerifyContext &) { return true; }));
|
||||||
|
if (clear_callback) {
|
||||||
|
ASSERT_TRUE(tls::set_verify_callback(svr.tls_context(), nullptr));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ClientCertMissing(setup);
|
||||||
|
ClientCertPresent(CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE, std::string(),
|
||||||
|
setup);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(SSLClientServerTest, ClientCertStillRequiredAfterVerifyCallbackSet) {
|
TEST(SSLClientServerTest, ClientCertStillRequiredAfterVerifyCallbackSet) {
|
||||||
ClientCertStillRequired(false);
|
ClientCertStillRequired(false);
|
||||||
}
|
}
|
||||||
@@ -14692,41 +14708,6 @@ TEST(SSLClientServerTest, PemMemoryClientEncryptedCertPresent) {
|
|||||||
CLIENT_ENCRYPTED_PRIVATE_KEY_PASS);
|
CLIENT_ENCRYPTED_PRIVATE_KEY_PASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SSLClientServerTest, ClientCertMissing) {
|
|
||||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
|
||||||
CLIENT_CA_CERT_DIR);
|
|
||||||
ASSERT_TRUE(svr.is_valid());
|
|
||||||
|
|
||||||
svr.Get("/test", [&](const Request &, Response &) { ASSERT_TRUE(false); });
|
|
||||||
|
|
||||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
|
||||||
auto se = detail::scope_exit([&] {
|
|
||||||
svr.stop();
|
|
||||||
t.join();
|
|
||||||
ASSERT_FALSE(svr.is_running());
|
|
||||||
});
|
|
||||||
|
|
||||||
svr.wait_until_ready();
|
|
||||||
|
|
||||||
SSLClient cli(HOST, PORT);
|
|
||||||
cli.set_connection_timeout(30);
|
|
||||||
// cert.pem does not match HOST. Skip the hostname check, which runs before
|
|
||||||
// the chain check on Windows, so the result does not depend on the order.
|
|
||||||
cli.enable_server_hostname_verification(false);
|
|
||||||
|
|
||||||
auto res = cli.Get("/test");
|
|
||||||
ASSERT_TRUE(!res);
|
|
||||||
// When client cert is missing and server requires it, connection fails
|
|
||||||
// Error type depends on backend implementation
|
|
||||||
EXPECT_TRUE(res.error() == Error::SSLServerVerification ||
|
|
||||||
res.error() == Error::SSLConnection);
|
|
||||||
|
|
||||||
// Verify backend error is captured
|
|
||||||
// Note: This test may have different error codes depending on the exact
|
|
||||||
// verification failure
|
|
||||||
EXPECT_NE(0UL, res.ssl_backend_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SSLClientServerTest, TrustDirOptional) {
|
TEST(SSLClientServerTest, TrustDirOptional) {
|
||||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE);
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE);
|
||||||
ASSERT_TRUE(svr.is_valid());
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
|||||||
Reference in new issue
Block a user