mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
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:
2 files changed
+109
-54
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user