Commit 5391a84280 for bind

commit 5391a84280693a4f7c4fa5d819643ff27e0dc966
Author: Michal Nowak <mnowak@isc.org>
Date:   Fri Sep 11 08:58:12 2026 +0000

    Require SubjectAltName for all TLS server certificates

    When verifying a server hostname, DoT already ignores the Subject
    Common Name, as RFC 8310 requires, but DoH still fell back to it for
    certificates without SubjectAltName.  RFC 9525 has since retired the
    Common Name for all TLS connections, overreaching to HTTPS.

    OpenSSL 4.1 stops consulting it by default, so "dig +https" already
    rejected such certificates there while still accepting them with older
    OpenSSL releases.

    Match the hostname against SubjectAltName only for both transports,
    so the outcome no longer depends on the OpenSSL version, and drop the
    parameter that carried the distinction.  named is unaffected: its
    outgoing TLS is DoT and was already strict.

    Assisted-by: Claude:claude-fable-5-1

diff --git a/bin/dig/dig.rst b/bin/dig/dig.rst
index 09e767bd74..636a03d198 100644
--- a/bin/dig/dig.rst
+++ b/bin/dig/dig.rst
@@ -718,6 +718,9 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to
    This option makes :program:`dig` use the provided hostname during remote
    server TLS certificate verification. Otherwise, the DNS server name
    is used. This option has no effect if :option:`+tls-ca` is not specified.
+   The name is matched against the Subject Alternative Name extension of
+   the server certificate only; the Common Name in the Subject field is
+   not consulted, for DNS over HTTPS as well as for DNS over TLS.

 .. option:: +trace, +notrace

diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c
index 030a88e9a9..2f73bdf436 100644
--- a/bin/dig/dighost.c
+++ b/bin/dig/dighost.c
@@ -2746,7 +2746,6 @@ get_create_tls_context(dig_query_t *query, const bool is_https,
 					: AF_INET;
 	isc_tlsctx_cache_transport_t transport =
 		is_https ? isc_tlsctx_cache_https : isc_tlsctx_cache_tls;
-	const bool hostname_ignore_subject = !is_https;
 	isc_tlsctx_client_session_cache_t *sess_cache = NULL,
 					  *found_sess_cache = NULL;

@@ -2774,15 +2773,8 @@ get_create_tls_context(dig_query_t *query, const bool is_https,

 		if (store != NULL) {
 			const char *hostname = get_tls_sni_hostname(query);
-			/*
-			 * According to RFC 8310, Subject field MUST NOT be
-			 * inspected when verifying hostname for DoT. Only
-			 * SubjectAltName must be checked. That is NOT the case
-			 * for HTTPS.
-			 */
 			CHECK(isc_tlsctx_enable_peer_verification(
-				ctx, false, store, hostname,
-				hostname_ignore_subject));
+				ctx, false, store, hostname));
 		}

 		if (query->lookup->tls_key_file_set &&
diff --git a/bin/tests/system/doth/tests.sh b/bin/tests/system/doth/tests.sh
index a22ec23c93..b744d248bd 100644
--- a/bin/tests/system/doth/tests.sh
+++ b/bin/tests/system/doth/tests.sh
@@ -26,21 +26,14 @@ OPENSSL_VERSION=$("$PYTHON" "$TOP_SRCDIR/bin/tests/system/doth/get_openssl_versi
 OPENSSL_VERSION_MAJOR=$(echo "$OPENSSL_VERSION" | cut -d ' ' -f 1)
 OPENSSL_VERSION_MINOR=$(echo "$OPENSSL_VERSION" | cut -d ' ' -f 2)

-# According to the RFC 8310, Section 8.1, Subject field MUST
-# NOT be inspected when verifying a hostname when using
-# DoT. Only SubjectAltName must be checked instead. That is
-# not the case for HTTPS, though.
-
-# Unfortunately, some quite old versions of OpenSSL (< 1.1.1)
-# might lack the functionality to implement that. It should
-# have very little real-world consequences, as most of the
-# production-ready certificates issued by real CAs will have
-# SubjectAltName set. In such a case, the Subject field is
-# ignored.
+# A server hostname is matched against SubjectAltName only; the Subject
+# Common Name is never consulted, for DoT (RFC 8310, Section 8.1) and
+# for DoH (RFC 9525) alike, so a certificate without SubjectAltName must
+# be rejected on both transports.
 #
-# On the platforms with too old TLS versions, e.g. RedHat 7, we should
-# ignore the tests checking the correct handling of absence of
-# SubjectAltName.
+# OpenSSL releases older than 1.1 lack the flag that enforces this, so
+# the checks for a missing SubjectAltName are skipped on such platforms
+# (e.g. RHEL 7).
 if [ $OPENSSL_VERSION_MAJOR -gt 1 ]; then
   run_san_tests=1
 elif [ $OPENSSL_VERSION_MAJOR -eq 1 ] && [ $OPENSSL_VERSION_MINOR -ge 1 ]; then
@@ -709,13 +702,13 @@ if [ -n "$run_san_tests" ]; then
   status=$((status + ret))
 fi

-# SubjectAltName is not required for HTTPS. Having a properly set
-# Common Name in the Subject field is enough.
+# The Subject Common Name is not consulted for DoH either (RFC 9525), so
+# a certificate without SubjectAltName must be rejected here as well.
 n=$((n + 1))
-echo_i "checking DoH query (when SubjectAltName is not set) ($n)"
+echo_i "checking DoH query (with TLS verification enabled when SubjectAltName is not set, failure expected) ($n)"
 ret=0
 dig_with_https_opts +tls-ca="$ca_file" +tls-hostname="srv01.crt02-no-san.example.com" -p "${EXTRAPORT3}" +comm @10.53.0.1 . SOA >dig.out.test$n || ret=1
-grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1
+grep "$msg_peer_verification_failed" dig.out.test$n >/dev/null || ret=1
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))

diff --git a/lib/dns/transport.c b/lib/dns/transport.c
index 398e0506f3..64ea0b5ac1 100644
--- a/lib/dns/transport.c
+++ b/lib/dns/transport.c
@@ -429,7 +429,6 @@ dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
 			dns_transport_get_always_verify_remote(transport);
 		char peer_addr_str[INET6_ADDRSTRLEN] = { 0 };
 		isc_netaddr_t peer_netaddr = { 0 };
-		bool hostname_ignore_subject;

 		/*
 		 * So, no context exists. Let's create one using the
@@ -490,15 +489,8 @@ dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
 				hostname = peer_addr_str;
 			}

-			/*
-			 * According to RFC 8310, Subject field MUST NOT
-			 * be inspected when verifying hostname for DoT.
-			 * Only SubjectAltName must be checked.
-			 */
-			hostname_ignore_subject = true;
 			CHECK(isc_tlsctx_enable_peer_verification(
-				tlsctx, false, store, hostname,
-				hostname_ignore_subject));
+				tlsctx, false, store, hostname));

 			/*
 			 * Let's load client certificate and enable
diff --git a/lib/isc/include/isc/tls.h b/lib/isc/include/isc/tls.h
index d824da0c6b..cbe21c828e 100644
--- a/lib/isc/include/isc/tls.h
+++ b/lib/isc/include/isc/tls.h
@@ -244,15 +244,22 @@ isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *ctx);
 isc_result_t
 isc_tlsctx_enable_peer_verification(isc_tlsctx_t *ctx, const bool is_server,
 				    isc_tls_cert_store_t *store,
-				    const char		 *hostname,
-				    bool hostname_ignore_subject);
-/*%<
- * Enable peer certificate and, optionally, hostname (for client contexts)
- * verification.
- *
- * Requires:
- *\li	'ctx' is not NULL;
- *\li	'store' is not NULL.
+				    const char		 *hostname);
+/**<
+ * \brief
+ * Enable peer certificate and, optionally, hostname verification for client
+ * contexts.
+ *
+ * \note
+ * Per RFC 9525, the hostname is only matches against the SubjectAltName
+ * extension.
+ *
+ * \par Requires:
+ * \li `ctx != NULL`
+ * \li `store != NULL`
+ *
+ * \retval ISC_R_SUCCESS on success
+ * \retval ISC_R_FAILURE on opaque OpenSSL failure
  */

 isc_result_t
diff --git a/lib/isc/tls.c b/lib/isc/tls.c
index 07167a0fb7..5058aa1146 100644
--- a/lib/isc/tls.c
+++ b/lib/isc/tls.c
@@ -734,8 +734,7 @@ isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *tls) {
 isc_result_t
 isc_tlsctx_enable_peer_verification(isc_tlsctx_t *tlsctx, const bool is_server,
 				    isc_tls_cert_store_t *store,
-				    const char *hostname,
-				    bool hostname_ignore_subject) {
+				    const char *hostname) {
 	int ret = 0;
 	REQUIRE(tlsctx != NULL);
 	REQUIRE(store != NULL);
@@ -761,26 +760,18 @@ isc_tlsctx_enable_peer_verification(isc_tlsctx_t *tlsctx, const bool is_server,
 			return ISC_R_FAILURE;
 		}

-#ifdef X509_CHECK_FLAG_NEVER_CHECK_SUBJECT
 		/*
-		 * According to the RFC 8310, Section 8.1, Subject field MUST
-		 * NOT be inspected when verifying a hostname when using
-		 * DoT. Only SubjectAltName must be checked instead. That is
-		 * not the case for HTTPS, though.
+		 * Match the hostname against SubjectAltName only and never
+		 * fall back to the Subject Common Name.  RFC 8310, Section
+		 * 8.1, requires this for DoT, RFC 9525 has since retired the
+		 * Common Name for all TLS connections, overreaching to DoH as
+		 * well.
 		 *
-		 * Unfortunately, some quite old versions of OpenSSL (< 1.1.1)
-		 * might lack the functionality to implement that. It should
-		 * have very little real-world consequences, as most of the
-		 * production-ready certificates issued by real CAs will have
-		 * SubjectAltName set. In such a case, the Subject field is
-		 * ignored.
+		 * OpenSSL 4.1 stops consulting CN by default; setting the flag
+		 * explicitly makes the outcome the same on every supported
+		 * OpenSSL version.
 		 */
-		if (hostname_ignore_subject) {
-			hostflags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;
-		}
-#else
-		UNUSED(hostname_ignore_subject);
-#endif
+		hostflags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;
 		X509_VERIFY_PARAM_set_hostflags(param, hostflags);
 	}

diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c
index 763f2bc3d1..f78996de70 100644
--- a/lib/ns/listenlist.c
+++ b/lib/ns/listenlist.c
@@ -93,7 +93,7 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl,
 				}

 				result = isc_tlsctx_enable_peer_verification(
-					sslctx, true, store, NULL, false);
+					sslctx, true, store, NULL);
 				if (result != ISC_R_SUCCESS) {
 					goto tls_error;
 				}