From 2ece5f116bf1b4f8a87299eba7ad19c2f015725f Mon Sep 17 00:00:00 2001 From: Daniel Ottiger Date: Sat, 18 Apr 2020 22:26:06 +0200 Subject: [PATCH] Pass certs and keys from memory (#432) * SSLServer: add constructor to pass ssl-certificates and key from memory * SSLClient: add constructor to pass ssl-certificates and key from memory * add TestCase for passing certificates from memory to SSLClient/SSLServer --- httplib.h | 67 +++++++++++++++++++++++++++++++++++++++++++++-- test/test.cc | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/httplib.h b/httplib.h index f722b40..1257d38 100644 --- a/httplib.h +++ b/httplib.h @@ -848,6 +848,9 @@ public: const char *client_ca_cert_file_path = nullptr, const char *client_ca_cert_dir_path = nullptr); + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + ~SSLServer() override; bool is_valid() const override; @@ -865,6 +868,9 @@ public: const std::string &client_cert_path = std::string(), const std::string &client_key_path = std::string()); + SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + ~SSLClient() override; bool is_valid() const override; @@ -872,6 +878,8 @@ public: void set_ca_cert_path(const char *ca_ceert_file_path, const char *ca_cert_dir_path = nullptr); + void set_ca_cert_store(X509_STORE *ca_cert_store); + void enable_server_certificate_verification(bool enabled); long get_openssl_verify_result() const; @@ -897,6 +905,7 @@ private: std::string ca_cert_file_path_; std::string ca_cert_dir_path_; + X509_STORE *ca_cert_store_ = nullptr; bool server_certificate_verification_ = false; long verify_result_ = 0; }; @@ -4654,6 +4663,33 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, } } +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(SSLv23_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, + SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, + nullptr); + } + } +} + inline SSLServer::~SSLServer() { if (ctx_) { SSL_CTX_free(ctx_); } } @@ -4693,6 +4729,24 @@ inline SSLClient::SSLClient(const std::string &host, int port, } } +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : Client(host, port) { + ctx_ = SSL_CTX_new(SSLv23_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + inline SSLClient::~SSLClient() { if (ctx_) { SSL_CTX_free(ctx_); } } @@ -4705,6 +4759,10 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } } +void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { ca_cert_store_ = ca_cert_store; } +} + inline void SSLClient::enable_server_certificate_verification(bool enabled) { server_certificate_verification_ = enabled; } @@ -4728,14 +4786,19 @@ inline bool SSLClient::process_and_close_socket( true, sock, request_count, read_timeout_sec_, read_timeout_usec_, ctx_, ctx_mutex_, [&](SSL *ssl) { - if (ca_cert_file_path_.empty()) { + if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) { SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr); - } else { + } else if (!ca_cert_file_path_.empty()) { if (!SSL_CTX_load_verify_locations( ctx_, ca_cert_file_path_.c_str(), nullptr)) { return false; } SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); + } else if (ca_cert_store_ != nullptr) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) { + SSL_CTX_set_cert_store(ctx_, ca_cert_store_); + } + SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); } if (SSL_connect(ssl) != 1) { return false; } diff --git a/test/test.cc b/test/test.cc index 81178c4..c0959f4 100644 --- a/test/test.cc +++ b/test/test.cc @@ -2530,6 +2530,80 @@ TEST(SSLClientServerTest, ClientCertPresent) { t.join(); } +TEST(SSLClientServerTest, MemoryClientCertPresent) { + X509 *server_cert; + EVP_PKEY *server_private_key; + X509_STORE *client_ca_cert_store; + X509 *client_cert; + EVP_PKEY *client_private_key; + + FILE *f = fopen(SERVER_CERT_FILE, "r+"); + server_cert = PEM_read_X509(f, nullptr, nullptr, nullptr); + fclose(f); + + f = fopen(SERVER_PRIVATE_KEY_FILE, "r+"); + server_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr); + fclose(f); + + f = fopen(CLIENT_CA_CERT_FILE, "r+"); + client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr); + client_ca_cert_store = X509_STORE_new(); + X509_STORE_add_cert(client_ca_cert_store, client_cert); + X509_free(client_cert); + fclose(f); + + f = fopen(CLIENT_CERT_FILE, "r+"); + client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr); + fclose(f); + + f = fopen(CLIENT_PRIVATE_KEY_FILE, "r+"); + client_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr); + fclose(f); + + SSLServer svr(server_cert, server_private_key, client_ca_cert_store); + ASSERT_TRUE(svr.is_valid()); + + svr.Get("/test", [&](const Request &req, Response &res) { + res.set_content("test", "text/plain"); + svr.stop(); + ASSERT_TRUE(true); + + auto peer_cert = SSL_get_peer_certificate(req.ssl); + ASSERT_TRUE(peer_cert != nullptr); + + auto subject_name = X509_get_subject_name(peer_cert); + ASSERT_TRUE(subject_name != nullptr); + + std::string common_name; + { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + common_name.assign(name, static_cast(name_len)); + } + + EXPECT_EQ("Common Name", common_name); + + X509_free(peer_cert); + }); + + thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); }); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + httplib::SSLClient cli(HOST, PORT, client_cert, client_private_key); + auto res = cli.Get("/test"); + cli.set_timeout_sec(30); + ASSERT_TRUE(res != nullptr); + ASSERT_EQ(200, res->status); + + X509_free(server_cert); + EVP_PKEY_free(server_private_key); + X509_free(client_cert); + EVP_PKEY_free(client_private_key); + + t.join(); +} + TEST(SSLClientServerTest, ClientCertMissing) { SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE, CLIENT_CA_CERT_DIR);