Fix TLS chain verification bypass for IP hosts on Mbed TLS and wolfSSL

For connections to IP-literal hosts with server certificate
verification enabled, the Mbed TLS and wolfSSL backends downgraded the
verification mode before the handshake because no hostname could be
bound for in-handshake checks:

- SSLClient skipped certificate chain validation entirely; only the
  post-handshake identity check (IP SAN match) remained, so any
  untrusted certificate carrying a matching IP SAN was accepted
- The WebSocket client skipped verification altogether on Mbed TLS,
  accepting any certificate

Keep the verification mode enabled for IP hosts and verify the
certificate identity post-handshake via tls::verify_hostname(), which
supports IP SANs on all backends. The WebSocket path now performs the
same post-handshake identity check as SSLClient. On Mbed TLS, sessions
explicitly opt out of in-handshake hostname verification (mandatory
since Mbed TLS 3.6.4) and the post-handshake check covers identity
instead; DNS hosts still bind the hostname during the handshake. Also
stop sending SNI for IP hosts on Mbed TLS and wolfSSL (RFC 6066).
This commit is contained in:
yhirose committed 2026-06-10 00:07:27 -04:00
1 parent 39ec7d0508
commit fa981cedae
2 files changed
+99 -20

No files matched your search

+33 -16
View File
@@ -9283,20 +9283,25 @@ inline bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx,
bool is_ip = is_ip_address(host);
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
if (is_ip && server_certificate_verification) {
set_verify_client(ctx, false);
} else {
set_verify_client(ctx, server_certificate_verification);
}
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
// Chain verification happens during the handshake even for IP hosts; the
// certificate identity is verified post-handshake via verify_hostname()
set_verify_client(ctx, server_certificate_verification);
#endif
session = create_session(ctx, sock);
if (!session) { return false; }
// RFC 6066: SNI must not be set for IP addresses
if (!is_ip) { set_sni(session, host.c_str()); }
if (server_certificate_verification) { set_hostname(session, host.c_str()); }
// RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL
// set_hostname also sets SNI, so it must be skipped for IP hosts as well;
// their identity is checked post-handshake below instead.
if (!is_ip) {
if (server_certificate_verification) {
set_hostname(session, host.c_str());
} else {
set_sni(session, host.c_str());
}
}
if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) {
return false;
@@ -9304,6 +9309,14 @@ inline bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx,
if (server_certificate_verification) {
if (get_verify_result(session) != 0) { return false; }
// Identity check against the peer certificate, post-handshake for all
// backends (same as SSLClient). For IP hosts this is the only identity
// verification since no hostname is bound during the handshake.
auto server_cert = get_peer_cert(session);
if (!server_cert) { return false; }
auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
if (!verify_hostname(server_cert, host.c_str())) { return false; }
}
return true;
@@ -16200,13 +16213,9 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
// MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses
// SSL_VERIFY_NONE by default and performs all verification post-handshake).
// For IP addresses with verification enabled, use OPTIONAL mode since
// these backends require hostname for strict verification.
if (is_ip && server_certificate_verification_) {
set_verify_client(ctx_, false);
} else {
set_verify_client(ctx_, server_certificate_verification_);
}
// Chain verification happens during the handshake even for IP hosts; the
// certificate identity is verified post-handshake via verify_hostname().
set_verify_client(ctx_, server_certificate_verification_);
#endif
// Create TLS session
@@ -18267,6 +18276,14 @@ inline session_t create_session(ctx_t ctx, socket_t sock) {
return nullptr;
}
// Explicitly opt out of in-handshake hostname verification by default;
// since Mbed TLS 3.6.4 a client handshake with certificate verification
// fails outright when no hostname was set. set_sni() installs the real
// hostname for DNS hosts; for IP hosts (where SNI must not be set) the
// caller verifies the certificate identity post-handshake via
// verify_hostname().
mbedtls_ssl_set_hostname(&session->ssl, nullptr);
// Set BIO callbacks
mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb,
impl::mbedtls_net_recv_cb, nullptr);