|
|
@@ -4830,7 +4830,7 @@ void set_verify_client(ctx_t ctx, bool require);
|
|
|
// Session management
|
|
|
session_t create_session(ctx_t ctx, socket_t sock);
|
|
|
void free_session(session_t session);
|
|
|
-bool set_sni(session_t session, const char *hostname);
|
|
|
+bool set_sni(session_t session, const char *hostname, bool verify_hostname);
|
|
|
|
|
|
// Handshake (non-blocking capable)
|
|
|
TlsError connect(session_t session);
|
|
|
@@ -10206,12 +10206,12 @@ inline bool setup_client_tls_session(
|
|
|
}
|
|
|
if (!session) { return fail(Error::SSLConnection, 0, get_error()); }
|
|
|
|
|
|
- // RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL
|
|
|
- // set_sni also turns on hostname verification during the handshake, so it
|
|
|
- // must be skipped for IP hosts as well; their identity is checked
|
|
|
- // post-handshake below instead.
|
|
|
+ // RFC 6066: SNI must not be set for IP addresses; skip it for IP hosts, so
|
|
|
+ // their identity is checked post-handshake below instead. On Mbed TLS and
|
|
|
+ // wolfSSL, set_sni also drives handshake-time hostname verification, so
|
|
|
+ // options.server_hostname_verification is threaded through here.
|
|
|
if (!is_ip_address(host)) {
|
|
|
- if (!set_sni(session, host.c_str())) {
|
|
|
+ if (!set_sni(session, host.c_str(), options.server_hostname_verification)) {
|
|
|
return fail(Error::SSLConnection, 0, get_error());
|
|
|
}
|
|
|
}
|
|
|
@@ -18062,12 +18062,15 @@ inline void free_session(session_t session) {
|
|
|
if (session) { SSL_free(static_cast<SSL *>(session)); }
|
|
|
}
|
|
|
|
|
|
-inline bool set_sni(session_t session, const char *hostname) {
|
|
|
+inline bool set_sni(session_t session, const char *hostname,
|
|
|
+ bool /*verify_hostname*/) {
|
|
|
if (!session || !hostname) return false;
|
|
|
|
|
|
auto ssl = static_cast<SSL *>(session);
|
|
|
|
|
|
- // Set SNI (Server Name Indication) only - does not enable verification
|
|
|
+ // Set SNI (Server Name Indication) only - does not enable verification.
|
|
|
+ // OpenSSL never binds identity checking to SNI (that happens post-
|
|
|
+ // handshake in setup_client_tls_session()), so verify_hostname is unused.
|
|
|
#if defined(OPENSSL_IS_BORINGSSL)
|
|
|
return SSL_set_tlsext_host_name(ssl, hostname) == 1;
|
|
|
#else
|
|
|
@@ -18705,6 +18708,21 @@ struct MbedTlsSession {
|
|
|
unsigned char peeked_byte = 0;
|
|
|
bool has_peeked_byte = false;
|
|
|
|
|
|
+ // Set by set_sni() when the caller disabled hostname verification, so the
|
|
|
+ // verify callback can clear the CN/SAN mismatch flag while still enforcing
|
|
|
+ // the rest of the chain (Mbed TLS ties SNI and identity checking together;
|
|
|
+ // OpenSSL and wolfSSL keep them independent).
|
|
|
+ bool suppress_hostname_mismatch = false;
|
|
|
+
|
|
|
+ // Copied from the owning MbedTlsContext at creation. set_sni() uses this to
|
|
|
+ // decide which verify callback to install when hostname verification is
|
|
|
+ // disabled: mbedtls_verify_callback() when a user callback is genuinely
|
|
|
+ // wired for this context, or a self-contained one otherwise, so a session
|
|
|
+ // 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;
|
|
|
+
|
|
|
MbedTlsSession() { mbedtls_ssl_init(&ssl); }
|
|
|
|
|
|
~MbedTlsSession() { mbedtls_ssl_free(&ssl); }
|
|
|
@@ -18721,7 +18739,8 @@ inline int &mbedtls_last_error() {
|
|
|
}
|
|
|
|
|
|
// Helper to map Mbed TLS error to ErrorCode
|
|
|
-inline ErrorCode map_mbedtls_error(int ret, int &out_errno) {
|
|
|
+inline ErrorCode map_mbedtls_error(int ret, int &out_errno,
|
|
|
+ uint32_t verify_flags) {
|
|
|
if (ret == 0) { return ErrorCode::Success; }
|
|
|
if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return ErrorCode::WantRead; }
|
|
|
if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return ErrorCode::WantWrite; }
|
|
|
@@ -18734,11 +18753,34 @@ inline ErrorCode map_mbedtls_error(int ret, int &out_errno) {
|
|
|
return ErrorCode::SyscallError;
|
|
|
}
|
|
|
if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
|
|
|
+ // Unlike OpenSSL/wolfSSL, Mbed TLS folds the CN/SAN identity check into
|
|
|
+ // the handshake's chain verification (see set_sni()); a mismatch there
|
|
|
+ // is reported the same way as any other verify_flags bit. Report it as
|
|
|
+ // HostnameMismatch, matching the other backends and the post-handshake
|
|
|
+ // identity check below, but only when naming is the sole problem -
|
|
|
+ // if the chain itself is also untrusted/expired/etc., that takes
|
|
|
+ // priority over the naming detail.
|
|
|
+ if (verify_flags == static_cast<uint32_t>(hostname_mismatch_code())) {
|
|
|
+ return ErrorCode::HostnameMismatch;
|
|
|
+ }
|
|
|
return ErrorCode::CertVerifyFailed;
|
|
|
}
|
|
|
return ErrorCode::Fatal;
|
|
|
}
|
|
|
|
|
|
+// Populates a TlsError from a failed (non-zero) mbedtls_ssl_handshake()
|
|
|
+// return value, including the verify-flags-dependent HostnameMismatch
|
|
|
+// mapping; shared by connect() and connect_nonblocking() so the
|
|
|
+// backend_code policy for that mapping only lives in one place.
|
|
|
+inline void fill_mbedtls_tls_error(TlsError &err, mbedtls_ssl_context &ssl,
|
|
|
+ int ret) {
|
|
|
+ auto verify_flags = mbedtls_ssl_get_verify_result(&ssl);
|
|
|
+ err.code = map_mbedtls_error(ret, err.sys_errno, verify_flags);
|
|
|
+ err.backend_code = err.code == ErrorCode::HostnameMismatch
|
|
|
+ ? static_cast<uint64_t>(verify_flags)
|
|
|
+ : static_cast<uint64_t>(-ret);
|
|
|
+}
|
|
|
+
|
|
|
// A TLS 1.3 NewSessionTicket (signaled by default on Mbed TLS 4.x) is a
|
|
|
// non-fatal notification delivered between records, not an error and not
|
|
|
// application data, so I/O calls that see it should just be retried. Kept in
|
|
|
@@ -18848,18 +18890,44 @@ inline int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl,
|
|
|
return 0; // Accept any SNI
|
|
|
}
|
|
|
|
|
|
+inline void mbedtls_clear_cn_mismatch(uint32_t *flags) {
|
|
|
+ *flags &= ~static_cast<uint32_t>(hostname_mismatch_code());
|
|
|
+}
|
|
|
+
|
|
|
+// Verify callback used when hostname verification is disabled for a session
|
|
|
+// that has no user-supplied verify callback of its own (MbedTlsSession::
|
|
|
+// has_verify_callback is false). Deliberately does not consult
|
|
|
+// 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,
|
|
|
+ mbedtls_x509_crt *, int,
|
|
|
+ uint32_t *flags) {
|
|
|
+ (void)data;
|
|
|
+ mbedtls_clear_cn_mismatch(flags);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
|
|
|
int cert_depth, uint32_t *flags);
|
|
|
|
|
|
// MbedTLS verify callback wrapper
|
|
|
inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
|
|
|
int cert_depth, uint32_t *flags) {
|
|
|
- auto &callback = get_verify_callback();
|
|
|
- if (!callback) { return 0; } // Continue with default verification
|
|
|
-
|
|
|
// data points to the MbedTlsSession
|
|
|
auto *session = static_cast<MbedTlsSession *>(data);
|
|
|
|
|
|
+ // set_sni() disabled hostname verification for this session: drop the
|
|
|
+ // CN/SAN mismatch flag so it doesn't fail the chain check below, mirroring
|
|
|
+ // the OpenSSL/wolfSSL backends where identity checking is independent of
|
|
|
+ // SNI. The final pass/fail decision still comes from the remaining flags
|
|
|
+ // (or, below, from the user's own verify callback).
|
|
|
+ if (session && session->suppress_hostname_mismatch) {
|
|
|
+ mbedtls_clear_cn_mismatch(flags);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto &callback = get_verify_callback();
|
|
|
+ if (!callback) { return 0; } // Continue with default verification
|
|
|
+
|
|
|
// Build context
|
|
|
VerifyContext verify_ctx;
|
|
|
verify_ctx.session = static_cast<session_t>(session);
|
|
|
@@ -19275,6 +19343,7 @@ inline session_t create_session(ctx_t ctx, socket_t sock) {
|
|
|
|
|
|
// Set per-session verify callback with session pointer if callback is
|
|
|
// registered
|
|
|
+ session->has_verify_callback = mctx->has_verify_callback;
|
|
|
if (mctx->has_verify_callback) {
|
|
|
mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
|
|
|
session);
|
|
|
@@ -19287,10 +19356,15 @@ inline void free_session(session_t session) {
|
|
|
if (session) { delete static_cast<impl::MbedTlsSession *>(session); }
|
|
|
}
|
|
|
|
|
|
-inline bool set_sni(session_t session, const char *hostname) {
|
|
|
+inline bool set_sni(session_t session, const char *hostname,
|
|
|
+ bool verify_hostname) {
|
|
|
if (!session || !hostname) { return false; }
|
|
|
auto msession = static_cast<impl::MbedTlsSession *>(session);
|
|
|
|
|
|
+ // mbedtls_ssl_set_hostname() both sends the SNI extension and binds the
|
|
|
+ // handshake-time CN/SAN check to `hostname`; the two can't be requested
|
|
|
+ // independently, so a disabled hostname check is handled below by masking
|
|
|
+ // the resulting mismatch flag instead of skipping this call.
|
|
|
int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname);
|
|
|
if (ret != 0) {
|
|
|
impl::mbedtls_last_error() = ret;
|
|
|
@@ -19298,6 +19372,21 @@ inline bool set_sni(session_t session, const char *hostname) {
|
|
|
}
|
|
|
|
|
|
msession->hostname = hostname;
|
|
|
+
|
|
|
+ if (!verify_hostname) {
|
|
|
+ msession->suppress_hostname_mismatch = true;
|
|
|
+ // If a user verify callback is already wired for this session,
|
|
|
+ // mbedtls_verify_callback() masks the mismatch flag itself before
|
|
|
+ // consulting it (see suppress_hostname_mismatch above) - reinstalling it
|
|
|
+ // here would be redundant. Otherwise install the self-contained masking
|
|
|
+ // callback, which never touches the process-wide callback slot.
|
|
|
+ if (!msession->has_verify_callback) {
|
|
|
+ mbedtls_ssl_set_verify(&msession->ssl,
|
|
|
+ impl::mbedtls_mask_hostname_mismatch_callback,
|
|
|
+ msession);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -19317,8 +19406,7 @@ inline TlsError connect(session_t session) {
|
|
|
if (ret == 0) {
|
|
|
err.code = ErrorCode::Success;
|
|
|
} else {
|
|
|
- err.code = impl::map_mbedtls_error(ret, err.sys_errno);
|
|
|
- err.backend_code = static_cast<uint64_t>(-ret);
|
|
|
+ impl::fill_mbedtls_tls_error(err, msession->ssl, ret);
|
|
|
impl::mbedtls_last_error() = ret;
|
|
|
}
|
|
|
|
|
|
@@ -19369,10 +19457,7 @@ inline bool connect_nonblocking(session_t session, socket_t sock,
|
|
|
}
|
|
|
|
|
|
// TlsError or timeout
|
|
|
- if (err) {
|
|
|
- err->code = impl::map_mbedtls_error(ret, err->sys_errno);
|
|
|
- err->backend_code = static_cast<uint64_t>(-ret);
|
|
|
- }
|
|
|
+ if (err) { impl::fill_mbedtls_tls_error(*err, msession->ssl, ret); }
|
|
|
impl::mbedtls_last_error() = ret;
|
|
|
return false;
|
|
|
}
|
|
|
@@ -19438,7 +19523,7 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- err.code = impl::map_mbedtls_error(ret, err.sys_errno);
|
|
|
+ err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0);
|
|
|
err.backend_code = static_cast<uint64_t>(-ret);
|
|
|
impl::mbedtls_last_error() = ret;
|
|
|
// mbedTLS signals a clean close_notify via a negative error code rather
|
|
|
@@ -19471,7 +19556,7 @@ inline ssize_t write(session_t session, const void *buf, size_t len,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- err.code = impl::map_mbedtls_error(ret, err.sys_errno);
|
|
|
+ err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0);
|
|
|
err.backend_code = static_cast<uint64_t>(-ret);
|
|
|
impl::mbedtls_last_error() = ret;
|
|
|
return -1;
|
|
|
@@ -20434,7 +20519,8 @@ inline void free_session(session_t session) {
|
|
|
if (session) { delete static_cast<impl::WolfSSLSession *>(session); }
|
|
|
}
|
|
|
|
|
|
-inline bool set_sni(session_t session, const char *hostname) {
|
|
|
+inline bool set_sni(session_t session, const char *hostname,
|
|
|
+ bool verify_hostname) {
|
|
|
if (!session || !hostname) { return false; }
|
|
|
auto wsession = static_cast<impl::WolfSSLSession *>(session);
|
|
|
|
|
|
@@ -20446,8 +20532,10 @@ inline bool set_sni(session_t session, const char *hostname) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- // Also set hostname for verification
|
|
|
- wolfSSL_check_domain_name(wsession->ssl, hostname);
|
|
|
+ // wolfSSL_check_domain_name binds identity checking to the handshake,
|
|
|
+ // separately from the SNI extension sent above; skip it when hostname
|
|
|
+ // verification is disabled so only the chain is checked, matching OpenSSL.
|
|
|
+ if (verify_hostname) { wolfSSL_check_domain_name(wsession->ssl, hostname); }
|
|
|
|
|
|
wsession->hostname = hostname;
|
|
|
return true;
|