Commit e7f3a42c23 for openssl.org

commit e7f3a42c237558214d0ca47b80e0465acd2fe9a7
Author: Bob Beck <beck@openssl.org>
Date:   Thu Aug 20 09:46:32 2026 -0600

    Apply DNS name constraints to the subject CN only when it may match

    Now that the subject DN is not consulted for host checks by default,
    call NAME_CONSTRAINTS_check_CN() on the leaf only under
    X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT - matching the verify logic

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

diff --git a/CHANGES.md b/CHANGES.md
index 6a1523e593..bb66efbfa4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -281,10 +281,14 @@ OpenSSL Releases

  * 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.
+   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.  The subject dn is now checked
+   only when the `X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT` flag is set.
+   Correspondingly, during chain verification DNS name constraints are
+   applied to the subject commonName of the leaf certificate only when
+   that flag is set, rather than whenever the leaf had no DNS subject
+   alternative name.

    *Bob Beck*

diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index 977d6165ca..1163698c73 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -778,27 +778,6 @@ static int check_extensions(X509_STORE_CTX *ctx)
     return 1;
 }

-static int has_san_id(const X509 *x, int gtype)
-{
-    int i;
-    int ret = 0;
-    GENERAL_NAMES *gs = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
-
-    if (gs == NULL)
-        return 0;
-
-    for (i = 0; i < sk_GENERAL_NAME_num(gs); i++) {
-        GENERAL_NAME *g = sk_GENERAL_NAME_value(gs, i);
-
-        if (g->type == gtype) {
-            ret = 1;
-            break;
-        }
-    }
-    GENERAL_NAMES_free(gs);
-    return ret;
-}
-
 /*-
  * Returns -1 on internal error.
  * Sadly, returns 0 also on internal error in ctx->verify_cb().
@@ -895,20 +874,22 @@ static int check_name_constraints(X509_STORE_CTX *ctx)

             if (nc) {
                 int rv = NAME_CONSTRAINTS_check(x, nc);
-                int ret = 1;

-                /* If EE certificate check commonName too */
+                /*
+                 * Apply DNS name constraints to the EE subject commonName
+                 * only when the commonName may be used for host name checks,
+                 * mirroring do_x509_check(): only when the caller opted in
+                 * with X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, and never when
+                 * X509_CHECK_FLAG_NEVER_CHECK_SUBJECT is set.
+                 */
                 if (rv == X509_V_OK && i == 0
                     && (ctx->param->hostflags
                            & X509_CHECK_FLAG_NEVER_CHECK_SUBJECT)
                         == 0
-                    && ((ctx->param->hostflags
-                            & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT)
-                            != 0
-                        || (ret = has_san_id(x, GEN_DNS)) == 0))
+                    && (ctx->param->hostflags
+                           & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT)
+                        != 0)
                     rv = NAME_CONSTRAINTS_check_CN(x, nc);
-                if (ret < 0)
-                    return ret;

                 switch (rv) {
                 case X509_V_OK:
diff --git a/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod b/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod
index bfe6d155f4..6963adcb93 100644
--- a/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod
+++ b/doc/man3/X509_VERIFY_PARAM_set_hostflags.pod
@@ -50,7 +50,10 @@ 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.
+type.  During certificate chain verification this flag additionally
+causes DNS name constraints to be applied to the subject B<commonName>
+of the leaf certificate, since the B<commonName> may then be used as a
+DNS identity; see L<NAME_CONSTRAINTS_check(3)>.

 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
diff --git a/test/recipes/25-test_verify.t b/test/recipes/25-test_verify.t
index 53cc4f9795..040b36383e 100644
--- a/test/recipes/25-test_verify.t
+++ b/test/recipes/25-test_verify.t
@@ -492,8 +492,8 @@ ok(verify("goodcn1-cert", "", ["root-cert"], ["ncca1-cert"], ),
 ok(verify("goodcn2-cert", "", ["root-cert"], ["ncca1-cert"], ),
    "Name Constraints CNs permitted - no SAN extension");

-ok(!verify("badcn1-cert", "", ["root-cert"], ["ncca1-cert"], ),
-   "Name Constraints CNs not permitted");
+ok(verify("badcn1-cert", "", ["root-cert"], ["ncca1-cert"], ),
+   "Name Constraints DNS-like CN not checked by default");

 ok(!verify("badalt1-cert", "", ["root-cert"], ["ncca1-cert"], ),
    "Name Constraints hostname not permitted");
@@ -510,11 +510,11 @@ ok(!verify("badalt4-cert", "", ["root-cert"], ["ncca1-cert"], ),
 ok(!verify("badalt5-cert", "", ["root-cert"], ["ncca1-cert"], ),
    "Name Constraints IP address not permitted");

-ok(!verify("badalt6-cert", "", ["root-cert"], ["ncca1-cert"], ),
-   "Name Constraints CN hostname not permitted");
+ok(verify("badalt6-cert", "", ["root-cert"], ["ncca1-cert"], ),
+   "Name Constraints CN hostname not checked by default");

-ok(!verify("badalt7-cert", "", ["root-cert"], ["ncca1-cert"], ),
-   "Name Constraints CN BMPSTRING hostname not permitted");
+ok(verify("badalt7-cert", "", ["root-cert"], ["ncca1-cert"], ),
+   "Name Constraints CN BMPSTRING hostname not checked by default");

 ok(!verify("badalt8-cert", "", ["root-cert"], ["ncca1-cert", "ncca3-cert"], ),
    "Name constraints nested DNS name not permitted 1");