|
|
@@ -13,6 +13,7 @@
|
|
|
|
|
|
#include <atomic>
|
|
|
#include <chrono>
|
|
|
+#include <cstdio>
|
|
|
#include <fstream>
|
|
|
#include <future>
|
|
|
#include <limits>
|
|
|
@@ -8721,6 +8722,197 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtx) {
|
|
|
ASSERT_EQ(StatusCode::OK_200, res->status);
|
|
|
}
|
|
|
|
|
|
+TEST(SSLClientServerTest, ClientCAListSentToClient) {
|
|
|
+ SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE);
|
|
|
+ ASSERT_TRUE(svr.is_valid());
|
|
|
+
|
|
|
+ // Set up a handler to verify client certificate is present
|
|
|
+ bool client_cert_verified = false;
|
|
|
+ svr.Get("/test", [&](const Request & /*req*/, Response &res) {
|
|
|
+ // Verify that client certificate was provided
|
|
|
+ client_cert_verified = true;
|
|
|
+ res.set_content("success", "text/plain");
|
|
|
+ });
|
|
|
+
|
|
|
+ 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();
|
|
|
+
|
|
|
+ // Client with certificate
|
|
|
+ SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
|
|
|
+ cli.enable_server_certificate_verification(false);
|
|
|
+ cli.set_connection_timeout(30);
|
|
|
+
|
|
|
+ auto res = cli.Get("/test");
|
|
|
+ ASSERT_TRUE(res);
|
|
|
+ ASSERT_EQ(StatusCode::OK_200, res->status);
|
|
|
+ ASSERT_TRUE(client_cert_verified);
|
|
|
+ EXPECT_EQ("success", res->body);
|
|
|
+}
|
|
|
+
|
|
|
+TEST(SSLClientServerTest, ClientCAListSetInContext) {
|
|
|
+ // Test that when client CA cert file is provided,
|
|
|
+ // SSL_CTX_set_client_CA_list is called and the CA list is properly set
|
|
|
+
|
|
|
+ // Create a server with client authentication
|
|
|
+ SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE);
|
|
|
+ ASSERT_TRUE(svr.is_valid());
|
|
|
+
|
|
|
+ // We can't directly access the SSL_CTX from SSLServer to verify,
|
|
|
+ // but we can test that the server properly requests client certificates
|
|
|
+ // and accepts valid ones from the specified CA
|
|
|
+
|
|
|
+ bool handler_called = false;
|
|
|
+ svr.Get("/test", [&](const Request &req, Response &res) {
|
|
|
+ handler_called = true;
|
|
|
+
|
|
|
+ // Verify that a client certificate was provided
|
|
|
+ auto peer_cert = SSL_get_peer_certificate(req.ssl);
|
|
|
+ ASSERT_TRUE(peer_cert != nullptr);
|
|
|
+
|
|
|
+ // Get the issuer name
|
|
|
+ auto issuer_name = X509_get_issuer_name(peer_cert);
|
|
|
+ ASSERT_TRUE(issuer_name != nullptr);
|
|
|
+
|
|
|
+ char issuer_buf[256];
|
|
|
+ X509_NAME_oneline(issuer_name, issuer_buf, sizeof(issuer_buf));
|
|
|
+
|
|
|
+ // The client certificate should be issued by our test CA
|
|
|
+ std::string issuer_str(issuer_buf);
|
|
|
+ EXPECT_TRUE(issuer_str.find("Root CA Name") != std::string::npos);
|
|
|
+
|
|
|
+ X509_free(peer_cert);
|
|
|
+ res.set_content("authenticated", "text/plain");
|
|
|
+ });
|
|
|
+
|
|
|
+ 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();
|
|
|
+
|
|
|
+ // Connect with a client certificate issued by the CA
|
|
|
+ SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
|
|
|
+ cli.enable_server_certificate_verification(false);
|
|
|
+ cli.set_connection_timeout(30);
|
|
|
+
|
|
|
+ auto res = cli.Get("/test");
|
|
|
+ ASSERT_TRUE(res);
|
|
|
+ ASSERT_EQ(StatusCode::OK_200, res->status);
|
|
|
+ ASSERT_TRUE(handler_called);
|
|
|
+ EXPECT_EQ("authenticated", res->body);
|
|
|
+}
|
|
|
+
|
|
|
+TEST(SSLClientServerTest, ClientCAListLoadErrorRecorded) {
|
|
|
+ // Test 1: Valid CA file - no error should be recorded
|
|
|
+ {
|
|
|
+ SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE,
|
|
|
+ CLIENT_CA_CERT_FILE);
|
|
|
+ ASSERT_TRUE(svr.is_valid());
|
|
|
+
|
|
|
+ // With valid setup, last_ssl_error should be 0
|
|
|
+ EXPECT_EQ(0, svr.ssl_last_error());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test 2: Invalid CA file content
|
|
|
+ // When SSL_load_client_CA_file fails, last_ssl_error_ should be set
|
|
|
+ {
|
|
|
+ // Create a temporary file with completely invalid content
|
|
|
+ const char *temp_invalid_ca = "./temp_invalid_ca_for_test.txt";
|
|
|
+ {
|
|
|
+ std::ofstream ofs(temp_invalid_ca);
|
|
|
+ ofs << "This is not a certificate file at all\n";
|
|
|
+ ofs << "Just plain text content\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create server with invalid CA file
|
|
|
+ SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, temp_invalid_ca);
|
|
|
+
|
|
|
+ // Clean up temporary file
|
|
|
+ std::remove(temp_invalid_ca);
|
|
|
+
|
|
|
+ // When there's an SSL error (from either SSL_CTX_load_verify_locations
|
|
|
+ // or SSL_load_client_CA_file), last_ssl_error_ should be non-zero
|
|
|
+ // Note: SSL_CTX_load_verify_locations typically fails first,
|
|
|
+ // but our error handling code path is still exercised
|
|
|
+ if (!svr.is_valid()) { EXPECT_NE(0, svr.ssl_last_error()); }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+TEST(SSLClientServerTest, ClientCAListFromX509Store) {
|
|
|
+ // Test SSL server using X509_STORE constructor with client CA certificates
|
|
|
+ // This test verifies that Phase 2 implementation correctly extracts CA names
|
|
|
+ // from an X509_STORE and sets them in the SSL context
|
|
|
+
|
|
|
+ // Load the CA certificate into memory
|
|
|
+ auto bio = BIO_new_file(CLIENT_CA_CERT_FILE, "r");
|
|
|
+ ASSERT_NE(nullptr, bio);
|
|
|
+
|
|
|
+ auto ca_cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
|
|
+ BIO_free(bio);
|
|
|
+ ASSERT_NE(nullptr, ca_cert);
|
|
|
+
|
|
|
+ // Create an X509_STORE and add the CA certificate
|
|
|
+ auto store = X509_STORE_new();
|
|
|
+ ASSERT_NE(nullptr, store);
|
|
|
+ ASSERT_EQ(1, X509_STORE_add_cert(store, ca_cert));
|
|
|
+
|
|
|
+ // Load server certificate and private key
|
|
|
+ auto cert_bio = BIO_new_file(SERVER_CERT_FILE, "r");
|
|
|
+ ASSERT_NE(nullptr, cert_bio);
|
|
|
+ auto server_cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
|
|
|
+ BIO_free(cert_bio);
|
|
|
+ ASSERT_NE(nullptr, server_cert);
|
|
|
+
|
|
|
+ auto key_bio = BIO_new_file(SERVER_PRIVATE_KEY_FILE, "r");
|
|
|
+ ASSERT_NE(nullptr, key_bio);
|
|
|
+ auto server_key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr);
|
|
|
+ BIO_free(key_bio);
|
|
|
+ ASSERT_NE(nullptr, server_key);
|
|
|
+
|
|
|
+ // Create SSLServer with X509_STORE constructor
|
|
|
+ // Note: X509_STORE ownership is transferred to SSL_CTX
|
|
|
+ SSLServer svr(server_cert, server_key, store);
|
|
|
+ ASSERT_TRUE(svr.is_valid());
|
|
|
+
|
|
|
+ // No SSL error should be recorded for valid setup
|
|
|
+ EXPECT_EQ(0, svr.ssl_last_error());
|
|
|
+
|
|
|
+ // Set up server endpoints
|
|
|
+ svr.Get("/test-x509store", [&](const Request & /*req*/, Response &res) {
|
|
|
+ res.set_content("ok", "text/plain");
|
|
|
+ });
|
|
|
+
|
|
|
+ // Start server in a thread
|
|
|
+ auto server_thread = thread([&]() { svr.listen(HOST, PORT); });
|
|
|
+ svr.wait_until_ready();
|
|
|
+
|
|
|
+ // Connect with client certificate (using constructor with paths)
|
|
|
+ SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
|
|
|
+ cli.enable_server_certificate_verification(false);
|
|
|
+
|
|
|
+ auto res = cli.Get("/test-x509store");
|
|
|
+ ASSERT_TRUE(res);
|
|
|
+ EXPECT_EQ(200, res->status);
|
|
|
+ EXPECT_EQ("ok", res->body);
|
|
|
+
|
|
|
+ // Clean up
|
|
|
+ X509_free(server_cert);
|
|
|
+ EVP_PKEY_free(server_key);
|
|
|
+ X509_free(ca_cert);
|
|
|
+
|
|
|
+ svr.stop();
|
|
|
+ server_thread.join();
|
|
|
+}
|
|
|
+
|
|
|
// Disabled due to the out-of-memory problem on GitHub Actions Workflows
|
|
|
TEST(SSLClientServerTest, DISABLED_LargeDataTransfer) {
|
|
|
|