Require calling mbedtls_ssl_set_hostname() for security

In a TLS client, when using certificate authentication, the client should
check that the certificate is valid for the server name that the client
expects. Otherwise, in most scenarios, a malicious server can impersonate
another server.

Normally, the application code should call mbedtls_ssl_set_hostname().
However, it's easy to forget. So raise an error if mandatory certificate
authentication is in effect and mbedtls_ssl_set_hostname() has not been
called. Raise the new error code
MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME, for easy
identification.

But don't raise the error if the backward compatibility option
MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME is
enabled.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index 993c69e..a5e5b28 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -9835,6 +9835,12 @@
 {
     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);
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index a88ff3b..633d910 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -6183,13 +6183,28 @@
          -C "x509_verify_cert() returned -" \
          -C "X509 - Certificate verification failed"
 
-run_test "Authentication: hostname unset, client required" \
+requires_config_disabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client required, secure config" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required set_hostname=no debug_level=2" \
+         1 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_enabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client required, historical config" \
          "$P_SRV" \
          "$P_CLI auth_mode=required set_hostname=no debug_level=2" \
          0 \
          -C "does not match with the expected CN" \
          -c "Certificate verification without having set hostname" \
          -c "Certificate verification without CN verification" \
+         -C "get_hostname_for_verification() returned -" \
          -C "x509_verify_cert() returned -" \
          -C "! mbedtls_ssl_handshake returned" \
          -C "X509 - Certificate verification failed"
@@ -6214,24 +6229,53 @@
          -C "x509_verify_cert() returned -" \
          -C "X509 - Certificate verification failed"
 
-run_test "Authentication: hostname unset, client default, server picks cert, 1.2" \
+requires_config_disabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client default, secure config, server picks cert, 1.2" \
+         "$P_SRV force_version=tls12 force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         1 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_disabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED
+run_test "Authentication: hostname unset, client default, secure config, server picks cert, 1.3" \
+         "$P_SRV force_version=tls13 tls13_kex_modes=ephemeral" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         1 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_enabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client default, historical config, server picks cert, 1.2" \
          "$P_SRV force_version=tls12 force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8" \
          "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
          0 \
          -C "does not match with the expected CN" \
          -c "Certificate verification without having set hostname" \
          -c "Certificate verification without CN verification" \
+         -C "get_hostname_for_verification() returned -" \
          -C "x509_verify_cert() returned -" \
          -C "X509 - Certificate verification failed"
 
+requires_config_enabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
 requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED
-run_test "Authentication: hostname unset, client default, server picks cert, 1.3" \
+run_test "Authentication: hostname unset, client default, historical config, server picks cert, 1.3" \
          "$P_SRV force_version=tls13 tls13_kex_modes=ephemeral" \
          "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
          0 \
          -C "does not match with the expected CN" \
          -c "Certificate verification without having set hostname" \
          -c "Certificate verification without CN verification" \
+         -C "get_hostname_for_verification() returned -" \
          -C "x509_verify_cert() returned -" \
          -C "X509 - Certificate verification failed"