mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 18:20:53 +02:00
Fix handling of otherName in ConfirmNameConstraints
This commit is contained in:
+257
@@ -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
|
||||
|
||||
+126
-13
@@ -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,
|
||||
|
||||
@@ -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*
|
||||
|
||||
Reference in New Issue
Block a user