mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Turn off session resumption on wolfSSL servers verifying clients
wolfSSL shares its session cache between contexts, so a session resumed on a server that checks client certificates could carry a certificate checked against another server's CA, or against a client CA replaced since. Such a server no longer resumes sessions.
This commit is contained in:
1 parent
a7fce92ed0
commit
9a08b01ed7
3 files changed
+100
No files matched your search
@@ -72,6 +72,8 @@ cpp-httplib supports multiple TLS backends through an abstraction layer:
|
|||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> **wolfSSL limitation:** `get_ca_certs()` and `get_ca_names()` only reflect CA certificates loaded via `load_ca_cert_store()`. Certificates loaded through `set_ca_cert_path()` or system certificates (`load_system_certs`) are not enumerable.
|
> **wolfSSL limitation:** `get_ca_certs()` and `get_ca_names()` only reflect CA certificates loaded via `load_ca_cert_store()`. Certificates loaded through `set_ca_cert_path()` or system certificates (`load_system_certs`) are not enumerable.
|
||||||
|
>
|
||||||
|
> A wolfSSL server that verifies client certificates does not resume TLS sessions: wolfSSL shares its session cache between contexts, so a resumed session could carry a certificate checked against another server's client CA.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> **BoringSSL (best-effort):** BoringSSL builds under `CPPHTTPLIB_OPENSSL_SUPPORT` and is exercised by CI against current upstream. Because BoringSSL does not guarantee API stability, support is best-effort — breakage may occasionally land. Two known behavioral differences vs OpenSSL: (1) BoringSSL's public headers require C++14 or later, so consumers must compile accordingly; (2) hostname verification is SAN-only per RFC 6125 §6.4.4 (no CN fallback).
|
> **BoringSSL (best-effort):** BoringSSL builds under `CPPHTTPLIB_OPENSSL_SUPPORT` and is exercised by CI against current upstream. Because BoringSSL does not guarantee API stability, support is best-effort — breakage may occasionally land. Two known behavioral differences vs OpenSSL: (1) BoringSSL's public headers require C++14 or later, so consumers must compile accordingly; (2) hostname verification is SAN-only per RFC 6125 §6.4.4 (no CN fallback).
|
||||||
|
|||||||
@@ -22010,6 +22010,18 @@ inline int session_ex_data_index() {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A resumed session skips the client's certificate, so a server that checks
|
||||||
|
// one would let in a certificate checked against another server's CA, as
|
||||||
|
// wolfSSL shares its session cache between contexts, or against a CA since
|
||||||
|
// replaced by update_server_client_ca().
|
||||||
|
inline void disable_server_resumption(WolfSSLContext *wctx) {
|
||||||
|
if (!wctx->is_server) { return; }
|
||||||
|
wolfSSL_CTX_set_session_cache_mode(wctx->ctx, WOLFSSL_SESS_CACHE_OFF);
|
||||||
|
#ifdef HAVE_SESSION_TICKET
|
||||||
|
wolfSSL_CTX_NoTicketTLSv12(wctx->ctx);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// wolfSSL verify callback wrapper
|
// wolfSSL verify callback wrapper
|
||||||
inline int wolfssl_verify_callback(int preverify_ok,
|
inline int wolfssl_verify_callback(int preverify_ok,
|
||||||
WOLFSSL_X509_STORE_CTX *x509_ctx) {
|
WOLFSSL_X509_STORE_CTX *x509_ctx) {
|
||||||
@@ -22279,6 +22291,7 @@ inline void set_verify_client(ctx_t ctx, bool require) {
|
|||||||
auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
|
auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
|
||||||
wctx->verify_client = require;
|
wctx->verify_client = require;
|
||||||
if (require) {
|
if (require) {
|
||||||
|
impl::disable_server_resumption(wctx);
|
||||||
wolfSSL_CTX_set_verify(
|
wolfSSL_CTX_set_verify(
|
||||||
wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||||
wctx->verify_callback ? impl::wolfssl_verify_callback : nullptr);
|
wctx->verify_callback ? impl::wolfssl_verify_callback : nullptr);
|
||||||
@@ -23015,6 +23028,7 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
|
|||||||
wctx->verify_callback = std::move(callback);
|
wctx->verify_callback = std::move(callback);
|
||||||
|
|
||||||
if (wctx->verify_callback) {
|
if (wctx->verify_callback) {
|
||||||
|
impl::disable_server_resumption(wctx);
|
||||||
wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
|
wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
|
||||||
impl::wolfssl_verify_callback);
|
impl::wolfssl_verify_callback);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4978,6 +4978,90 @@ TEST(SSLClientServerTest, ClientResumesSessionUntilClientCAChanges) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
|
||||||
|
// wolfSSL shares its session cache between contexts, so a resumed session
|
||||||
|
// could carry a client certificate checked against another server's CA, or
|
||||||
|
// against a client CA since replaced.
|
||||||
|
TEST(SSLClientServerTest, ClientSessionIsNotResumedPastItsCA) {
|
||||||
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE);
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
// Trusts a CA that did not sign the client's certificate.
|
||||||
|
SSLServer other(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, SERVER_CERT2_FILE);
|
||||||
|
ASSERT_TRUE(other.is_valid());
|
||||||
|
for (auto s : {&svr, &other}) {
|
||||||
|
s->Get("/test", [](const Request &, Response &res) {
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||||
|
thread t2 = thread([&]() { ASSERT_TRUE(other.listen(HOST, PORT + 1)); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
other.stop();
|
||||||
|
svr.stop();
|
||||||
|
t2.join();
|
||||||
|
t.join();
|
||||||
|
});
|
||||||
|
svr.wait_until_ready();
|
||||||
|
other.wait_until_ready();
|
||||||
|
|
||||||
|
auto ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
|
||||||
|
auto ctx_se = detail::scope_exit([&] { wolfSSL_CTX_free(ctx); });
|
||||||
|
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, nullptr);
|
||||||
|
ASSERT_EQ(WOLFSSL_SUCCESS, wolfSSL_CTX_use_certificate_file(
|
||||||
|
ctx, CLIENT_CERT_FILE, WOLFSSL_FILETYPE_PEM));
|
||||||
|
ASSERT_EQ(WOLFSSL_SUCCESS,
|
||||||
|
wolfSSL_CTX_use_PrivateKey_file(ctx, CLIENT_PRIVATE_KEY_FILE,
|
||||||
|
WOLFSSL_FILETYPE_PEM));
|
||||||
|
|
||||||
|
WOLFSSL_SESSION *session = nullptr;
|
||||||
|
auto session_se = detail::scope_exit([&] {
|
||||||
|
if (session) { wolfSSL_SESSION_free(session); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// A request over a new connection, resuming `session` when there is one and
|
||||||
|
// keeping the connection's own in its place. Tells whether it was answered.
|
||||||
|
auto get = [&](int port) {
|
||||||
|
auto error = Error::Success;
|
||||||
|
auto sock = detail::create_client_socket(
|
||||||
|
HOST, "", port, AF_UNSPEC, false, false, nullptr,
|
||||||
|
/*connection_timeout_sec=*/5, 0,
|
||||||
|
/*read_timeout_sec=*/5, 0,
|
||||||
|
/*write_timeout_sec=*/5, 0, std::string(), error);
|
||||||
|
if (sock == INVALID_SOCKET) { return false; }
|
||||||
|
auto sock_se = detail::scope_exit([&] { detail::close_socket(sock); });
|
||||||
|
|
||||||
|
auto ssl = wolfSSL_new(ctx);
|
||||||
|
auto ssl_se = detail::scope_exit([&] { wolfSSL_free(ssl); });
|
||||||
|
wolfSSL_set_fd(ssl, static_cast<int>(sock));
|
||||||
|
if (session) { wolfSSL_set_session(ssl, session); }
|
||||||
|
if (wolfSSL_connect(ssl) != WOLFSSL_SUCCESS) { return false; }
|
||||||
|
|
||||||
|
std::string req = "GET /test HTTP/1.1\r\nHost: x\r\n"
|
||||||
|
"Connection: close\r\n\r\n";
|
||||||
|
wolfSSL_write(ssl, req.data(), static_cast<int>(req.size()));
|
||||||
|
std::string res;
|
||||||
|
char buf[256];
|
||||||
|
int n;
|
||||||
|
while ((n = wolfSSL_read(ssl, buf, sizeof(buf))) > 0) {
|
||||||
|
res.append(buf, static_cast<size_t>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
wolfSSL_shutdown(ssl);
|
||||||
|
if (session) { wolfSSL_SESSION_free(session); }
|
||||||
|
session = wolfSSL_get1_session(ssl);
|
||||||
|
return res.find("200 OK") != std::string::npos;
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_TRUE(get(PORT));
|
||||||
|
EXPECT_FALSE(get(PORT + 1));
|
||||||
|
|
||||||
|
ASSERT_TRUE(get(PORT));
|
||||||
|
ASSERT_TRUE(replace_client_ca(svr));
|
||||||
|
EXPECT_FALSE(get(PORT));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(SSLClientServerTest, UpdateCertsPemWaitsForHandshakeInProgress) {
|
TEST(SSLClientServerTest, UpdateCertsPemWaitsForHandshakeInProgress) {
|
||||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
ASSERT_TRUE(svr.is_valid());
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
|||||||
Reference in new issue
Block a user