Derive the TLS verify mode from one place per backend

set_verify_client() and set_verify_callback() each set the verify mode on
their own, and set_verify_callback() ignored whether the peer's certificate
was required. A server that required client certificates therefore stopped
requiring them as soon as a verify callback was set on its context (OpenSSL,
Mbed TLS, wolfSSL) or set and then cleared again (OpenSSL, Mbed TLS).

Compute the mode from "peer certificate required" and "callback set" in one
helper per backend and call it from set_verify_client(),
set_verify_callback() and the context constructors. The context now records
that a client verifies the server by default, so clearing a callback on a
client context restores that default.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAYpQgXt95LdDQcJYgjB8
This commit is contained in:
Claude committed 2026-10-16 21:12:47 +00:00
1 parent a445ed23bf
commit 300c084d6f
2 files changed
+109 -54

No files matched your search

+56 -54
View File
@@ -4013,6 +4013,8 @@ struct MbedTlsContext {
mbedtls_x509_crt own_cert;
mbedtls_pk_context own_key;
bool is_server = false;
// Whether the peer's certificate is required; set by
// tls::set_verify_client(). A client verifies the server by default.
bool verify_client = false;
// Set by tls::set_verify_callback().
VerifyCallback verify_callback;
@@ -4038,6 +4040,8 @@ namespace impl {
struct WolfSSLContext {
WOLFSSL_CTX *ctx = nullptr;
bool is_server = false;
// Whether the peer's certificate is required; set by
// tls::set_verify_client(). A client verifies the server by default.
bool verify_client = false;
// Set by tls::set_verify_callback().
VerifyCallback verify_callback;
@@ -19307,6 +19311,17 @@ inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
return (*callback)(verify_ctx) ? 1 : 0;
}
// The verify mode follows from whether the peer's certificate is required and
// whether a verify callback is set, however the two were last changed.
inline void apply_verify_mode(SSL_CTX *ssl_ctx, bool require) {
auto callback = get_openssl_verify_callback(ssl_ctx);
auto has_callback = callback && *callback;
auto mode = require ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
: (has_callback ? SSL_VERIFY_PEER : SSL_VERIFY_NONE);
SSL_CTX_set_verify(ssl_ctx, mode,
has_callback ? openssl_verify_callback : nullptr);
}
// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not
// thread-safe; X509_STORE_get1_objects (OpenSSL 3.3+) returns a snapshot
// that must be released with release_store_objects
@@ -19537,11 +19552,7 @@ inline ctx_t create_server_context() {
inline void set_verify_client(ctx_t ctx, bool require) {
if (!ctx) return;
SSL_CTX_set_verify(static_cast<SSL_CTX *>(ctx),
require
? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
: SSL_VERIFY_NONE,
nullptr);
impl::apply_verify_mode(static_cast<SSL_CTX *>(ctx), require);
}
inline session_t create_session(ctx_t ctx, socket_t sock) {
@@ -20192,11 +20203,10 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
}
delete old;
if (stored) {
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
} else {
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
}
// Keep requiring the peer's certificate if set_verify_client() asked for it
auto require =
(SSL_CTX_get_verify_mode(ssl_ctx) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) != 0;
impl::apply_verify_mode(ssl_ctx, require);
return true;
}
@@ -20464,6 +20474,20 @@ inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
}
// The authentication mode follows from whether the peer's certificate is
// required and whether a verify callback is set, however the two were last
// changed. OPTIONAL makes sure the callback runs even if verification is not
// required (matching OpenSSL, where SSL_VERIFY_PEER is set).
inline void apply_authmode(MbedTlsContext &mctx) {
auto mode = MBEDTLS_SSL_VERIFY_NONE;
if (mctx.verify_client) {
mode = MBEDTLS_SSL_VERIFY_REQUIRED;
} else if (mctx.verify_callback) {
mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
}
mbedtls_ssl_conf_authmode(&mctx.conf, mode);
}
} // namespace impl
inline ctx_t create_client_context() {
@@ -20508,7 +20532,8 @@ inline ctx_t create_client_context() {
#endif
// Default: verify peer certificate
mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
ctx->verify_client = true;
impl::apply_authmode(*ctx);
// Set minimum TLS version to 1.2
#ifdef CPPHTTPLIB_MBEDTLS_V3
@@ -20563,7 +20588,7 @@ inline ctx_t create_server_context() {
#endif
// Default: don't verify client
mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
impl::apply_authmode(*ctx);
// Set minimum TLS version to 1.2
#ifdef CPPHTTPLIB_MBEDTLS_V3
@@ -20812,15 +20837,7 @@ inline void set_verify_client(ctx_t ctx, bool require) {
if (!ctx) { return; }
auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
mctx->verify_client = require;
if (require) {
mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
} else {
// If a verify callback is set, use OPTIONAL mode to ensure the callback
// is called (matching OpenSSL behavior). Otherwise use NONE.
mbedtls_ssl_conf_authmode(&mctx->conf, mctx->verify_callback
? MBEDTLS_SSL_VERIFY_OPTIONAL
: MBEDTLS_SSL_VERIFY_NONE);
}
impl::apply_authmode(*mctx);
}
inline session_t create_session(ctx_t ctx, socket_t sock) {
@@ -21558,14 +21575,9 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
if (!ctx) { return false; }
auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
// The callback itself is wired per session in create_session()
mbed_ctx->verify_callback = std::move(callback);
if (mbed_ctx->verify_callback) {
// Set OPTIONAL mode to ensure callback is called even when verification
// is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set).
// The callback itself is wired per session in create_session().
mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
}
impl::apply_authmode(*mbed_ctx);
return true;
}
@@ -21722,6 +21734,17 @@ inline int wolfssl_verify_callback(int preverify_ok,
return accepted ? 1 : 0;
}
// The verify mode follows from whether the peer's certificate is required and
// whether a verify callback is set, however the two were last changed.
inline void apply_verify_mode(WolfSSLContext &wctx) {
auto has_callback = static_cast<bool>(wctx.verify_callback);
auto mode = wctx.verify_client
? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
: (has_callback ? SSL_VERIFY_PEER : SSL_VERIFY_NONE);
wolfSSL_CTX_set_verify(wctx.ctx, mode,
has_callback ? wolfssl_verify_callback : nullptr);
}
inline void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) {
wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char *>(password));
wolfSSL_CTX_set_default_passwd_cb(
@@ -21756,7 +21779,8 @@ inline ctx_t create_client_context() {
}
// Default: verify peer certificate
wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr);
ctx->verify_client = true;
impl::apply_verify_mode(*ctx);
return static_cast<ctx_t>(ctx);
}
@@ -21780,7 +21804,7 @@ inline ctx_t create_server_context() {
}
// Default: don't verify client
wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr);
impl::apply_verify_mode(*ctx);
// Enable SNI on server
wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME,
@@ -21953,18 +21977,7 @@ inline void set_verify_client(ctx_t ctx, bool require) {
if (!ctx) { return; }
auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
wctx->verify_client = require;
if (require) {
wolfSSL_CTX_set_verify(
wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
wctx->verify_callback ? impl::wolfssl_verify_callback : nullptr);
} else {
if (wctx->verify_callback) {
wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
impl::wolfssl_verify_callback);
} else {
wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr);
}
}
impl::apply_verify_mode(*wctx);
}
inline session_t create_session(ctx_t ctx, socket_t sock) {
@@ -22716,18 +22729,7 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
wctx->verify_callback = std::move(callback);
if (wctx->verify_callback) {
wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
impl::wolfssl_verify_callback);
} else {
wolfSSL_CTX_set_verify(
wctx->ctx,
wctx->verify_client
? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
: SSL_VERIFY_NONE,
nullptr);
}
impl::apply_verify_mode(*wctx);
return true;
}
+53
View File
@@ -14571,6 +14571,59 @@ TEST(SSLClientServerTest, ClientEncryptedCertPresent) {
CLIENT_ENCRYPTED_PRIVATE_KEY_PASS);
}
// 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) {
using namespace httplib::tls;
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 &res) {
res.set_content("test", "text/plain");
});
ASSERT_TRUE(set_verify_callback(svr.tls_context(),
[](const VerifyContext &) { return true; }));
if (clear_callback) {
ASSERT_TRUE(set_verify_callback(svr.tls_context(), nullptr));
}
auto port = svr.bind_to_any_port(HOST);
ASSERT_LT(0, port);
thread t = thread([&]() { svr.listen_after_bind(); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
{
SSLClient cli(HOST, port);
cli.enable_server_certificate_verification(false);
cli.set_connection_timeout(30);
EXPECT_FALSE(cli.Get("/test"));
}
{
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) << "Error: " << to_string(res.error());
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}
TEST(SSLClientServerTest, ClientCertStillRequiredAfterVerifyCallbackSet) {
ClientCertStillRequired(false);
}
TEST(SSLClientServerTest, ClientCertStillRequiredAfterVerifyCallbackCleared) {
ClientCertStillRequired(true);
}
// PEM memory-based constructor tests (works with all TLS backends)
void PemMemoryClientCertPresent(
const std::string &client_cert_file,