From 9a0a01ae87476462d3b58a7627bdb539da670ea9 Mon Sep 17 00:00:00 2001 From: Ruby Martin Date: Thu, 2 Jul 2026 11:14:26 -0600 Subject: [PATCH 1/2] Apply Subject CN dNSName name-constraint check unless a dNSName SAN is present. A non-dNSName SAN will not skip the CN dNSName name-constraint check now --- wolfcrypt/src/asn.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index c359aa6e81..b2b62da280 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -18748,20 +18748,30 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) byte nameType = nameTypes[i]; DNS_entry* name = NULL; DNS_entry subjectDnsName; /* temporary node used for subject name */ + DNS_entry* dnsSan = NULL; /* scan for a dNSName SAN */ XMEMSET(&subjectDnsName, 0, sizeof(DNS_entry)); switch (nameType) { case ASN_DNS_TYPE: name = cert->altNames; - /* Apply DNS constraints to leaf Subject CN when no SAN - * (legacy hostname-in-CN). Skipped for CAs. */ - if (cert->subjectCN != NULL && cert->altNames == NULL && - !cert->isCA) { - subjectDnsName.next = NULL; - subjectDnsName.type = ASN_DNS_TYPE; - subjectDnsName.len = cert->subjectCNLen; - subjectDnsName.name = cert->subjectCN; + /* Apply DNS name constraints to the Subject CN as a legacy + * hostname-in-CN fallback only when the cert presents no + * dNSName SAN (RFC 6125 6.4.4). A SAN of another type + * (iPAddress/registeredID/otherName/...) must NOT suppress + * this check. Skip for CA certs. */ + if (cert->subjectCN != NULL && !cert->isCA) { + for (dnsSan = cert->altNames; dnsSan != NULL; + dnsSan = dnsSan->next) { + if (dnsSan->type == ASN_DNS_TYPE) + break; + } + if (dnsSan == NULL) { /* no dNSName SAN present */ + subjectDnsName.next = NULL; + subjectDnsName.type = ASN_DNS_TYPE; + subjectDnsName.len = cert->subjectCNLen; + subjectDnsName.name = cert->subjectCN; + } } break; case ASN_IP_TYPE: From bd4c11aec4e50ea8a68b72f945fad2f1de0a1cd4 Mon Sep 17 00:00:00 2001 From: Ruby Martin Date: Thu, 2 Jul 2026 11:43:19 -0600 Subject: [PATCH 2/2] Expand test_wolfSSL_CertManagerNameConstraint_DNS_CN with non-dNSName SAN cases update comments --- tests/api/test_certman.c | 126 +++++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 13 deletions(-) diff --git a/tests/api/test_certman.c b/tests/api/test_certman.c index 2bcb91b68d..419f16c673 100644 --- a/tests/api/test_certman.c +++ b/tests/api/test_certman.c @@ -1591,16 +1591,20 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void) !defined(NO_WOLFSSL_CM_VERIFY) && !defined(NO_RSA) && \ defined(OPENSSL_EXTRA) && defined(WOLFSSL_CERT_GEN) && \ defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_ALT_NAMES) && \ - !defined(NO_SHA256) - /* Test that DNS name constraints are enforced against the Subject CN - * when no SAN extension is present. The CA cert (cert-ext-ncdns.der) - * permits only DNS:wolfssl.com and DNS:example.com. A leaf cert with - * CN=evil.attacker.com and no SAN should be REJECTED. */ + !defined(NO_SHA256) && !defined(IGNORE_NAME_CONSTRAINTS) + /* CA (cert-ext-ncdns.der) permits DNS:wolfssl.com and DNS:example.com. The + * Subject CN is checked against these dNSName constraints as a fallback + * only when the leaf has no dNSName SAN (RFC 6125 6.4.4); a non-dNSName + * SAN must not suppress that check. */ WOLFSSL_CERT_MANAGER* cm = NULL; WOLFSSL_EVP_PKEY *priv = NULL; WOLFSSL_X509_NAME* name = NULL; const char* ca_cert = "./certs/test/cert-ext-ncdns.der"; const char* server_cert = "./certs/test/server-goodcn.pem"; + /* non-dNSName SAN values. The CA declares no registeredID or iPAddress + * constraint, so these names are themselves unconstrained. */ + static const byte rid_dummy[] = { 0x2A, 0x03, 0x04 }; /* OID 1.2.3.4 */ + static const byte ip_dummy[] = { 203, 0, 113, 7 }; byte *der = NULL; int derSz; @@ -1619,7 +1623,7 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void) ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, derSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - /* Sanity check: cert with SAN=evil.attacker.com is correctly rejected */ + /* Negative case: leaf with an out-of-scope dNSName SAN. */ ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, WOLFSSL_FILETYPE_PEM)); ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); @@ -1645,9 +1649,33 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void) wolfSSL_X509_free(x509); x509 = NULL; - /* NOW the actual vulnerability test: cert with CN=evil.attacker.com - * but NO SAN. The DNS name constraint should still reject this, since - * wolfSSL's hostname verification falls back to CN when no SAN exists. */ + /* Positive case: leaf with an out-of-scope CN but an in-scope dNSName + * SAN; the dNSName SAN suppresses the CN fallback, so it is accepted. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + name = NULL; + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", MBSTRING_UTF8, + (byte*)"US", 2, -1, 0), SSL_SUCCESS); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)"evil.attacker.com", 17, -1, 0), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + ExpectIntEQ(wolfSSL_X509_add_altname(x509, "wolfssl.com", + ASN_DNS_TYPE), WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + wolfSSL_X509_free(x509); + x509 = NULL; + + /* Negative case: leaf with an out-of-scope CN and no SAN; the CN is + * checked against the DNS constraint as a fallback. */ ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, WOLFSSL_FILETYPE_PEM)); ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); @@ -1664,17 +1692,15 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void) X509_NAME_free(name); name = NULL; - /* Do NOT add any SAN this is the bypass vector */ + /* No SAN added; CN=evil.attacker.com violates the DNS constraint. */ ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); - /* Should be ASN_NAME_INVALID_E because CN violates the constraint */ ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); wolfSSL_X509_free(x509); x509 = NULL; - /* Positive test: CN matches a permitted name (wolfssl.com) and no SAN is - * present. The CN fallback should accept this cert. */ + /* Positive case: leaf with an in-scope CN and no SAN. */ ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, WOLFSSL_FILETYPE_PEM)); ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); @@ -1694,6 +1720,80 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void) /* No SAN added; CN=wolfssl.com matches the permitted DNS constraint. */ ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + wolfSSL_X509_free(x509); + x509 = NULL; + + /* Negative case: leaf with an out-of-scope CN and a registeredID SAN; + * a non-dNSName SAN must not suppress the CN check. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + name = NULL; + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", MBSTRING_UTF8, + (byte*)"US", 2, -1, 0), SSL_SUCCESS); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)"evil.attacker.com", 17, -1, 0), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + ExpectIntEQ(wolfSSL_X509_add_altname_ex(x509, (const char*)rid_dummy, + sizeof(rid_dummy), ASN_RID_TYPE), WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + wolfSSL_X509_free(x509); + x509 = NULL; + + /* Negative case: leaf with an out-of-scope CN and an iPAddress SAN. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + name = NULL; + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", MBSTRING_UTF8, + (byte*)"US", 2, -1, 0), SSL_SUCCESS); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)"evil.attacker.com", 17, -1, 0), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + ExpectIntEQ(wolfSSL_X509_add_altname_ex(x509, (const char*)ip_dummy, + sizeof(ip_dummy), ASN_IP_TYPE), WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + wolfSSL_X509_free(x509); + x509 = NULL; + + /* Positive case: leaf with an in-scope CN and a registeredID SAN; the + * check must not over-reject. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + name = NULL; + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", MBSTRING_UTF8, + (byte*)"US", 2, -1, 0), SSL_SUCCESS); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)"wolfssl.com", 11, -1, 0), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + ExpectIntEQ(wolfSSL_X509_add_altname_ex(x509, (const char*)rid_dummy, + sizeof(rid_dummy), ASN_RID_TYPE), WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);