Commit 00565bb35c for openssl.org

commit 00565bb35cbaddad3fdd9c2a00d6ae360b7161f5
Author: Bob Beck <beck@openssl.org>
Date:   Thu Jul 16 16:13:38 2026 +0200

    Stop consulting the subject DN by default in X509 name checks

    do_x509_check() now matches the subject commonName (host checks) and
    emailAddress (email checks) only when X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
    is set, rather than whenever no matching subjectAltName is present.

    Update v3nametest to request ALWAYS_CHECK_SUBJECT for its subject-DN cases.

    Fixes: openssl/project#1897

    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Andrew Dinh <andrewd@openssl.org>
    MergeDate: Wed Aug 26 15:21:48 2026
    (Merged from https://github.com/openssl/openssl/pull/31982)

diff --git a/CHANGES.md b/CHANGES.md
index 7e04ce4b4b..6a1523e593 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -279,6 +279,15 @@ OpenSSL Releases

    *Marcel Cornu and Tomasz Kantecki*

+ * X509 certificate verification no longer consults the subject
+   distinguished name by default.  Previously, when a certificate
+   contained no subject alternative name of the type being checked, the
+   subject commonName (for host name checks) or emailAddress (for email
+   checks) was matched instead.  This fallback now happens only when the
+   `X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT` flag is set.
+
+   *Bob Beck*
+
  * Changed the output of the -disabled option for the list command.
    Displaying disabled features, protocols, and algorithms, in relevant sections.
    Disabled features are now generated at configuration time.
diff --git a/crypto/x509/v3_utl.c b/crypto/x509/v3_utl.c
index 4ccfcacadb..70f4bd8e34 100644
--- a/crypto/x509/v3_utl.c
+++ b/crypto/x509/v3_utl.c
@@ -874,7 +874,6 @@ static int do_x509_check(const X509 *x, const char *chk, size_t chklen,
     int i;
     int cnid = NID_undef;
     int alt_type;
-    int san_present = 0;
     int rv = 0;
     equal_fn equal;

@@ -934,15 +933,8 @@ static int do_x509_check(const X509 *x, const char *chk, size_t chklen,
                      *   SmtpUTF8Mailbox is encoded as UTF8String.
                      *
                      * If it is not a UTF8String then that is unexpected, and
-                     * we ignore the invalid SAN (neither set san_present nor
-                     * consider it a candidate for equality).  This does mean
-                     * that the subject CN may be considered, as would be the
-                     * case when the malformed SmtpUtf8Mailbox SAN is instead
-                     * simply absent.
-                     *
-                     * When CN-ID matching is not desirable, applications can
-                     * choose to turn it off, doing so is at this time a best
-                     * practice.
+                     * we ignore the invalid SAN, so it is not considered a
+                     * candidate for equality.
                      */
                     if (othername_nid != NID_id_on_SmtpUTF8Mailbox
                         || gen->d.otherName->value->type != V_ASN1_UTF8STRING)
@@ -968,7 +960,6 @@ static int do_x509_check(const X509 *x, const char *chk, size_t chklen,
                 cstr = gen->d.iPAddress;
                 break;
             }
-            san_present = 1;
             /* Positive on success, negative on error! */
             if ((rv = do_check_string(cstr, alt_type, equal, flags,
                      chk, chklen, peername))
@@ -978,12 +969,17 @@ static int do_x509_check(const X509 *x, const char *chk, size_t chklen,
         GENERAL_NAMES_free(gens);
         if (rv != 0)
             return rv;
-        if (san_present && !(flags & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT))
-            return 0;
     }

-    /* We're done if CN-ID is not pertinent */
-    if (cnid == NID_undef || (flags & X509_CHECK_FLAG_NEVER_CHECK_SUBJECT))
+    /*
+     * The subject DN is not consulted by default: the subject commonName or
+     * emailAddress is matched only when the caller explicitly opts in with
+     * X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, and never when
+     * X509_CHECK_FLAG_NEVER_CHECK_SUBJECT is set.
+     */
+    if (cnid == NID_undef
+        || (flags & X509_CHECK_FLAG_NEVER_CHECK_SUBJECT) != 0
+        || (flags & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT) == 0)
         return 0;

     i = -1;
diff --git a/doc/man3/X509_VERIFY_PARAM_set1_host.pod b/doc/man3/X509_VERIFY_PARAM_set1_host.pod
index 76608a18d6..d21096ad44 100644
--- a/doc/man3/X509_VERIFY_PARAM_set1_host.pod
+++ b/doc/man3/X509_VERIFY_PARAM_set1_host.pod
@@ -277,14 +277,14 @@ checks are performed. Configuring a list for a given name type implicitly
 enables the corresponding check; clearing it (by passing NULL or the
 empty string to the C<set1_> form) disables it.

-Unless suppressed by the host flags, the configured hostnames are also
-matched against the B<commonName> attribute of the certificate's subject
-distinguished name, and the configured RFC 822 email addresses against the
-subject B<emailAddress> attribute. SMTPUTF8 email and IP addresses have no
-subject distinguished name counterpart and are matched only against the
-subject alternative name extension. See L<X509_VERIFY_PARAM_set_hostflags(3)>
-and L<X509_check_host(3)> for the flags that govern whether and when the
-subject distinguished name is consulted.
+By default only the subject alternative name extension is consulted. When
+B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> is set, the configured hostnames
+are also matched against the B<commonName> attribute of the certificate's
+subject distinguished name, and the configured RFC 822 email addresses
+against the subject B<emailAddress> attribute. SMTPUTF8 email and IP
+addresses have no subject distinguished name counterpart and are always
+matched only against the subject alternative name extension. See
+L<X509_VERIFY_PARAM_set_hostflags(3)> and L<X509_check_host(3)>.

 =head1 SEE ALSO

diff --git a/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod b/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod
index 714334d886..bfe6d155f4 100644
--- a/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod
+++ b/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod
@@ -43,21 +43,19 @@ The I<flags> default to 0. They may be set to a bitwise OR of the following:

 =back

-The B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> flag causes the function
-to consider the subject DN even if the certificate contains at least
-one subject alternative name of the right type (DNS name or email
-address as appropriate); the default is to ignore the subject DN
-when at least one corresponding subject alternative names is present.
-
-The B<X509_CHECK_FLAG_NEVER_CHECK_SUBJECT> flag causes the function to never
-consider the subject DN even if the certificate contains no subject alternative
-names of the right type (DNS name or email address as appropriate); the default
-is to use the subject DN when no corresponding subject alternative names are
-present.
-
-If both B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> and
-B<X509_CHECK_FLAG_NEVER_CHECK_SUBJECT> are specified, the latter takes
-precedence and the subject DN is not checked for matching names.
+By default the subject distinguished name is not consulted; matching is
+performed only against the subject alternative name extension.
+
+The B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> flag causes the function to
+also match against the subject DN (the B<commonName> attribute for DNS
+names, the B<emailAddress> attribute for email addresses), whether or not
+the certificate contains a subject alternative name of the corresponding
+type.
+
+The B<X509_CHECK_FLAG_NEVER_CHECK_SUBJECT> flag suppresses matching against
+the subject DN. As that is already the default, this flag has an effect
+only in combination with B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT>, over
+which it takes precedence.

 If set, B<X509_CHECK_FLAG_NO_WILDCARDS> disables wildcard
 expansion.
diff --git a/doc/man3/X509_check_host.pod b/doc/man3/X509_check_host.pod
index 6b095f9582..e43dddbbe5 100644
--- a/doc/man3/X509_check_host.pod
+++ b/doc/man3/X509_check_host.pod
@@ -24,13 +24,14 @@ The validity of the certificate and its trust level has to be checked by
 other means.

 X509_check_host() checks if the certificate Subject Alternative
-Name (SAN) or Subject CommonName (CN) matches the specified hostname,
-which must be encoded in the preferred name syntax described
-in section 3.5 of RFC 1034.  By default, wildcards are supported
+Name (SAN) matches the specified hostname, and, when
+B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> is set, the Subject CommonName
+(CN) as well.  The hostname must be encoded in the preferred name syntax
+described in section 3.5 of RFC 1034.  By default, wildcards are supported
 and they match  only in the left-most label; but they may match
 part of that label with an explicit prefix or suffix.  For example,
 by default, the host B<name> "www.example.com" would match a
-certificate with a SAN or CN value of "*.example.com", "w*.example.com"
+certificate with a SAN value of "*.example.com", "w*.example.com"
 or "*w.example.com".

 Per section 6.4.2 of RFC 6125, B<name> values representing international
@@ -87,20 +88,19 @@ flags:

 =back

-The B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> flag causes the function
-to consider the subject DN even if the certificate contains at least
-one subject alternative name of the right type (DNS name or email
-address as appropriate); the default is to ignore the subject DN
-when at least one corresponding subject alternative names is present.
-
-The B<X509_CHECK_FLAG_NEVER_CHECK_SUBJECT> flag causes the function to never
-consider the subject DN even if the certificate contains no subject alternative
-names of the right type (DNS name or email address as appropriate); the default
-is to use the subject DN when no corresponding subject alternative names are
-present.
-If both B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> and
-B<X509_CHECK_FLAG_NEVER_CHECK_SUBJECT> are specified, the latter takes
-precedence and the subject DN is not checked for matching names.
+By default the subject distinguished name is not consulted; matching is
+performed only against the subject alternative name extension.
+
+The B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> flag causes the function to
+also match against the subject DN (the B<commonName> attribute for DNS
+names, the B<emailAddress> attribute for email addresses), whether or not
+the certificate contains a subject alternative name of the corresponding
+type.
+
+The B<X509_CHECK_FLAG_NEVER_CHECK_SUBJECT> flag suppresses matching against
+the subject DN. As that is already the default, this flag has an effect
+only in combination with B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT>, over
+which it takes precedence.

 If set, B<X509_CHECK_FLAG_NO_WILDCARDS> disables wildcard
 expansion; this only applies to B<X509_check_host>.
diff --git a/test/v3nametest.c b/test/v3nametest.c
index 82777e6086..bfdee4d309 100644
--- a/test/v3nametest.c
+++ b/test/v3nametest.c
@@ -212,17 +212,18 @@ struct set_name_fn {
     const char *name;
     int host;
     int email;
+    int subject; /* name is in the subject DN, so needs ALWAYS_CHECK_SUBJECT */
 };

 #if !defined(OPENSSL_NO_DEPRECATED_4_1)
 OSSL_BEGIN_ALLOW_DEPRECATED
 static const struct set_name_fn name_fns[] = {
-    { set_cn1, "set CN", 1, 0 },
-    { set_email1, "set emailAddress", 0, 1 },
-    { set_altname_dns, "set dnsName", 1, 0 },
-    { set_altname_dns2, "set dnsName", 1, 0 },
-    { set_altname_email, "set rfc822Name", 0, 1 },
-    { set_altname_email2, "set rfc822Name", 0, 1 },
+    { set_cn1, "set CN", 1, 0, 1 },
+    { set_email1, "set emailAddress", 0, 1, 1 },
+    { set_altname_dns, "set dnsName", 1, 0, 0 },
+    { set_altname_dns2, "set dnsName", 1, 0, 0 },
+    { set_altname_email, "set rfc822Name", 0, 1, 0 },
+    { set_altname_email2, "set rfc822Name", 0, 1, 0 },
 };

 static X509 *make_cert(void)
@@ -259,6 +260,7 @@ static int run_cert(X509 *crt, const char *nameincert,
 {
     const char *const *pname = names;
     int failed = 0;
+    unsigned int subj = fn->subject ? X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT : 0;

     for (; *pname != NULL; ++pname) {
         int samename = OPENSSL_strcasecmp(nameincert, *pname) == 0;
@@ -271,7 +273,7 @@ static int run_cert(X509 *crt, const char *nameincert,
         memcpy(name, *pname, namelen + 1);

         match = -1;
-        if (!TEST_int_ge(ret = X509_check_host(crt, name, namelen, 0, NULL),
+        if (!TEST_int_ge(ret = X509_check_host(crt, name, namelen, subj, NULL),
                 0)) {
             failed = 1;
         } else if (fn->host) {
@@ -286,7 +288,7 @@ static int run_cert(X509 *crt, const char *nameincert,

         match = -1;
         if (!TEST_int_ge(ret = X509_check_host(crt, name, namelen,
-                             X509_CHECK_FLAG_NO_WILDCARDS,
+                             X509_CHECK_FLAG_NO_WILDCARDS | subj,
                              NULL),
                 0)) {
             failed = 1;
@@ -302,7 +304,7 @@ static int run_cert(X509 *crt, const char *nameincert,
             failed = 1;

         match = -1;
-        ret = X509_check_email(crt, name, namelen, 0);
+        ret = X509_check_email(crt, name, namelen, subj);
         if (fn->email) {
             if (ret && !samename)
                 match = 1;
@@ -395,9 +397,9 @@ static int test_long_names(void)
  * The subject commonName / emailAddress is consulted during verification
  * only when X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT is set, and never when
  * X509_CHECK_FLAG_NEVER_CHECK_SUBJECT is set -- whether or not a subject
- * alternative name of the corresponding type is present. (The default,
- * flags == 0, is exercised by the "set CN" / "set emailAddress" entries in
- * the main matrix.)
+ * alternative name of the corresponding type is present. In particular the
+ * default, flags == 0, matches neither, even when the certificate has no
+ * subject alternative name at all.
  */
 static int test_check_subject_flags(void)
 {
@@ -407,6 +409,7 @@ static int test_check_subject_flags(void)
     /* Subject commonName, no SAN. */
     if (!TEST_ptr(crt = make_cert())
         || !TEST_true(set_cn1(crt, "example.com"))
+        || !TEST_int_eq(X509_check_host(crt, "example.com", 0, 0, NULL), 0)
         || !TEST_int_eq(X509_check_host(crt, "example.com", 0,
                             X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, NULL),
             1)
@@ -441,6 +444,7 @@ static int test_check_subject_flags(void)
     /* Subject emailAddress, no SAN. */
     if (!TEST_ptr(crt = make_cert())
         || !TEST_true(set_email1(crt, "user@example.com"))
+        || !TEST_int_eq(X509_check_email(crt, "user@example.com", 0, 0), 0)
         || !TEST_int_eq(X509_check_email(crt, "user@example.com", 0,
                             X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT),
             1)