From 837542f9cf8c91a67869fd1f8a194ef0705541cb Mon Sep 17 00:00:00 2001 From: HuangHaitao Date: Wed, 23 Jul 2025 01:47:45 +0800 Subject: [PATCH] Fix CVE-2025-27809 Signed-off-by: HuangHaitao --- ChangeLog.d/mbedtls_ssl_set_hostname.txt | 18 ++++ include/mbedtls/error.h | 2 +- include/mbedtls/mbedtls_config.h | 40 +++++++++ include/mbedtls/ssl.h | 104 ++++++++++++++++++++--- library/ssl_client.c | 18 ++-- library/ssl_misc.h | 12 +++ library/ssl_tls.c | 99 ++++++++++++++++++--- 7 files changed, 261 insertions(+), 32 deletions(-) create mode 100644 ChangeLog.d/mbedtls_ssl_set_hostname.txt diff --git a/ChangeLog.d/mbedtls_ssl_set_hostname.txt b/ChangeLog.d/mbedtls_ssl_set_hostname.txt new file mode 100644 index 000000000..fd3129070 --- /dev/null +++ b/ChangeLog.d/mbedtls_ssl_set_hostname.txt @@ -0,0 +1,18 @@ +Default behavior changes + * In TLS clients, if mbedtls_ssl_set_hostname() has not been called, + mbedtls_ssl_handshake() now fails with + MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + if certificate-based authentication of the server is attempted. + This is because authenticating a server without knowing what name + to expect is usually insecure. To restore the old behavior, either + call mbedtls_ssl_set_hostname() with NULL as the hostname, or + enable the new compile-time option + MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME. + +Security + * Note that TLS clients should generally call mbedtls_ssl_set_hostname() + if they use certificate authentication (i.e. not pre-shared keys). + Otherwise, in many scenarios, the server could be impersonated. + The library will now prevent the handshake and return + MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + if mbedtls_ssl_set_hostname() has not been called. \ No newline at end of file diff --git a/include/mbedtls/error.h b/include/mbedtls/error.h index 23a83f2c8..9cf80f237 100644 --- a/include/mbedtls/error.h +++ b/include/mbedtls/error.h @@ -92,7 +92,7 @@ * MD 5 5 * HKDF 5 1 (Started from top) * PKCS7 5 12 (Started from 0x5300) - * SSL 5 2 (Started from 0x5F00) + * SSL 5 3 (Started from 0x5F00) * CIPHER 6 8 (Started from 0x6080) * SSL 6 22 (Started from top, plus 0x6000) * SSL 7 20 (Started from 0x7000, gaps at diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h index 458bb7ffd..475c2515a 100644 --- a/include/mbedtls/mbedtls_config.h +++ b/include/mbedtls/mbedtls_config.h @@ -1604,6 +1604,46 @@ */ //#define MBEDTLS_SSL_ASYNC_PRIVATE +/** \def MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + * + * In TLS clients, when a client authenticates a server through its + * certificate, the client normally checks three things: + * - the certificate chain must be valid; + * - the chain must start from a trusted CA; + * - the certificate must cover the server name that is expected by the client. + * + * Omitting any of these checks is generally insecure, and can allow a + * malicious server to impersonate a legitimate server. + * + * The third check may be safely skipped in some unusual scenarios, + * such as networks where eavesdropping is a risk but not active attacks, + * or a private PKI where the client equally trusts all servers that are + * accredited by the root CA. + * + * You should call mbedtls_ssl_set_hostname() with the expected server name + * before starting a TLS handshake on a client (unless the client is + * set up to only use PSK-based authentication, which does not rely on the + * host name). This configuration option controls what happens if a TLS client + * is configured with the authentication mode #MBEDTLS_SSL_VERIFY_REQUIRED + * (default), certificate authentication is enabled and the client does not + * call mbedtls_ssl_set_hostname(): + * + * - If this option is unset (default), the connection attempt is aborted + * with the error #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME. + * - If this option is set, the TLS library does not check the server name + * that the certificate is valid for. This is the historical behavior + * of Mbed TLS, but may be insecure as explained above. + * + * Enable this option for strict backward compatibility if you have + * determined that it is secure in the scenario where you are using + * Mbed TLS. + * + * \deprecated This option exists only for backward compatibility and will + * be removed in the next major version of Mbed TLS. + * + */ +#define MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + /** * \def MBEDTLS_SSL_CONTEXT_SERIALIZATION * diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h index 172d4693b..4e1e3684a 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -169,6 +169,42 @@ #define MBEDTLS_ERR_SSL_VERSION_MISMATCH -0x5F00 /** Invalid value in SSL config */ #define MBEDTLS_ERR_SSL_BAD_CONFIG -0x5E80 +/* Error space gap */ +/** Attempt to verify a certificate without an expected hostname. + * This is usually insecure. + * + * In TLS clients, when a client authenticates a server through its + * certificate, the client normally checks three things: + * - the certificate chain must be valid; + * - the chain must start from a trusted CA; + * - the certificate must cover the server name that is expected by the client. + * + * Omitting any of these checks is generally insecure, and can allow a + * malicious server to impersonate a legitimate server. + * + * The third check may be safely skipped in some unusual scenarios, + * such as networks where eavesdropping is a risk but not active attacks, + * or a private PKI where the client equally trusts all servers that are + * accredited by the root CA. + * + * You should call mbedtls_ssl_set_hostname() with the expected server name + * before starting a TLS handshake on a client (unless the client is + * set up to only use PSK-based authentication, which does not rely on the + * host name). If you have determined that server name verification is not + * required for security in your scenario, call mbedtls_ssl_set_hostname() + * with \p NULL as the server name. + * + * This error is raised if all of the following conditions are met: + * + * - A TLS client is configured with the authentication mode + * #MBEDTLS_SSL_VERIFY_REQUIRED (default). + * - Certificate authentication is enabled. + * - The client does not call mbedtls_ssl_set_hostname(). + * - The configuration option + * #MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + * is not enabled. + */ +#define MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME -0x5D80 /* * Constants from RFC 8446 for TLS 1.3 PSK modes @@ -1878,8 +1914,35 @@ struct mbedtls_ssl_context { * User settings */ #if defined(MBEDTLS_X509_CRT_PARSE_C) - char *MBEDTLS_PRIVATE(hostname); /*!< expected peer CN for verification - (and SNI if available) */ + /** Expected peer CN for verification. + * + * Also used on clients for SNI, + * and for TLS 1.3 session resumption using tickets. + * + * The value of this field can be: + * - \p NULL in a newly initialized or reset context. + * - A heap-allocated copy of the last value passed to + * mbedtls_ssl_set_hostname(), if the last call had a non-null + * \p hostname argument. + * - A special value to indicate that mbedtls_ssl_set_hostname() + * was called with \p NULL (as opposed to never having been called). + * See `mbedtls_ssl_get_hostname_pointer()` in `ssl_tls.c`. + * + * If this field contains the value \p NULL and the configuration option + * #MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + * is unset, on a TLS client, attempting to verify a server certificate + * results in the error + * #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME. + * + * If this field contains the special value described above, or if + * the value is \p NULL and the configuration option + * #MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + * is set, then the peer name verification is skipped, which may be + * insecure, especially on a client. Furthermore, on a client, the + * server_name extension is not sent, and the server name is ignored + * in TLS 1.3 session resumption using tickets. + */ + char *MBEDTLS_PRIVATE(hostname); #endif /* MBEDTLS_X509_CRT_PARSE_C */ #if defined(MBEDTLS_SSL_ALPN) @@ -1987,6 +2050,14 @@ void mbedtls_ssl_init(mbedtls_ssl_context *ssl); * Calling mbedtls_ssl_setup again is not supported, even * if no session is active. * + * \warning After setting up a client context, if certificate-based + * authentication is enabled, you should call + * mbedtls_ssl_set_hostname() to specifiy the expected + * name of the server. Without this, in most scenarios, + * the TLS connection is insecure. See + * #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + * for more information. + * * \note If #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto * subsystem must have been initialized by calling * psa_crypto_init() before calling this function. @@ -3952,16 +4023,29 @@ void mbedtls_ssl_conf_sig_algs(mbedtls_ssl_config *conf, #if defined(MBEDTLS_X509_CRT_PARSE_C) /** * \brief Set or reset the hostname to check against the received - * server certificate. It sets the ServerName TLS extension, - * too, if that extension is enabled. (client-side only) + * peer certificate. On a client, this also sets the + * ServerName TLS extension, if that extension is enabled. + * On a TLS 1.3 client, this also sets the server name in + * the session resumption ticket, if that feature is enabled. * * \param ssl SSL context - * \param hostname the server hostname, may be NULL to clear hostname - - * \note Maximum hostname length MBEDTLS_SSL_MAX_HOST_NAME_LEN. - * - * \return 0 if successful, MBEDTLS_ERR_SSL_ALLOC_FAILED on - * allocation failure, MBEDTLS_ERR_SSL_BAD_INPUT_DATA on + * \param hostname The server hostname. This may be \c NULL to clear + * the hostname. + * + * \note Maximum hostname length #MBEDTLS_SSL_MAX_HOST_NAME_LEN. + * + * \note If the hostname is \c NULL on a client, then the server + * is not authenticated: it only needs to have a valid + * certificate, not a certificate matching its name. + * Therefore you should always call this function on a client, + * unless the connection is set up to only allow + * pre-shared keys, or in scenarios where server + * impersonation is not a concern. See the documentation of + * #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + * for more details. + * + * \return 0 if successful, #MBEDTLS_ERR_SSL_ALLOC_FAILED on + * allocation failure, #MBEDTLS_ERR_SSL_BAD_INPUT_DATA on * too long input hostname. * * Hostname set to the one provided on success (cleared diff --git a/library/ssl_client.c b/library/ssl_client.c index 345e60893..0bd00cd91 100644 --- a/library/ssl_client.c +++ b/library/ssl_client.c @@ -29,19 +29,20 @@ static int ssl_write_hostname_ext(mbedtls_ssl_context *ssl, size_t *olen) { unsigned char *p = buf; + const char *hostname = mbedtls_ssl_get_hostname_pointer(ssl); size_t hostname_len; *olen = 0; - if (ssl->hostname == NULL) { + if (hostname == NULL) { return 0; } MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, adding server name extension: %s", - ssl->hostname)); + hostname)); - hostname_len = strlen(ssl->hostname); + hostname_len = strlen(hostname); MBEDTLS_SSL_CHK_BUF_PTR(p, end, hostname_len + 9); @@ -85,7 +86,7 @@ static int ssl_write_hostname_ext(mbedtls_ssl_context *ssl, MBEDTLS_PUT_UINT16_BE(hostname_len, p, 0); p += 2; - memcpy(p, ssl->hostname, hostname_len); + memcpy(p, hostname, hostname_len); *olen = hostname_len + 9; @@ -881,13 +882,14 @@ static int ssl_prepare_client_hello(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_PROTO_TLS1_3) && \ defined(MBEDTLS_SSL_SESSION_TICKETS) && \ defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) + const char *context_hostname = mbedtls_ssl_get_hostname_pointer(ssl); if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_3 && ssl->handshake->resume) { - int hostname_mismatch = ssl->hostname != NULL || + int hostname_mismatch = context_hostname != NULL || session_negotiate->hostname != NULL; - if (ssl->hostname != NULL && session_negotiate->hostname != NULL) { + if (context_hostname != NULL && session_negotiate->hostname != NULL) { hostname_mismatch = strcmp( - ssl->hostname, session_negotiate->hostname) != 0; + context_hostname, session_negotiate->hostname) != 0; } if (hostname_mismatch) { @@ -898,7 +900,7 @@ static int ssl_prepare_client_hello(mbedtls_ssl_context *ssl) } } else { return mbedtls_ssl_session_set_hostname(session_negotiate, - ssl->hostname); + context_hostname); } #endif /* MBEDTLS_SSL_PROTO_TLS1_3 && MBEDTLS_SSL_SESSION_TICKETS && diff --git a/library/ssl_misc.h b/library/ssl_misc.h index 764e14406..15a08f481 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -2879,6 +2879,18 @@ int mbedtls_ssl_tls13_write_binders_of_pre_shared_key_ext( unsigned char *buf, unsigned char *end); #endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_PSK_ENABLED */ +#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) +/** Get the host name from the SSL context. + * + * \param[in] ssl SSL context + * + * \return The \p hostname pointer from the SSL context. + * \c NULL if mbedtls_ssl_set_hostname() has never been called on + * \p ssl or if it was last called with \p NULL. + */ +const char *mbedtls_ssl_get_hostname_pointer(const mbedtls_ssl_context *ssl); +#endif /* MBEDTLS_SSL_SERVER_NAME_INDICATION */ + #if defined(MBEDTLS_SSL_PROTO_TLS1_3) && \ defined(MBEDTLS_SSL_SESSION_TICKETS) && \ defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) && \ diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 639c23be1..8be0d2d86 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -2747,6 +2747,51 @@ void mbedtls_ssl_conf_groups(mbedtls_ssl_config *conf, } #if defined(MBEDTLS_X509_CRT_PARSE_C) + +/* A magic value for `ssl->hostname` indicating that + * mbedtls_ssl_set_hostname() has been called with `NULL`. + * If mbedtls_ssl_set_hostname() has never been called on `ssl`, then + * `ssl->hostname == NULL`. */ +static const char *const ssl_hostname_skip_cn_verification = ""; + +#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) +/** Whether mbedtls_ssl_set_hostname() has been called. + * + * \param[in] ssl SSL context + * + * \return \c 1 if mbedtls_ssl_set_hostname() has been called on \p ssl + * (including `mbedtls_ssl_set_hostname(ssl, NULL)`), + * otherwise \c 0. + */ +static int mbedtls_ssl_has_set_hostname_been_called( + const mbedtls_ssl_context *ssl) +{ + return ssl->hostname != NULL; +} +#endif + +/* Micro-optimization: don't export this function if it isn't needed outside + * of this source file. */ +#if !defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) +static +#endif +const char *mbedtls_ssl_get_hostname_pointer(const mbedtls_ssl_context *ssl) +{ + if (ssl->hostname == ssl_hostname_skip_cn_verification) { + return NULL; + } + return ssl->hostname; +} + +static void mbedtls_ssl_free_hostname(mbedtls_ssl_context *ssl) +{ + if (ssl->hostname != NULL && + ssl->hostname != ssl_hostname_skip_cn_verification) { + mbedtls_zeroize_and_free(ssl->hostname, strlen(ssl->hostname)); + } + ssl->hostname = NULL; +} + int mbedtls_ssl_set_hostname(mbedtls_ssl_context *ssl, const char *hostname) { /* Initialize to suppress unnecessary compiler warning */ @@ -2764,18 +2809,21 @@ int mbedtls_ssl_set_hostname(mbedtls_ssl_context *ssl, const char *hostname) /* Now it's clear that we will overwrite the old hostname, * so we can free it safely */ - - if (ssl->hostname != NULL) { - mbedtls_zeroize_and_free(ssl->hostname, strlen(ssl->hostname)); - } - - /* Passing NULL as hostname shall clear the old one */ + mbedtls_ssl_free_hostname(ssl); if (hostname == NULL) { - ssl->hostname = NULL; + /* Passing NULL as hostname clears the old one, but leaves a + * special marker to indicate that mbedtls_ssl_set_hostname() + * has been called. */ + /* ssl->hostname should be const, but isn't. We won't actually + * write to the buffer, so it's ok to cast away the const. */ + ssl->hostname = (char *) ssl_hostname_skip_cn_verification; } else { ssl->hostname = mbedtls_calloc(1, hostname_len + 1); if (ssl->hostname == NULL) { + /* mbedtls_ssl_set_hostname() has been called, but unsuccessfully. + * Leave ssl->hostname in the same state as if the function had + * not been called, i.e. a null pointer. */ return MBEDTLS_ERR_SSL_ALLOC_FAILED; } @@ -5548,9 +5596,7 @@ void mbedtls_ssl_free(mbedtls_ssl_context *ssl) } #if defined(MBEDTLS_X509_CRT_PARSE_C) - if (ssl->hostname != NULL) { - mbedtls_zeroize_and_free(ssl->hostname, strlen(ssl->hostname)); - } + mbedtls_ssl_free_hostname(ssl); #endif #if defined(MBEDTLS_SSL_DTLS_HELLO_VERIFY) && defined(MBEDTLS_SSL_SRV_C) @@ -9731,6 +9777,27 @@ int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert, return ret; } +static int get_hostname_for_verification(mbedtls_ssl_context *ssl, + const char **hostname) +{ + if (!mbedtls_ssl_has_set_hostname_been_called(ssl)) { + MBEDTLS_SSL_DEBUG_MSG(1, ("Certificate verification without having set hostname")); +#if !defined(MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME) + if (mbedtls_ssl_conf_get_endpoint(ssl->conf) == MBEDTLS_SSL_IS_CLIENT && + ssl->conf->authmode == MBEDTLS_SSL_VERIFY_REQUIRED) { + return MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME; + } +#endif + } + + *hostname = mbedtls_ssl_get_hostname_pointer(ssl); + if (*hostname == NULL) { + MBEDTLS_SSL_DEBUG_MSG(2, ("Certificate verification without CN verification")); + } + + return 0; +} + int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, int authmode, mbedtls_x509_crt *chain, @@ -9756,7 +9823,13 @@ int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, p_vrfy = ssl->conf->p_vrfy; } - int ret = 0; + const char *hostname = ""; + int ret = get_hostname_for_verification(ssl, &hostname); + if (ret != 0) { + MBEDTLS_SSL_DEBUG_RET(1, "get_hostname_for_verification", ret); + return ret; + } + int have_ca_chain_or_callback = 0; #if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK) if (ssl->conf->f_ca_cb != NULL) { @@ -9769,7 +9842,7 @@ int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, ssl->conf->f_ca_cb, ssl->conf->p_ca_cb, ssl->conf->cert_profile, - ssl->hostname, + hostname, &ssl->session_negotiate->verify_result, f_vrfy, p_vrfy); } else @@ -9796,7 +9869,7 @@ int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, chain, ca_chain, ca_crl, ssl->conf->cert_profile, - ssl->hostname, + hostname, &ssl->session_negotiate->verify_result, f_vrfy, p_vrfy, rs_ctx); } -- Gitee