Boost.Asio SSL stream accepts certificates for mismatched hostnames without custom verification callback
0 reputation · 03 Jul 2020, 06:04 UTC
0 reputation · 03 Jul 2020, 06:04 UTC
A Boost.Asio SSL client configured with ssl::verify_peer successfully validates a server certificate chain — trust anchor, expiration, and signature — yet connects to a host whose certificate contains a different Subject Alternative Name or Common Name than the target hostname. No verification failure occurs unless the application installs a custom callback that explicitly matches the hostname against the certificate's SAN or CN fields.
Boost.Asio's ssl::stream and ssl::context provide TLS transport primitives but do not implement RFC 6125 hostname verification by default. The library's built-in verify_callback checks chain validity only. Developers must supply their own callback, typically using X509_check_host (OpenSSL 1.1.0+) or manual SAN parsing, to enforce that the connected hostname appears in the certificate. This design reflects Asio's low-level abstraction philosophy, deferring application-layer policy to the user.
ssl::context::native_handle()?std::net proposals to standardize hostname verification requirements?35275 reputation · 03 Jul 2020, 11:53 UTC
This is expected behavior, not a bug. ssl::verify_peer only asks OpenSSL to validate the certificate chain — trust anchor, signatures, and expiry. Matching the certificate's SAN/CN against the hostname you dialed is application-layer policy (RFC 6125), and Boost.Asio deliberately leaves that to you. So a perfectly valid certificate for other.example.com will pass verify_peer even when you connected to api.example.com.
You do not need to write SAN-parsing code. Boost.Asio ships ready-made callback helpers:
ssl::stream<tcp::socket> stream(io_ctx, ssl_ctx);
stream.set_verify_mode(ssl::verify_peer);
stream.set_verify_callback(
ssl::host_name_verification("api.example.com")); // Boost >= ~1.73
// older Boost: ssl::rfc2818_verification("api.example.com")
// Also set SNI so the server presents the right certificate:
SSL_set_tlsext_host_name(stream.native_handle(), "api.example.com");
stream.handshake(ssl::stream_base::client);
Despite its name, rfc2818_verification checks SANs and falls back to CN only when no SANs exist. host_name_verification is the newer, stricter helper; check BOOST_VERSION if you're unsure which is available.
SSL_set_tlsext_host_name, a virtual-hosted server may present a default certificate, and your verification then fails (or worse, passes) for the wrong name.true unconditionally — a common debugging shortcut — silently reopens the mismatch hole. If you wrap the built-in helper, propagate its result rather than swallowing it.native_handle(), verification semantics are backend-specific; there is no single portable pattern beyond per-backend checks. Most production Asio code standardizes on OpenSSL for this reason.rfc2818_verification/host_name_verification to the underlying Asio stream — that is the intended pattern, not a workaround.std::net proposal that mandates hostname verification at the transport layer. Given that those efforts have stalled, I would not design around it — treat this as uncertain and verify against current proposals if it matters to you.verify_peer set — the handshake succeeds, demonstrating the gap.ssl::host_name_verification(expected_host) — the same connection now fails with a certificate verification error.openssl s_client -connect host:443 -servername host to inspect which SANs the server actually presents.Assumptions: OpenSSL-backed Asio, Boost 1.73+ for host_name_verification. If you're on a different TLS backend, the callback approach above needs review.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.