From 6fdd0de0caaaa0af67ae54fa5dd70943224515ae Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 28 Apr 2026 11:05:37 -0500 Subject: [PATCH] Fix handling of otherName in ConfirmNameConstraints --- tests/api.c | 257 ++++++++++++++++++++++++++++++++++++++++ wolfcrypt/src/asn.c | 139 ++++++++++++++++++++-- wolfssl/wolfcrypt/asn.h | 16 +++ 3 files changed, 399 insertions(+), 13 deletions(-) diff --git a/tests/api.c b/tests/api.c index 05a7688d7f..c30ae094bf 100644 --- a/tests/api.c +++ b/tests/api.c @@ -22193,6 +22193,262 @@ static int test_MakeCertWith0Ser(void) return EXPECT_RESULT(); } +#if 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) + +/* Build a SubjectAltName extension value (a SEQUENCE wrapping a single + * otherName GeneralName) for the Microsoft UPN OID 1.3.6.1.4.1.311.20.2.3 + * with the given 7-byte UTF8String value. */ +static word32 build_otherName_san(byte* out, word32 outSz, const char* val7) +{ + static const byte prefix[] = { + 0x30, 0x19, /* SEQUENCE, 25 */ + 0xA0, 0x17, /* [0] CONSTRUCTED, 23 */ + 0x06, 0x0A, /* OBJECT ID, 10 */ + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, + 0x14, 0x02, 0x03, /* UPN OID */ + 0xA0, 0x09, /* [0] EXPLICIT, 9 */ + 0x0C, 0x07 /* UTF8String, 7 */ + }; + if (outSz < sizeof(prefix) + 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 subtree of + * the given list type ([0] permitted or [1] excluded) for an otherName + * UPN whose UTF8 value is the given 7-byte string. */ +static word32 build_otherName_nameConstraints(byte* out, word32 outSz, + int excluded, const char* val7) +{ + static const byte common[] = { + 0x30, 0x1D, /* SEQUENCE, 29 */ + 0x00, 0x1B, /* listTag, 27 (patched) */ + 0x30, 0x19, /* GeneralSubtree, 25 */ + 0xA0, 0x17, /* [0] CONSTRUCTED, 23 */ + 0x06, 0x0A, + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, + 0x14, 0x02, 0x03, + 0xA0, 0x09, + 0x0C, 0x07 + }; + if (outSz < sizeof(common) + 7) + return 0; + XMEMCPY(out, common, sizeof(common)); + out[2] = excluded ? 0xA1 : 0xA0; /* listTag */ + XMEMCPY(out + sizeof(common), val7, 7); + return (word32)(sizeof(common) + 7); +} + +/* Build a chain (root -> intermediate -> leaf) where the intermediate + * carries `nameConstraintsDer` as a (possibly critical) nameConstraints + * extension and the leaf carries `sanDer` as its SAN. Loads root and + * intermediate as trusted CAs into a fresh CertManager, parses the leaf + * with VERIFY, and returns the result code from wc_ParseCert(). */ +static int verify_with_otherName_chain(const byte* nameConstraintsDer, + word32 nameConstraintsDerSz, int critical, + const byte* sanDer, word32 sanDerSz) +{ + Cert cert; + DecodedCert decodedCert; + byte rootDer[FOURK_BUF]; + byte icaDer[FOURK_BUF]; + byte leafDer[FOURK_BUF]; + int rootDerSz = 0, icaDerSz = 0, leafDerSz = 0; + int parseRet = -1; + WC_RNG rng; + ecc_key rootKey, icaKey, leafKey; + WOLFSSL_CERT_MANAGER* cm = NULL; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&rootKey, 0, sizeof(rootKey)); + XMEMSET(&icaKey, 0, sizeof(icaKey)); + XMEMSET(&leafKey, 0, sizeof(leafKey)); + + if (wc_InitRng(&rng) != 0) goto done; + if (wc_ecc_init(&rootKey) != 0) goto done; + if (wc_ecc_init(&icaKey) != 0) goto done; + if (wc_ecc_init(&leafKey) != 0) goto done; + if (wc_ecc_make_key(&rng, 32, &rootKey) != 0) goto done; + if (wc_ecc_make_key(&rng, 32, &icaKey) != 0) goto done; + if (wc_ecc_make_key(&rng, 32, &leafKey) != 0) goto done; + + /* Self-signed root. */ + if (wc_InitCert(&cert) != 0) goto done; + (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.org, "OtherNCRoot", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.commonName, "OtherNCRoot", CTC_NAME_SIZE); + cert.selfSigned = 1; + cert.isCA = 1; + cert.sigType = CTC_SHA256wECDSA; + cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN; + if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey) != 0) + goto done; + if (wc_MakeCert(&cert, rootDer, FOURK_BUF, NULL, &rootKey, &rng) < 0) + goto done; + rootDerSz = wc_SignCert(cert.bodySz, cert.sigType, rootDer, FOURK_BUF, + NULL, &rootKey, &rng); + if (rootDerSz < 0) goto done; + + /* Intermediate, signed by root, carrying nameConstraints. */ + if (wc_InitCert(&cert) != 0) goto done; + cert.selfSigned = 0; + cert.isCA = 1; + cert.sigType = CTC_SHA256wECDSA; + cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN; + (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.org, "OtherNCICA", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.commonName, "OtherNCICA", CTC_NAME_SIZE); + if (wc_SetIssuerBuffer(&cert, rootDer, rootDerSz) != 0) goto done; + if (wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey) != 0) + goto done; + if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey) != 0) + goto done; + if (nameConstraintsDer != NULL) { + /* nameConstraints OID = 2.5.29.30 */ + if (wc_SetCustomExtension(&cert, critical ? 1 : 0, "2.5.29.30", + nameConstraintsDer, nameConstraintsDerSz) != 0) + goto done; + } + if (wc_MakeCert(&cert, icaDer, FOURK_BUF, NULL, &icaKey, &rng) < 0) + goto done; + icaDerSz = wc_SignCert(cert.bodySz, cert.sigType, icaDer, FOURK_BUF, + NULL, &rootKey, &rng); + if (icaDerSz < 0) goto done; + + /* Leaf, signed by intermediate, carrying the otherName SAN. */ + if (wc_InitCert(&cert) != 0) goto done; + cert.selfSigned = 0; + cert.isCA = 0; + cert.sigType = CTC_SHA256wECDSA; + (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.org, "OtherNCLeaf", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.commonName, "OtherNCLeaf", CTC_NAME_SIZE); + if (wc_SetIssuerBuffer(&cert, icaDer, icaDerSz) != 0) goto done; + if (wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey) != 0) + goto done; + if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &leafKey) != 0) + goto done; + if (sanDerSz > sizeof(cert.altNames)) goto done; + XMEMCPY(cert.altNames, sanDer, sanDerSz); + cert.altNamesSz = (int)sanDerSz; + if (wc_MakeCert(&cert, leafDer, FOURK_BUF, NULL, &leafKey, &rng) < 0) + goto done; + leafDerSz = wc_SignCert(cert.bodySz, cert.sigType, leafDer, FOURK_BUF, + NULL, &icaKey, &rng); + if (leafDerSz < 0) goto done; + + cm = wolfSSL_CertManagerNew(); + if (cm == NULL) goto done; + if (wolfSSL_CertManagerLoadCABuffer(cm, rootDer, rootDerSz, + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) goto done; + if (wolfSSL_CertManagerLoadCABuffer(cm, icaDer, icaDerSz, + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) goto done; + + wc_InitDecodedCert(&decodedCert, leafDer, (word32)leafDerSz, NULL); + parseRet = wc_ParseCert(&decodedCert, CERT_TYPE, VERIFY, cm); + wc_FreeDecodedCert(&decodedCert); + +done: + if (cm != NULL) wolfSSL_CertManagerFree(cm); + wc_ecc_free(&leafKey); + wc_ecc_free(&icaKey); + wc_ecc_free(&rootKey); + wc_FreeRng(&rng); + return parseRet; +} +#endif + +/* Verifies wolfSSL enforces an issuing CA's nameConstraints extension on a + * leaf certificate's otherName SAN (RFC 5280 4.2.1.10). The vulnerability + * was that ConfirmNameConstraints() ignored ASN_OTHER_TYPE entirely, so a + * malicious intermediate could issue leaves whose otherName SAN violated + * its own subtree. + * + * Coverage: + * 1. Critical excluded subtree, leaf SAN matches -> reject + * 2. Critical excluded subtree, leaf SAN does NOT match -> accept + * (positive control: distinguishes 'right rule fired' from + * 'broke everything with otherName') + * 3. Non-critical excluded subtree, leaf SAN matches -> reject + * (excluded is enforced regardless of criticality) + * 4. Critical permitted subtree, leaf SAN matches -> accept + * 5. Critical permitted subtree, leaf SAN does NOT match -> reject + */ +static int test_NameConstraints_OtherName(void) +{ + EXPECT_DECLS; +#if 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]; + byte ncPermittedAllowed[64]; + word32 sanBlockedSz, sanAllowedSz; + word32 ncExcludedBlockedSz, ncPermittedAllowedSz; + + sanBlockedSz = + build_otherName_san(sanBlocked, sizeof(sanBlocked), "blocked"); + sanAllowedSz = + build_otherName_san(sanAllowed, sizeof(sanAllowed), "allowed"); + ncExcludedBlockedSz = build_otherName_nameConstraints( + ncExcludedBlocked, sizeof(ncExcludedBlocked), 1, "blocked"); + ncPermittedAllowedSz = build_otherName_nameConstraints( + ncPermittedAllowed, sizeof(ncPermittedAllowed), 0, "allowed"); + ExpectIntGT((int)sanBlockedSz, 0); + ExpectIntGT((int)sanAllowedSz, 0); + ExpectIntGT((int)ncExcludedBlockedSz, 0); + ExpectIntGT((int)ncPermittedAllowedSz, 0); + + /* (1) Original bypass scenario: critical excluded otherName matches + * the leaf's otherName 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 critical excluded subtree, but the leaf + * carries a DIFFERENT otherName value, so byte-comparison says no + * match and the chain MUST verify. This pins the rejection in (1) + * to the matching path rather than to a blanket 'reject any + * otherName under critical'. */ + 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)); + + /* (4) Critical permitted subtree, leaf SAN inside the permitted set: + * verification succeeds. */ + ExpectIntEQ(verify_with_otherName_chain( + ncPermittedAllowed, ncPermittedAllowedSz, 1, + sanAllowed, sanAllowedSz), + 0); + + /* (5) Critical permitted subtree, leaf SAN outside the permitted set: + * verification rejects. */ + ExpectIntEQ(verify_with_otherName_chain( + ncPermittedAllowed, ncPermittedAllowedSz, 1, + sanBlocked, sanBlockedSz), + WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); +#endif + return EXPECT_RESULT(); +} + static int test_MakeCertWithCaFalse(void) { EXPECT_DECLS; @@ -37012,6 +37268,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_PathLenSelfIssued), TEST_DECL(test_PathLenSelfIssuedAllowed), TEST_DECL(test_PathLenNoKeyUsage), + TEST_DECL(test_NameConstraints_OtherName), TEST_DECL(test_MakeCertWith0Ser), TEST_DECL(test_MakeCertWithCaFalse), #ifdef WOLFSSL_CERT_SIGN_CB diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 9a3be56616..f2737c9857 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -4375,7 +4375,8 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert); static int DecodeCrlDist(const byte* input, word32 sz, DecodedCert* cert); static int DecodeAuthInfo(const byte* input, word32 sz, DecodedCert* cert); #ifndef IGNORE_NAME_CONSTRAINTS -static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head, word32 limit, void* heap); +static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head, + word32 limit, byte* hasUnsupported, void* heap); static int DecodeNameConstraints(const byte* input, word32 sz, DecodedCert* cert); #endif #if defined(WOLFSSL_SEP) || defined(WOLFSSL_CERT_EXT) @@ -17654,6 +17655,26 @@ static int PermittedListOk(DNS_entry* name, Base_entry* dnsList, byte nameType) break; } } + else if (nameType == ASN_OTHER_TYPE) { + /* RFC 5280 4.2.1.10: otherName matching is byte-exact + * comparison of the full OtherName encoding. The FPKI/SEP + * path also stores entries that contain only the parsed + * UPN/FASCN value and have oidSum != 0; those are not + * byte-comparable with the OID || [0] EXPLICIT value form + * stored for the constraint, so we explicitly skip them. + * Without that guard, a coincidental length match could + * mis-validate. */ + if ( + #ifdef WOLFSSL_FPKI + name->oidSum == 0 && + #endif + name->len == current->nameSz && + XMEMCMP(name->name, current->name, + (size_t)current->nameSz) == 0) { + match = 1; + break; + } + } else if (name->len >= current->nameSz && wolfssl_local_MatchBaseName(nameType, name->name, name->len, current->name, current->nameSz)) { @@ -17701,6 +17722,19 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType) break; } } + else if (nameType == ASN_OTHER_TYPE) { + /* See note in PermittedListOk about byte-exact matching. */ + if ( + #ifdef WOLFSSL_FPKI + name->oidSum == 0 && + #endif + name->len == current->nameSz && + XMEMCMP(name->name, current->name, + (size_t)current->nameSz) == 0) { + ret = 1; + break; + } + } else if (name->len >= current->nameSz && wolfssl_local_MatchBaseName(nameType, name->name, name->len, current->name, current->nameSz)) { @@ -17718,13 +17752,14 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType) static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) { const byte nameTypes[] = {ASN_RFC822_TYPE, ASN_DNS_TYPE, ASN_DIR_TYPE, - ASN_IP_TYPE, ASN_URI_TYPE}; + ASN_IP_TYPE, ASN_URI_TYPE, ASN_OTHER_TYPE}; int i; if (signer == NULL || cert == NULL) return 0; - if (signer->excludedNames == NULL && signer->permittedNames == NULL) + if (signer->excludedNames == NULL && signer->permittedNames == NULL && + !signer->extNameConstraintHasUnsupported) return 1; for (i=0; i < (int)sizeof(nameTypes); i++) { @@ -17789,10 +17824,18 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) case ASN_URI_TYPE: name = cert->altNames; break; + case ASN_OTHER_TYPE: + /* otherName SAN entries are stored on cert->altNames with + * type ASN_OTHER_TYPE. Match by byte-exact comparison of + * the OtherName encoding (OID || [0] EXPLICIT value). For + * FPKI/SEP builds, altNames may also contain entries that + * hold only the parsed UPN/FASCN value (oidSum != 0); the + * explicit oidSum guard in IsInExcludedList / + * PermittedListOk skips those so a coincidental length + * match cannot mis-validate. */ + name = cert->altNames; + break; default: - /* Other types of names are ignored for now. - * Shouldn't it be rejected if it there is a altNamesByType[nameType] - * and signer->extNameConstraintCrit is set? */ return 0; } @@ -17833,6 +17876,19 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) } } + /* RFC 5280 4.2.1.10: "If a name constraints extension that is marked as + * critical imposes constraints on a particular name form ... the + * application MUST either process the constraint or reject the + * certificate." otherName is processed by byte-comparison above; any + * remaining unsupported forms (registeredID, x400Address, ediPartyName) + * trigger the fail-closed reject below. */ + if (signer->extNameConstraintCrit && + signer->extNameConstraintHasUnsupported) { + WOLFSSL_MSG("Critical nameConstraints contains unsupported " + "GeneralName form; rejecting"); + return 0; + } + return 1; } @@ -18140,8 +18196,34 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, /* GeneralName choice: otherName */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_OTHER_TYPE)) { /* TODO: test data for code path */ + #ifndef IGNORE_NAME_CONSTRAINTS + /* Store the raw OtherName encoding so ConfirmNameConstraints() can + * byte-match it against the issuing CA's subtree (RFC 5280 + * 4.2.1.10). DecodeOtherName() may also add an entry that holds + * only the parsed UPN/FASCN value with oidSum != 0; the explicit + * oidSum guard in IsInExcludedList()/PermittedListOk() ensures + * those parsed entries are skipped during byte-comparison. */ + ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ASN_OTHER_TYPE, &cert->altNames); + if (ret != 0) { + return ret; + } + #endif ret = DecodeOtherName(cert, input, &idx, len); } +#elif !defined(IGNORE_NAME_CONSTRAINTS) + /* GeneralName choice: otherName. + * No OID-specific decoding in this build, but we store the raw + * OtherName encoding (OID || [0] EXPLICIT value) on altNames so + * ConfirmNameConstraints() can byte-match it against the issuing CA's + * nameConstraints subtree (RFC 5280 4.2.1.10). */ + else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_OTHER_TYPE)) { + ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ASN_OTHER_TYPE, &cert->altNames); + if (ret == 0) { + idx += (word32)len; + } + } #endif /* GeneralName choice: dNSName, x400Address, ediPartyName */ else { @@ -19249,8 +19331,14 @@ static int DecodeSubtreeGeneralName(const byte* input, word32 sz, byte tag, (void)heap; - /* if constructed has leading sequence */ - if ((tag & ASN_CONSTRUCTED) == ASN_CONSTRUCTED) { + /* directoryName is encoded as [4] CONSTRUCTED { Name } where Name is a + * SEQUENCE - strip the inner SEQUENCE header. + * otherName is encoded as [0] CONSTRUCTED { OID, [0] EXPLICIT value } + * where the inner content is NOT a SEQUENCE; keep the bytes as-is so + * we can byte-match a leaf SAN otherName against the constraint. + */ + if ((tag & ASN_CONSTRUCTED) == ASN_CONSTRUCTED && + (tag & ASN_TYPE_MASK) != ASN_OTHER_TYPE) { ret = GetASN_Sequence(input, &nameIdx, &strLen, sz, 0); if (ret < 0) { ret = ASN_PARSE_E; @@ -19309,8 +19397,17 @@ static int DecodeSubtreeGeneralName(const byte* input, word32 sz, byte tag, * @return ASN_PARSE_E when SEQUENCE is not found as expected. */ #ifdef WOLFSSL_ASN_TEMPLATE +/* Decode a sub-tree of name constraints. + * + * @param [out] hasUnsupported Set to 1 when an entry with a GeneralName + * form we cannot fully enforce was + * encountered. Drives the RFC 5280 4.2.1.10 + * fail-closed requirement for critical + * nameConstraints extensions; must not be + * NULL. + */ static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head, - word32 limit, void* heap) + word32 limit, byte* hasUnsupported, void* heap) { DECL_ASNGETDATA(dataASN, subTreeASN_Length); word32 idx = 0; @@ -19352,13 +19449,21 @@ static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head, t == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE) || t == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_DIR_TYPE) || t == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE) || - t == (ASN_CONTEXT_SPECIFIC | ASN_URI_TYPE)) { + t == (ASN_CONTEXT_SPECIFIC | ASN_URI_TYPE) || + t == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | + ASN_OTHER_TYPE)) { /* Parse the general name and store a new entry. */ ret = DecodeSubtreeGeneralName(input + GetASNItem_DataIdx(dataASN[SUBTREEASN_IDX_BASE], input), dataASN[SUBTREEASN_IDX_BASE].length, t, head, heap); } - /* Skip entry. */ + else { + /* GeneralName form (e.g. registeredID, x400Address, + * ediPartyName) we do not enforce. Record so the caller can + * fail-closed when the nameConstraints extension is critical + * (RFC 5280 4.2.1.10). */ + *hasUnsupported = 1; + } } } @@ -19406,6 +19511,7 @@ static int DecodeNameConstraints(const byte* input, word32 sz, DECL_ASNGETDATA(dataASN, nameConstraintsASN_Length); word32 idx = 0; int ret = 0; + byte hasUnsupported = 0; CALLOC_ASNGETDATA(dataASN, nameConstraintsASN_Length, ret, cert->heap); @@ -19421,7 +19527,7 @@ static int DecodeNameConstraints(const byte* input, word32 sz, dataASN[NAMECONSTRAINTSASN_IDX_PERMIT].data.ref.data, dataASN[NAMECONSTRAINTSASN_IDX_PERMIT].data.ref.length, &cert->permittedNames, WOLFSSL_MAX_NAME_CONSTRAINTS, - cert->heap); + &hasUnsupported, cert->heap); } } if (ret == 0) { @@ -19431,10 +19537,14 @@ static int DecodeNameConstraints(const byte* input, word32 sz, dataASN[NAMECONSTRAINTSASN_IDX_EXCLUDE].data.ref.data, dataASN[NAMECONSTRAINTSASN_IDX_EXCLUDE].data.ref.length, &cert->excludedNames, WOLFSSL_MAX_NAME_CONSTRAINTS, - cert->heap); + &hasUnsupported, cert->heap); } } + if (ret == 0 && hasUnsupported) { + cert->extNameConstraintHasUnsupported = 1; + } + FREE_ASNGETDATA(dataASN, cert->heap); return ret; @@ -22878,6 +22988,9 @@ int FillSigner(Signer* signer, DecodedCert* cert, int type, DerBuffer *der) #ifndef IGNORE_NAME_CONSTRAINTS signer->permittedNames = cert->permittedNames; signer->excludedNames = cert->excludedNames; + signer->extNameConstraintCrit = cert->extNameConstraintCrit; + signer->extNameConstraintHasUnsupported = + cert->extNameConstraintHasUnsupported; #endif #ifndef NO_SKID XMEMCPY(signer->subjectKeyIdHash, cert->extSubjKeyId, diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index b3028c9c99..3e1028275b 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2063,6 +2063,11 @@ struct DecodedCert { WC_BITFIELD extAuthKeyIdCrit:1; #ifndef IGNORE_NAME_CONSTRAINTS WC_BITFIELD extNameConstraintCrit:1; + /* Set when DecodeSubtree encountered a constraint form (e.g. + * registeredID, x400Address, ediPartyName) we cannot enforce. Used + * together with extNameConstraintCrit to implement the RFC 5280 + * 4.2.1.10 fail-closed requirement. */ + WC_BITFIELD extNameConstraintHasUnsupported:1; #endif WC_BITFIELD extSubjKeyIdCrit:1; WC_BITFIELD extKeyUsageCrit:1; @@ -2130,6 +2135,17 @@ struct Signer { byte extKeyUsage; word16 maxPathLen; WC_BITFIELD selfSigned:1; +#ifndef IGNORE_NAME_CONSTRAINTS + /* Mirror of DecodedCert::extNameConstraintCrit and + * extNameConstraintHasUnsupported so ConfirmNameConstraints can + * implement the RFC 5280 4.2.1.10 fail-closed requirement when a + * critical nameConstraints extension imposes a constraint form we + * cannot fully enforce. Co-located with selfSigned to share its + * bitfield storage word and avoid growing sizeof(Signer), which is + * load-bearing for PERSIST_CERT_CACHE. */ + WC_BITFIELD extNameConstraintCrit:1; + WC_BITFIELD extNameConstraintHasUnsupported:1; +#endif const byte* publicKey; int nameLen; const char*