6823 - Apply dirName constraints to SANs under WOLFSSL_NO_ASN_STRICT

This commit is contained in:
aidan garske
2026-07-15 16:33:55 -07:00
parent 92d5375e77
commit a0cc010217
4 changed files with 125 additions and 24 deletions
+6 -1
View File
@@ -436,7 +436,12 @@ jobs:
{"name": "opensslextra-no-filesystem-no-bio", "minutes": 0.9,
"configure": ["--enable-opensslextra", "--disable-filesystem", "CPPFLAGS=-DNO_BIO"]},
{"name": "no-examples-no-malloc", "minutes": 0.8,
"configure": ["--disable-examples", "CPPFLAGS=-DWOLFSSL_NO_MALLOC"]}
"configure": ["--disable-examples", "CPPFLAGS=-DWOLFSSL_NO_MALLOC"]},
{"comment": "WOLFSSL_NO_ASN_STRICT still enforces the RFC 5280 4.2 and 4.2.1.10 MUSTs",
"name": "no-asn-strict-certs",
"configure": ["--enable-testcert", "--enable-opensslextra", "--enable-certgen",
"--enable-certreq", "--enable-certext", "--enable-ecc",
"CPPFLAGS=-DWOLFSSL_NO_ASN_STRICT -DWOLFSSL_CUSTOM_OID -DHAVE_OID_ENCODING -DWOLFSSL_ALT_NAMES"]}
]
EOF
.github/scripts/parallel-make-check.py \
+111
View File
@@ -24314,6 +24314,51 @@ static word32 build_registeredID_san(byte* out, word32 outSz)
return (word32)sizeof(ridSan);
}
/* Build a SubjectAltName extension value (a SEQUENCE wrapping a single
* directoryName GeneralName) holding the Name "CN=<val7>". */
static word32 build_dirName_san(byte* out, word32 outSz, const char* val7)
{
static const byte prefix[] = {
0x30, 0x16, /* SEQUENCE, 22 */
0xA4, 0x14, /* [4] dirName, 20 */
0x30, 0x12, /* Name SEQUENCE, 18 */
0x31, 0x10, /* RDN SET, 16 */
0x30, 0x0E, /* ATV SEQUENCE, 14 */
0x06, 0x03, 0x55, 0x04, 0x03, /* OID 2.5.4.3 (CN) */
0x0C, 0x07 /* UTF8String, 7 */
};
/* The UTF8String length above is baked in, so val7 must be exactly 7. */
if (outSz < sizeof(prefix) + 7 || XSTRLEN(val7) != 7)
return 0;
XMEMCPY(out, prefix, sizeof(prefix));
XMEMCPY(out + sizeof(prefix), val7, 7);
return (word32)(sizeof(prefix) + 7);
}
/* Build a NameConstraints extension value carrying a single excludedSubtree
* ([1]) for the directoryName "CN=<val7>". */
static word32 build_dirName_nameConstraints(byte* out, word32 outSz,
const char* val7)
{
static const byte nc[] = {
0x30, 0x1A, /* SEQUENCE, 26 */
0xA1, 0x18, /* [1] excluded, 24 */
0x30, 0x16, /* GeneralSubtree, 22 */
0xA4, 0x14, /* [4] dirName, 20 */
0x30, 0x12, /* Name SEQUENCE, 18 */
0x31, 0x10, /* RDN SET, 16 */
0x30, 0x0E, /* ATV SEQUENCE, 14 */
0x06, 0x03, 0x55, 0x04, 0x03, /* OID 2.5.4.3 (CN) */
0x0C, 0x07 /* UTF8String, 7 */
};
/* The UTF8String length above is baked in, so val7 must be exactly 7. */
if (outSz < sizeof(nc) + 7 || XSTRLEN(val7) != 7)
return 0;
XMEMCPY(out, nc, sizeof(nc));
XMEMCPY(out + sizeof(nc), val7, 7);
return (word32)(sizeof(nc) + 7);
}
/* Build a NameConstraints extension value with a single excludedSubtree
* carrying a registeredID GeneralName for OID 1.2.3.4. wolfSSL enforces
* registeredID name constraints by byte-comparing OID bodies, so a leaf
@@ -24600,6 +24645,71 @@ static int test_NameConstraints_OtherName(void)
return EXPECT_RESULT();
}
/* Verifies wolfSSL applies an issuing CA's directoryName nameConstraints to a
* leaf's directoryName SAN entries, not just to the subject field (RFC 5280
* 4.2.1.10: "Restrictions of the form directoryName MUST be applied to the
* subject field ... and to any names of type directoryName in the
* subjectAltName extension"). The leaf subject never matches the constraint,
* so only the SAN can trigger it.
*
* Coverage:
* 1. Critical excluded subtree, leaf dirName SAN matches -> reject
* 2. Critical excluded subtree, leaf dirName SAN differs -> accept
* (positive control: pins the rejection to the matching path)
* 3. Non-critical excluded subtree, leaf SAN matches -> reject
* (excluded is enforced regardless of criticality)
*
* A permitted-subtree case is not included: for directoryName the leaf's
* subject field is always checked against the permitted list as well, so the
* generated leaf's subject would drive the result rather than its SAN.
*/
static int test_NameConstraints_DirName(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_ASN_TEMPLATE) && \
defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
defined(WOLFSSL_ALT_NAMES) && defined(WOLFSSL_CUSTOM_OID) && \
defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
byte sanBlocked[64];
byte sanAllowed[64];
byte ncExcludedBlocked[64];
word32 sanBlockedSz, sanAllowedSz;
word32 ncExcludedBlockedSz;
sanBlockedSz = build_dirName_san(sanBlocked, sizeof(sanBlocked), "blocked");
sanAllowedSz = build_dirName_san(sanAllowed, sizeof(sanAllowed), "allowed");
ncExcludedBlockedSz = build_dirName_nameConstraints(
ncExcludedBlocked, sizeof(ncExcludedBlocked), "blocked");
ExpectIntGT((int)sanBlockedSz, 0);
ExpectIntGT((int)sanAllowedSz, 0);
ExpectIntGT((int)ncExcludedBlockedSz, 0);
/* (1) Critical excluded directoryName matches the leaf's dirName SAN.
* Must be rejected. */
ExpectIntEQ(verify_with_otherName_chain(
ncExcludedBlocked, ncExcludedBlockedSz, 1,
sanBlocked, sanBlockedSz),
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
/* (2) Positive control: same excluded subtree, leaf carries a DIFFERENT
* dirName SAN, so no match and the chain MUST verify. */
ExpectIntEQ(verify_with_otherName_chain(
ncExcludedBlocked, ncExcludedBlockedSz, 1,
sanAllowed, sanAllowedSz),
0);
/* (3) Non-critical excluded subtree, leaf SAN matches: exclusion is
* enforced regardless of criticality. */
ExpectIntEQ(verify_with_otherName_chain(
ncExcludedBlocked, ncExcludedBlockedSz, 0,
sanBlocked, sanBlockedSz),
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_ASN_TEMPLATE) && \
defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
@@ -37030,6 +37140,7 @@ TEST_CASE testCases[] = {
TEST_DECL(test_PathLenSelfIssuedAllowed),
TEST_DECL(test_PathLenNoKeyUsage),
TEST_DECL(test_NameConstraints_OtherName),
TEST_DECL(test_NameConstraints_DirName),
TEST_DECL(test_NameConstraints_DnsUriWildcard),
TEST_DECL(test_NameConstraints_SubtreeMinMax),
TEST_DECL(test_ParseSerial0FixtureMatrix),
-20
View File
@@ -1024,13 +1024,8 @@ int test_wolfSSL_CertManagerNameConstraint2(void)
wolfSSL_X509_sign(x509, priv, EVP_sha256());
#endif
ExpectNotNull((der = wolfSSL_X509_get_der(x509, &derSz)));
#ifndef WOLFSSL_NO_ASN_STRICT
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
#else
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
#endif
/* check that it still fails if one bad altname and one good altname is in
* the certificate */
@@ -1050,13 +1045,8 @@ int test_wolfSSL_CertManagerNameConstraint2(void)
wolfSSL_X509_sign(x509, priv, EVP_sha256());
#endif
ExpectNotNull((der = wolfSSL_X509_get_der(x509, &derSz)));
#ifndef WOLFSSL_NO_ASN_STRICT
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
#else
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
#endif
/* check it fails with switching position of bad altname */
wolfSSL_X509_free(x509);
@@ -1075,13 +1065,8 @@ int test_wolfSSL_CertManagerNameConstraint2(void)
wolfSSL_X509_sign(x509, priv, EVP_sha256());
#endif
ExpectNotNull((der = wolfSSL_X509_get_der(x509, &derSz)));
#ifndef WOLFSSL_NO_ASN_STRICT
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
#else
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
#endif
wolfSSL_CertManagerFree(cm);
wolfSSL_X509_free(x509);
@@ -1110,13 +1095,8 @@ int test_wolfSSL_CertManagerNameConstraint2(void)
wolfSSL_X509_sign(x509, priv, EVP_sha256());
#endif
ExpectNotNull((der = wolfSSL_X509_get_der(x509, &derSz)));
#ifndef WOLFSSL_NO_ASN_STRICT
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
#else
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
#endif
wolfSSL_CertManagerFree(cm);
wolfSSL_X509_free(x509);
wolfSSL_X509_free(ca);
+8 -3
View File
@@ -52,7 +52,14 @@ ASN Options:
* RSA_DECODE_EXTRA: Decodes extra information in RSA public key.
* WOLFSSL_CERT_GEN: Cert generation. Saves extra certificate info in GetName.
* WOLFSSL_NO_ASN_STRICT: Disable strict RFC compliance checks to
restore 3.13.0 behavior.
restore 3.13.0 behavior. It no longer disables these RFC 5280 checks:
duplicate detection on the certificate extensions that use
VERIFY_AND_SET_OID (4.2), rejection of a critical extension whose OID is
unrecognized (4.2, subject to any WC_ASN_UNKNOWN_EXT_CB callback), and
application of directoryName name constraints to subjectAltName entries as
well as the subject (4.2.1.10). It still relaxes the critical check for a
recognized-but-unsupported extension (e.g. certificatePolicies without
WOLFSSL_SEP or WOLFSSL_CERT_EXT) and the CRL duplicate-extension check.
* WOLFSSL_ASN_ALLOW_0_SERIAL: Even if WOLFSSL_NO_ASN_STRICT is not defined,
allow a length=1, but zero value serial number.
* WOLFSSL_NO_OCSP_OPTIONAL_CERTS: Skip optional OCSP certs (responder issuer
@@ -19048,9 +19055,7 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert)
}
break;
case ASN_DIR_TYPE:
#ifndef WOLFSSL_NO_ASN_STRICT
name = cert->altDirNames;
#endif
/* RFC 5280 section 4.2.1.10
"Restrictions of the form directoryName MUST be