@@ -22350,6 +22350,310 @@ static int test_MakeCertWith0Ser(void)
2235022350 return EXPECT_RESULT();
2235122351}
2235222352
22353+ #if defined(WOLFSSL_ASN_TEMPLATE) && \
22354+ defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
22355+ defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
22356+ defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
22357+ defined(WOLFSSL_ALT_NAMES) && defined(WOLFSSL_CUSTOM_OID) && \
22358+ defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
22359+
22360+ /* Build a SubjectAltName extension value (a SEQUENCE wrapping a single
22361+ * otherName GeneralName) for the Microsoft UPN OID 1.3.6.1.4.1.311.20.2.3
22362+ * with the given 7-byte UTF8String value. */
22363+ static word32 build_otherName_san(byte* out, word32 outSz, const char* val7)
22364+ {
22365+ static const byte prefix[] = {
22366+ 0x30, 0x19, /* SEQUENCE, 25 */
22367+ 0xA0, 0x17, /* [0] CONSTRUCTED, 23 */
22368+ 0x06, 0x0A, /* OBJECT ID, 10 */
22369+ 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37,
22370+ 0x14, 0x02, 0x03, /* UPN OID */
22371+ 0xA0, 0x09, /* [0] EXPLICIT, 9 */
22372+ 0x0C, 0x07 /* UTF8String, 7 */
22373+ };
22374+ if (outSz < sizeof(prefix) + 7)
22375+ return 0;
22376+ XMEMCPY(out, prefix, sizeof(prefix));
22377+ XMEMCPY(out + sizeof(prefix), val7, 7);
22378+ return (word32)(sizeof(prefix) + 7);
22379+ }
22380+
22381+ /* Build a NameConstraints extension value with a single excludedSubtree
22382+ * carrying a registeredID GeneralName for OID 1.2.3.4. registeredID is a
22383+ * GeneralName form wolfSSL does not enforce, so DecodeSubtree() must
22384+ * record it as 'unsupported' and ConfirmNameConstraints() must fail
22385+ * closed when the extension is critical (RFC 5280 4.2.1.10). */
22386+ static word32 build_registeredID_nameConstraints(byte* out, word32 outSz)
22387+ {
22388+ static const byte ridNc[] = {
22389+ 0x30, 0x09, /* SEQUENCE, 9 */
22390+ 0xA1, 0x07, /* [1] excluded, 7 */
22391+ 0x30, 0x05, /* GeneralSubtree, 5 */
22392+ 0x88, 0x03, /* [8] regId, 3 */
22393+ 0x2A, 0x03, 0x04 /* OID 1.2.3.4 */
22394+ };
22395+ if (outSz < sizeof(ridNc))
22396+ return 0;
22397+ XMEMCPY(out, ridNc, sizeof(ridNc));
22398+ return (word32)sizeof(ridNc);
22399+ }
22400+
22401+ /* Build a NameConstraints extension value carrying a single subtree of
22402+ * the given list type ([0] permitted or [1] excluded) for an otherName
22403+ * UPN whose UTF8 value is the given 7-byte string. */
22404+ static word32 build_otherName_nameConstraints(byte* out, word32 outSz,
22405+ int excluded, const char* val7)
22406+ {
22407+ static const byte common[] = {
22408+ 0x30, 0x1D, /* SEQUENCE, 29 */
22409+ 0x00, 0x1B, /* listTag, 27 (patched) */
22410+ 0x30, 0x19, /* GeneralSubtree, 25 */
22411+ 0xA0, 0x17, /* [0] CONSTRUCTED, 23 */
22412+ 0x06, 0x0A,
22413+ 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37,
22414+ 0x14, 0x02, 0x03,
22415+ 0xA0, 0x09,
22416+ 0x0C, 0x07
22417+ };
22418+ if (outSz < sizeof(common) + 7)
22419+ return 0;
22420+ XMEMCPY(out, common, sizeof(common));
22421+ out[2] = excluded ? 0xA1 : 0xA0; /* listTag */
22422+ XMEMCPY(out + sizeof(common), val7, 7);
22423+ return (word32)(sizeof(common) + 7);
22424+ }
22425+
22426+ /* Build a chain (root -> intermediate -> leaf) where the intermediate
22427+ * carries `nameConstraintsDer` as a (possibly critical) nameConstraints
22428+ * extension and the leaf carries `sanDer` as its SAN. Loads root and
22429+ * intermediate as trusted CAs into a fresh CertManager, parses the leaf
22430+ * with VERIFY, and returns the result code from wc_ParseCert(). */
22431+ static int verify_with_otherName_chain(const byte* nameConstraintsDer,
22432+ word32 nameConstraintsDerSz, int critical,
22433+ const byte* sanDer, word32 sanDerSz)
22434+ {
22435+ Cert cert;
22436+ DecodedCert decodedCert;
22437+ byte rootDer[FOURK_BUF];
22438+ byte icaDer[FOURK_BUF];
22439+ byte leafDer[FOURK_BUF];
22440+ int rootDerSz = 0, icaDerSz = 0, leafDerSz = 0;
22441+ int parseRet = -1;
22442+ WC_RNG rng;
22443+ ecc_key rootKey, icaKey, leafKey;
22444+ WOLFSSL_CERT_MANAGER* cm = NULL;
22445+
22446+ XMEMSET(&rng, 0, sizeof(rng));
22447+ XMEMSET(&rootKey, 0, sizeof(rootKey));
22448+ XMEMSET(&icaKey, 0, sizeof(icaKey));
22449+ XMEMSET(&leafKey, 0, sizeof(leafKey));
22450+
22451+ if (wc_InitRng(&rng) != 0) goto done;
22452+ if (wc_ecc_init(&rootKey) != 0) goto done;
22453+ if (wc_ecc_init(&icaKey) != 0) goto done;
22454+ if (wc_ecc_init(&leafKey) != 0) goto done;
22455+ if (wc_ecc_make_key(&rng, 32, &rootKey) != 0) goto done;
22456+ if (wc_ecc_make_key(&rng, 32, &icaKey) != 0) goto done;
22457+ if (wc_ecc_make_key(&rng, 32, &leafKey) != 0) goto done;
22458+
22459+ /* Self-signed root. */
22460+ if (wc_InitCert(&cert) != 0) goto done;
22461+ (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
22462+ (void)XSTRNCPY(cert.subject.org, "OtherNCRoot", CTC_NAME_SIZE);
22463+ (void)XSTRNCPY(cert.subject.commonName, "OtherNCRoot", CTC_NAME_SIZE);
22464+ cert.selfSigned = 1;
22465+ cert.isCA = 1;
22466+ cert.sigType = CTC_SHA256wECDSA;
22467+ cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN;
22468+ if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey) != 0)
22469+ goto done;
22470+ if (wc_MakeCert(&cert, rootDer, FOURK_BUF, NULL, &rootKey, &rng) < 0)
22471+ goto done;
22472+ rootDerSz = wc_SignCert(cert.bodySz, cert.sigType, rootDer, FOURK_BUF,
22473+ NULL, &rootKey, &rng);
22474+ if (rootDerSz < 0) goto done;
22475+
22476+ /* Intermediate, signed by root, carrying nameConstraints. */
22477+ if (wc_InitCert(&cert) != 0) goto done;
22478+ cert.selfSigned = 0;
22479+ cert.isCA = 1;
22480+ cert.sigType = CTC_SHA256wECDSA;
22481+ cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN;
22482+ (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
22483+ (void)XSTRNCPY(cert.subject.org, "OtherNCICA", CTC_NAME_SIZE);
22484+ (void)XSTRNCPY(cert.subject.commonName, "OtherNCICA", CTC_NAME_SIZE);
22485+ if (wc_SetIssuerBuffer(&cert, rootDer, rootDerSz) != 0) goto done;
22486+ if (wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey) != 0)
22487+ goto done;
22488+ if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey) != 0)
22489+ goto done;
22490+ if (nameConstraintsDer != NULL) {
22491+ /* nameConstraints OID = 2.5.29.30 */
22492+ if (wc_SetCustomExtension(&cert, critical ? 1 : 0, "2.5.29.30",
22493+ nameConstraintsDer, nameConstraintsDerSz) != 0)
22494+ goto done;
22495+ }
22496+ if (wc_MakeCert(&cert, icaDer, FOURK_BUF, NULL, &icaKey, &rng) < 0)
22497+ goto done;
22498+ icaDerSz = wc_SignCert(cert.bodySz, cert.sigType, icaDer, FOURK_BUF,
22499+ NULL, &rootKey, &rng);
22500+ if (icaDerSz < 0) goto done;
22501+
22502+ /* Leaf, signed by intermediate, carrying the otherName SAN. */
22503+ if (wc_InitCert(&cert) != 0) goto done;
22504+ cert.selfSigned = 0;
22505+ cert.isCA = 0;
22506+ cert.sigType = CTC_SHA256wECDSA;
22507+ (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
22508+ (void)XSTRNCPY(cert.subject.org, "OtherNCLeaf", CTC_NAME_SIZE);
22509+ (void)XSTRNCPY(cert.subject.commonName, "OtherNCLeaf", CTC_NAME_SIZE);
22510+ if (wc_SetIssuerBuffer(&cert, icaDer, icaDerSz) != 0) goto done;
22511+ if (wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey) != 0)
22512+ goto done;
22513+ if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &leafKey) != 0)
22514+ goto done;
22515+ if (sanDer != NULL && sanDerSz > 0) {
22516+ if (sanDerSz > sizeof(cert.altNames)) goto done;
22517+ XMEMCPY(cert.altNames, sanDer, sanDerSz);
22518+ cert.altNamesSz = (int)sanDerSz;
22519+ }
22520+ if (wc_MakeCert(&cert, leafDer, FOURK_BUF, NULL, &leafKey, &rng) < 0)
22521+ goto done;
22522+ leafDerSz = wc_SignCert(cert.bodySz, cert.sigType, leafDer, FOURK_BUF,
22523+ NULL, &icaKey, &rng);
22524+ if (leafDerSz < 0) goto done;
22525+
22526+ cm = wolfSSL_CertManagerNew();
22527+ if (cm == NULL) goto done;
22528+ if (wolfSSL_CertManagerLoadCABuffer(cm, rootDer, rootDerSz,
22529+ WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) goto done;
22530+ if (wolfSSL_CertManagerLoadCABuffer(cm, icaDer, icaDerSz,
22531+ WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) goto done;
22532+
22533+ wc_InitDecodedCert(&decodedCert, leafDer, (word32)leafDerSz, NULL);
22534+ parseRet = wc_ParseCert(&decodedCert, CERT_TYPE, VERIFY, cm);
22535+ wc_FreeDecodedCert(&decodedCert);
22536+
22537+ done:
22538+ if (cm != NULL) wolfSSL_CertManagerFree(cm);
22539+ wc_ecc_free(&leafKey);
22540+ wc_ecc_free(&icaKey);
22541+ wc_ecc_free(&rootKey);
22542+ wc_FreeRng(&rng);
22543+ return parseRet;
22544+ }
22545+ #endif
22546+
22547+ /* Verifies wolfSSL enforces an issuing CA's nameConstraints extension on a
22548+ * leaf certificate's otherName SAN (RFC 5280 4.2.1.10). The vulnerability
22549+ * was that ConfirmNameConstraints() ignored ASN_OTHER_TYPE entirely, so a
22550+ * malicious intermediate could issue leaves whose otherName SAN violated
22551+ * its own subtree.
22552+ *
22553+ * Coverage:
22554+ * 1. Critical excluded subtree, leaf SAN matches -> reject
22555+ * 2. Critical excluded subtree, leaf SAN does NOT match -> accept
22556+ * (positive control: distinguishes 'right rule fired' from
22557+ * 'broke everything with otherName')
22558+ * 3. Non-critical excluded subtree, leaf SAN matches -> reject
22559+ * (excluded is enforced regardless of criticality)
22560+ * 4. Critical permitted subtree, leaf SAN matches -> accept
22561+ * 5. Critical permitted subtree, leaf SAN does NOT match -> reject
22562+ * 6. Critical nameConstraints carrying an unsupported form
22563+ * (registeredID), leaf has no relevant SAN -> reject
22564+ * (RFC 5280 4.2.1.10 fail-closed for unprocessed forms)
22565+ * 7. Same as (6) but non-critical -> accept
22566+ */
22567+ static int test_NameConstraints_OtherName(void)
22568+ {
22569+ EXPECT_DECLS;
22570+ #if defined(WOLFSSL_ASN_TEMPLATE) && \
22571+ defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
22572+ defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
22573+ defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
22574+ defined(WOLFSSL_ALT_NAMES) && defined(WOLFSSL_CUSTOM_OID) && \
22575+ defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
22576+ byte sanBlocked[64];
22577+ byte sanAllowed[64];
22578+ byte ncExcludedBlocked[64];
22579+ byte ncPermittedAllowed[64];
22580+ byte ncRegisteredID[16];
22581+ word32 sanBlockedSz, sanAllowedSz;
22582+ word32 ncExcludedBlockedSz, ncPermittedAllowedSz, ncRegisteredIDSz;
22583+
22584+ sanBlockedSz =
22585+ build_otherName_san(sanBlocked, sizeof(sanBlocked), "blocked");
22586+ sanAllowedSz =
22587+ build_otherName_san(sanAllowed, sizeof(sanAllowed), "allowed");
22588+ ncExcludedBlockedSz = build_otherName_nameConstraints(
22589+ ncExcludedBlocked, sizeof(ncExcludedBlocked), 1, "blocked");
22590+ ncPermittedAllowedSz = build_otherName_nameConstraints(
22591+ ncPermittedAllowed, sizeof(ncPermittedAllowed), 0, "allowed");
22592+ ncRegisteredIDSz = build_registeredID_nameConstraints(
22593+ ncRegisteredID, sizeof(ncRegisteredID));
22594+ ExpectIntGT((int)sanBlockedSz, 0);
22595+ ExpectIntGT((int)sanAllowedSz, 0);
22596+ ExpectIntGT((int)ncExcludedBlockedSz, 0);
22597+ ExpectIntGT((int)ncPermittedAllowedSz, 0);
22598+ ExpectIntGT((int)ncRegisteredIDSz, 0);
22599+
22600+ /* (1) Original bypass scenario: critical excluded otherName matches
22601+ * the leaf's otherName SAN. Must be rejected. */
22602+ ExpectIntEQ(verify_with_otherName_chain(
22603+ ncExcludedBlocked, ncExcludedBlockedSz, 1,
22604+ sanBlocked, sanBlockedSz),
22605+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
22606+
22607+ /* (2) Positive control: same critical excluded subtree, but the leaf
22608+ * carries a DIFFERENT otherName value, so byte-comparison says no
22609+ * match and the chain MUST verify. This pins the rejection in (1)
22610+ * to the matching path rather than to a blanket 'reject any
22611+ * otherName under critical'. */
22612+ ExpectIntEQ(verify_with_otherName_chain(
22613+ ncExcludedBlocked, ncExcludedBlockedSz, 1,
22614+ sanAllowed, sanAllowedSz),
22615+ 0);
22616+
22617+ /* (3) Non-critical excluded subtree, leaf SAN matches: exclusion is
22618+ * enforced regardless of criticality. */
22619+ ExpectIntEQ(verify_with_otherName_chain(
22620+ ncExcludedBlocked, ncExcludedBlockedSz, 0,
22621+ sanBlocked, sanBlockedSz),
22622+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
22623+
22624+ /* (4) Critical permitted subtree, leaf SAN inside the permitted set:
22625+ * verification succeeds. */
22626+ ExpectIntEQ(verify_with_otherName_chain(
22627+ ncPermittedAllowed, ncPermittedAllowedSz, 1,
22628+ sanAllowed, sanAllowedSz),
22629+ 0);
22630+
22631+ /* (5) Critical permitted subtree, leaf SAN outside the permitted set:
22632+ * verification rejects. */
22633+ ExpectIntEQ(verify_with_otherName_chain(
22634+ ncPermittedAllowed, ncPermittedAllowedSz, 1,
22635+ sanBlocked, sanBlockedSz),
22636+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
22637+
22638+ /* (6) Critical nameConstraints carrying a GeneralName form wolfSSL
22639+ * does not enforce (registeredID). RFC 5280 4.2.1.10 requires the
22640+ * verifier to either process the constraint or reject; we reject
22641+ * fail-closed. The leaf needs no SAN to exercise this path. */
22642+ ExpectIntEQ(verify_with_otherName_chain(
22643+ ncRegisteredID, ncRegisteredIDSz, 1, NULL, 0),
22644+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
22645+
22646+ /* (7) Same as (6) but non-critical: RFC 5280 only mandates the
22647+ * fail-closed reject when the extension is critical, so a
22648+ * non-critical unsupported constraint form is silently ignored
22649+ * and verification succeeds. */
22650+ ExpectIntEQ(verify_with_otherName_chain(
22651+ ncRegisteredID, ncRegisteredIDSz, 0, NULL, 0),
22652+ 0);
22653+ #endif
22654+ return EXPECT_RESULT();
22655+ }
22656+
2235322657static int test_MakeCertWithCaFalse(void)
2235422658{
2235522659 EXPECT_DECLS;
@@ -37347,6 +37651,7 @@ TEST_CASE testCases[] = {
3734737651 TEST_DECL(test_PathLenSelfIssued),
3734837652 TEST_DECL(test_PathLenSelfIssuedAllowed),
3734937653 TEST_DECL(test_PathLenNoKeyUsage),
37654+ TEST_DECL(test_NameConstraints_OtherName),
3735037655 TEST_DECL(test_MakeCertWith0Ser),
3735137656 TEST_DECL(test_MakeCertWithCaFalse),
3735237657#ifdef WOLFSSL_CERT_SIGN_CB
0 commit comments