From 71b5f0a1c89871b041da14a72d4a0ea5a2d64816 Mon Sep 17 00:00:00 2001 From: yhirose Date: Wed, 21 Oct 2026 16:19:15 -0400 Subject: [PATCH] Read a NUL-terminated client CA in SSLServer::PemMemory SSLServer::PemMemory takes a length of 0 to mean a NUL-terminated string for cert_pem and key_pem, but skipped client_ca_pem when its length was 0, so a server given its client CA that way started without requiring client certificates. Read the client CA under the same rule as the other fields. An empty client CA now fails to load instead of turning the check off, and the README documents the length-0 rule. --- README.md | 2 ++ httplib.h | 6 ++++-- test/test.cc | 18 ++++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 50db44d2..4fe8b8d3 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,8 @@ client_pem.key_pem_len = client_key.size(); httplib::SSLClient cli("api.example.com", 443, client_pem); ``` +A length of 0 means the field is a NUL-terminated string. + `httplib::ws::WebSocketClient` has the same `PemMemory` constructor for `wss://` connections. See [README-websocket.md](README-websocket.md) for details. ### Peer Certificate Inspection diff --git a/httplib.h b/httplib.h index aecb8e95..88b306fb 100644 --- a/httplib.h +++ b/httplib.h @@ -18824,8 +18824,10 @@ inline SSLServer::SSLServer(const PemMemory &pem) { last_ssl_error_ = static_cast(get_error()); free_context(ctx_); ctx_ = nullptr; - } else if (pem.client_ca_pem && pem.client_ca_pem_len > 0) { - if (!load_ca_pem(ctx_, pem.client_ca_pem, pem.client_ca_pem_len)) { + } else if (pem.client_ca_pem) { + auto ca_len = pem.client_ca_pem_len ? pem.client_ca_pem_len + : strlen(pem.client_ca_pem); + if (!load_ca_pem(ctx_, pem.client_ca_pem, ca_len)) { last_ssl_error_ = static_cast(get_error()); free_context(ctx_); ctx_ = nullptr; diff --git a/test/test.cc b/test/test.cc index 99f67136..aa73f965 100644 --- a/test/test.cc +++ b/test/test.cc @@ -16309,7 +16309,8 @@ TEST(SSLClientServerTest, PemMemoryClientEncryptedCertPresent) { CLIENT_ENCRYPTED_PRIVATE_KEY_PASS); } -// A server built from PEM memory with a client CA requires client certificates +// A server built from PEM memory with a client CA requires client certificates, +// also when the CA is a NUL-terminated string passed with a length of 0 TEST(SSLClientServerTest, PemMemoryClientCertMissing) { std::string server_cert_pem, server_key_pem, client_ca_pem; read_file(SERVER_CERT_FILE, server_cert_pem); @@ -16325,9 +16326,18 @@ TEST(SSLClientServerTest, PemMemoryClientCertMissing) { client_ca_pem.size(), nullptr // no password for server key }; - SSLServer svr(server_pem); - ASSERT_TRUE(svr.is_valid()); - RejectsClientWithoutCert(svr); + for (auto client_ca_pem_len : {client_ca_pem.size(), size_t{0}}) { + SCOPED_TRACE(client_ca_pem_len); + server_pem.client_ca_pem_len = client_ca_pem_len; + SSLServer svr(server_pem); + ASSERT_TRUE(svr.is_valid()); + RejectsClientWithoutCert(svr); + } + + // An empty CA fails to load instead of turning the check off + server_pem.client_ca_pem = ""; + server_pem.client_ca_pem_len = 0; + EXPECT_FALSE(SSLServer(server_pem).is_valid()); } // The PemMemory lengths are honoured, so the PEM needs no NUL terminator