mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Store the TLS verify callback per context instead of thread_local
tls::set_verify_callback() kept the callback in a thread_local slot, so two SSLClients configured on one thread overwrote each other's verifier, and a client used from another thread ran none. Keep it with the context instead (OpenSSL SSL_CTX ex_data, MbedTlsContext/WolfSSLContext member) and have each backend trampoline look it up from the session's context. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wxevjmRqzpy8qDAD3vdwr
This commit is contained in:
2 files changed
+151
-39
No files matched your search
@@ -4013,6 +4013,9 @@ struct MbedTlsContext {
|
|||||||
bool is_server = false;
|
bool is_server = false;
|
||||||
bool verify_client = false;
|
bool verify_client = false;
|
||||||
bool has_verify_callback = false;
|
bool has_verify_callback = false;
|
||||||
|
// Installed by tls::set_verify_callback(); run by the sessions created from
|
||||||
|
// this context.
|
||||||
|
VerifyCallback verify_callback;
|
||||||
|
|
||||||
MbedTlsContext();
|
MbedTlsContext();
|
||||||
~MbedTlsContext();
|
~MbedTlsContext();
|
||||||
@@ -4037,6 +4040,10 @@ struct WolfSSLContext {
|
|||||||
bool is_server = false;
|
bool is_server = false;
|
||||||
bool verify_client = false;
|
bool verify_client = false;
|
||||||
bool has_verify_callback = false;
|
bool has_verify_callback = false;
|
||||||
|
// Installed by tls::set_verify_callback(); run by the sessions created from
|
||||||
|
// this context (each one points its verify-callback ctx back at this
|
||||||
|
// struct in create_session()).
|
||||||
|
VerifyCallback verify_callback;
|
||||||
std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs
|
std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs
|
||||||
|
|
||||||
WolfSSLContext();
|
WolfSSLContext();
|
||||||
@@ -18933,16 +18940,6 @@ inline PeerCert get_peer_cert_from_session(const_session_t session) {
|
|||||||
|
|
||||||
namespace impl {
|
namespace impl {
|
||||||
|
|
||||||
inline VerifyCallback &get_verify_callback() {
|
|
||||||
static thread_local VerifyCallback callback;
|
|
||||||
return callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline VerifyCallback &get_mbedtls_verify_callback() {
|
|
||||||
static thread_local VerifyCallback callback;
|
|
||||||
return callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a string is an IPv4 address
|
// Check if a string is an IPv4 address
|
||||||
inline bool is_ipv4_address(const std::string &str) {
|
inline bool is_ipv4_address(const std::string &str) {
|
||||||
int dots = 0;
|
int dots = 0;
|
||||||
@@ -19268,16 +19265,29 @@ inline STACK_OF(X509_NAME) *
|
|||||||
return ca_list;
|
return ca_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSL_CTX ex_data slot holding the heap-allocated VerifyCallback installed by
|
||||||
|
// set_verify_callback(); freed by set_verify_callback() and free_context().
|
||||||
|
inline int openssl_verify_callback_index() {
|
||||||
|
static const int index =
|
||||||
|
SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VerifyCallback *get_openssl_verify_callback(const SSL_CTX *ssl_ctx) {
|
||||||
|
return static_cast<VerifyCallback *>(
|
||||||
|
SSL_CTX_get_ex_data(ssl_ctx, openssl_verify_callback_index()));
|
||||||
|
}
|
||||||
|
|
||||||
// OpenSSL verify callback wrapper
|
// OpenSSL verify callback wrapper
|
||||||
inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
|
inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
|
||||||
auto &callback = get_verify_callback();
|
|
||||||
if (!callback) { return preverify_ok; }
|
|
||||||
|
|
||||||
// Get SSL object from X509_STORE_CTX
|
// Get SSL object from X509_STORE_CTX
|
||||||
auto ssl = static_cast<SSL *>(
|
auto ssl = static_cast<SSL *>(
|
||||||
X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
||||||
if (!ssl) { return preverify_ok; }
|
if (!ssl) { return preverify_ok; }
|
||||||
|
|
||||||
|
auto callback = get_openssl_verify_callback(SSL_get_SSL_CTX(ssl));
|
||||||
|
if (!callback || !*callback) { return preverify_ok; }
|
||||||
|
|
||||||
// Get current certificate and depth
|
// Get current certificate and depth
|
||||||
auto cert = X509_STORE_CTX_get_current_cert(ctx);
|
auto cert = X509_STORE_CTX_get_current_cert(ctx);
|
||||||
int depth = X509_STORE_CTX_get_error_depth(ctx);
|
int depth = X509_STORE_CTX_get_error_depth(ctx);
|
||||||
@@ -19293,7 +19303,7 @@ inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
|
|||||||
verify_ctx.error_string =
|
verify_ctx.error_string =
|
||||||
(error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
|
(error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
|
||||||
|
|
||||||
return callback(verify_ctx) ? 1 : 0;
|
return (*callback)(verify_ctx) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not
|
// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not
|
||||||
@@ -19334,7 +19344,10 @@ inline ctx_t create_client_context() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void free_context(ctx_t ctx) {
|
inline void free_context(ctx_t ctx) {
|
||||||
if (ctx) { SSL_CTX_free(static_cast<SSL_CTX *>(ctx)); }
|
if (!ctx) { return; }
|
||||||
|
auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
|
||||||
|
delete impl::get_openssl_verify_callback(ssl_ctx);
|
||||||
|
SSL_CTX_free(ssl_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool set_min_version(ctx_t ctx, Version version) {
|
inline bool set_min_version(ctx_t ctx, Version version) {
|
||||||
@@ -20165,9 +20178,20 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
|
|||||||
if (!ctx) { return false; }
|
if (!ctx) { return false; }
|
||||||
auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
|
auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
|
||||||
|
|
||||||
impl::get_verify_callback() = std::move(callback);
|
VerifyCallback *stored = nullptr;
|
||||||
|
if (callback) {
|
||||||
|
stored = new (std::nothrow) VerifyCallback(std::move(callback));
|
||||||
|
if (!stored) { return false; }
|
||||||
|
}
|
||||||
|
auto old = impl::get_openssl_verify_callback(ssl_ctx);
|
||||||
|
if (SSL_CTX_set_ex_data(ssl_ctx, impl::openssl_verify_callback_index(),
|
||||||
|
stored) != 1) {
|
||||||
|
delete stored;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete old;
|
||||||
|
|
||||||
if (impl::get_verify_callback()) {
|
if (stored) {
|
||||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
|
||||||
} else {
|
} else {
|
||||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
|
||||||
@@ -20224,13 +20248,14 @@ struct MbedTlsSession {
|
|||||||
// OpenSSL and wolfSSL keep them independent).
|
// OpenSSL and wolfSSL keep them independent).
|
||||||
bool suppress_hostname_mismatch = false;
|
bool suppress_hostname_mismatch = false;
|
||||||
|
|
||||||
|
// The context this session was created from; mbedtls_verify_callback()
|
||||||
|
// runs its verify_callback.
|
||||||
|
const MbedTlsContext *ctx = nullptr;
|
||||||
|
|
||||||
// Copied from the owning MbedTlsContext at creation. set_sni() uses this to
|
// Copied from the owning MbedTlsContext at creation. set_sni() uses this to
|
||||||
// decide which verify callback to install when hostname verification is
|
// decide which verify callback to install when hostname verification is
|
||||||
// disabled: mbedtls_verify_callback() when a user callback is genuinely
|
// disabled: mbedtls_verify_callback() when a user callback is wired for this
|
||||||
// wired for this context, or a self-contained one otherwise, so a session
|
// context, or a self-contained one otherwise.
|
||||||
// that never opted into a callback never consults the process-wide
|
|
||||||
// set_verify_callback() slot (which some other, unrelated client may have
|
|
||||||
// populated).
|
|
||||||
bool has_verify_callback = false;
|
bool has_verify_callback = false;
|
||||||
|
|
||||||
MbedTlsSession() { mbedtls_ssl_init(&ssl); }
|
MbedTlsSession() { mbedtls_ssl_init(&ssl); }
|
||||||
@@ -20406,9 +20431,7 @@ inline void mbedtls_clear_cn_mismatch(uint32_t *flags) {
|
|||||||
|
|
||||||
// Verify callback used when hostname verification is disabled for a session
|
// Verify callback used when hostname verification is disabled for a session
|
||||||
// that has no user-supplied verify callback of its own (MbedTlsSession::
|
// that has no user-supplied verify callback of its own (MbedTlsSession::
|
||||||
// has_verify_callback is false). Deliberately does not consult
|
// has_verify_callback is false).
|
||||||
// get_verify_callback(): that slot is process-wide, so reading it here would
|
|
||||||
// pick up whatever another, unrelated client last installed there.
|
|
||||||
inline int mbedtls_mask_hostname_mismatch_callback(void *data,
|
inline int mbedtls_mask_hostname_mismatch_callback(void *data,
|
||||||
mbedtls_x509_crt *, int,
|
mbedtls_x509_crt *, int,
|
||||||
uint32_t *flags) {
|
uint32_t *flags) {
|
||||||
@@ -20435,7 +20458,8 @@ inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
|
|||||||
mbedtls_clear_cn_mismatch(flags);
|
mbedtls_clear_cn_mismatch(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &callback = get_verify_callback();
|
if (!session || !session->ctx) { return 0; }
|
||||||
|
const auto &callback = session->ctx->verify_callback;
|
||||||
if (!callback) { return 0; } // Continue with default verification
|
if (!callback) { return 0; } // Continue with default verification
|
||||||
|
|
||||||
// Build context
|
// Build context
|
||||||
@@ -20853,6 +20877,7 @@ inline session_t create_session(ctx_t ctx, socket_t sock) {
|
|||||||
|
|
||||||
// Set per-session verify callback with session pointer if callback is
|
// Set per-session verify callback with session pointer if callback is
|
||||||
// registered
|
// registered
|
||||||
|
session->ctx = mctx;
|
||||||
session->has_verify_callback = mctx->has_verify_callback;
|
session->has_verify_callback = mctx->has_verify_callback;
|
||||||
if (mctx->has_verify_callback) {
|
if (mctx->has_verify_callback) {
|
||||||
mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
|
mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
|
||||||
@@ -20889,7 +20914,7 @@ inline bool set_sni(session_t session, const char *hostname,
|
|||||||
// mbedtls_verify_callback() masks the mismatch flag itself before
|
// mbedtls_verify_callback() masks the mismatch flag itself before
|
||||||
// consulting it (see suppress_hostname_mismatch above) - reinstalling it
|
// consulting it (see suppress_hostname_mismatch above) - reinstalling it
|
||||||
// here would be redundant. Otherwise install the self-contained masking
|
// here would be redundant. Otherwise install the self-contained masking
|
||||||
// callback, which never touches the process-wide callback slot.
|
// callback.
|
||||||
if (!msession->has_verify_callback) {
|
if (!msession->has_verify_callback) {
|
||||||
mbedtls_ssl_set_verify(&msession->ssl,
|
mbedtls_ssl_set_verify(&msession->ssl,
|
||||||
impl::mbedtls_mask_hostname_mismatch_callback,
|
impl::mbedtls_mask_hostname_mismatch_callback,
|
||||||
@@ -21572,19 +21597,17 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
|
|||||||
if (!ctx) { return false; }
|
if (!ctx) { return false; }
|
||||||
auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
|
auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
|
||||||
|
|
||||||
impl::get_verify_callback() = std::move(callback);
|
mbed_ctx->verify_callback = std::move(callback);
|
||||||
mbed_ctx->has_verify_callback =
|
mbed_ctx->has_verify_callback = static_cast<bool>(mbed_ctx->verify_callback);
|
||||||
static_cast<bool>(impl::get_verify_callback());
|
|
||||||
|
|
||||||
if (mbed_ctx->has_verify_callback) {
|
if (mbed_ctx->has_verify_callback) {
|
||||||
// Set OPTIONAL mode to ensure callback is called even when verification
|
// Set OPTIONAL mode to ensure callback is called even when verification
|
||||||
// is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set)
|
// is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set).
|
||||||
|
// The callback itself is wired per session in create_session(), which
|
||||||
|
// passes the session so mbedtls_verify_callback() can reach this context.
|
||||||
mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
|
mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
|
||||||
mbedtls_ssl_conf_verify(&mbed_ctx->conf, impl::mbedtls_verify_callback,
|
|
||||||
nullptr);
|
|
||||||
} else {
|
|
||||||
mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr);
|
|
||||||
}
|
}
|
||||||
|
mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21710,8 +21733,12 @@ inline int wolfssl_sni_callback(WOLFSSL *ssl, int *ret, void *exArg) {
|
|||||||
// 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) {
|
||||||
auto &callback = get_verify_callback();
|
// userCtx carries the pointer create_session() passed to
|
||||||
if (!callback) { return preverify_ok; }
|
// wolfSSL_SetCertCbCtx()
|
||||||
|
auto wctx = x509_ctx ? static_cast<const WolfSSLContext *>(x509_ctx->userCtx)
|
||||||
|
: nullptr;
|
||||||
|
if (!wctx || !wctx->verify_callback) { return preverify_ok; }
|
||||||
|
const auto &callback = wctx->verify_callback;
|
||||||
|
|
||||||
WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
|
WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
|
||||||
int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx);
|
int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx);
|
||||||
@@ -22001,6 +22028,9 @@ inline session_t create_session(ctx_t ctx, socket_t sock) {
|
|||||||
|
|
||||||
wolfSSL_set_fd(session->ssl, static_cast<int>(sock));
|
wolfSSL_set_fd(session->ssl, static_cast<int>(sock));
|
||||||
|
|
||||||
|
// Lets wolfssl_verify_callback() find this context's verify_callback
|
||||||
|
wolfSSL_SetCertCbCtx(session->ssl, wctx);
|
||||||
|
|
||||||
return static_cast<session_t>(session);
|
return static_cast<session_t>(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22728,8 +22758,8 @@ inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
|
|||||||
if (!ctx) { return false; }
|
if (!ctx) { return false; }
|
||||||
auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
|
auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
|
||||||
|
|
||||||
impl::get_verify_callback() = std::move(callback);
|
wctx->verify_callback = std::move(callback);
|
||||||
wctx->has_verify_callback = static_cast<bool>(impl::get_verify_callback());
|
wctx->has_verify_callback = static_cast<bool>(wctx->verify_callback);
|
||||||
|
|
||||||
if (wctx->has_verify_callback) {
|
if (wctx->has_verify_callback) {
|
||||||
wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
|
wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
|
||||||
|
|||||||
@@ -15429,6 +15429,88 @@ TEST(VerifyCallbackTest, VerifyContextFields) {
|
|||||||
EXPECT_TRUE(saw_leaf_cert);
|
EXPECT_TRUE(saw_leaf_cert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each client's verifier is stored with its own TLS context, so configuring a
|
||||||
|
// second client on the same thread must not replace the first one's.
|
||||||
|
TEST(VerifyCallbackTest, PerClientVerifiersOnSameThread) {
|
||||||
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
|
||||||
|
svr.Get("/test", [](const Request &, Response &res) {
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
std::atomic<int> count1{0};
|
||||||
|
std::atomic<int> count2{0};
|
||||||
|
|
||||||
|
SSLClient cli1(HOST, PORT);
|
||||||
|
cli1.enable_server_certificate_verification(false);
|
||||||
|
cli1.set_server_certificate_verifier([&](const tls::VerifyContext &) {
|
||||||
|
count1++;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
SSLClient cli2(HOST, PORT);
|
||||||
|
cli2.enable_server_certificate_verification(false);
|
||||||
|
cli2.set_server_certificate_verifier([&](const tls::VerifyContext &) {
|
||||||
|
count2++;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto res1 = cli1.Get("/test");
|
||||||
|
ASSERT_TRUE(res1) << "Error: " << to_string(res1.error());
|
||||||
|
EXPECT_GT(count1.load(), 0);
|
||||||
|
EXPECT_EQ(0, count2.load());
|
||||||
|
|
||||||
|
count1 = 0;
|
||||||
|
auto res2 = cli2.Get("/test");
|
||||||
|
ASSERT_TRUE(res2) << "Error: " << to_string(res2.error());
|
||||||
|
EXPECT_EQ(0, count1.load());
|
||||||
|
EXPECT_GT(count2.load(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A verifier set on one thread must still run when the request is made from
|
||||||
|
// another thread.
|
||||||
|
TEST(VerifyCallbackTest, VerifierRunsOnAnotherThread) {
|
||||||
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
|
||||||
|
svr.Get("/test", [](const Request &, Response &res) {
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
std::atomic<int> count{0};
|
||||||
|
|
||||||
|
SSLClient cli(HOST, PORT);
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
cli.set_server_certificate_verifier([&](const tls::VerifyContext &) {
|
||||||
|
count++;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Result res;
|
||||||
|
thread worker([&]() { res = cli.Get("/test"); });
|
||||||
|
worker.join();
|
||||||
|
|
||||||
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
|
EXPECT_GT(count.load(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TlsVerifyErrorTest, GetVerifyErrorString) {
|
TEST(TlsVerifyErrorTest, GetVerifyErrorString) {
|
||||||
using httplib::tls::TlsError;
|
using httplib::tls::TlsError;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user