From bc26ce05cd79fb58d3b3faffb5b1084740d77ade Mon Sep 17 00:00:00 2001 From: Ruby Martin Date: Wed, 10 Jun 2026 17:09:43 -0600 Subject: [PATCH] walk cert chain to apply ancestor name constraints - Ancestor walk added to ParseCertRelative - FindSignerByAkidOrName helper: AKID->SKID with name-hash validation, name-only fallback only when AKID is absent - Signer fields: authKeyIdSet, authKeyIdHash - issuerNameHash ifdef widened to include !IGNORE_NAME_CONSTRAINTS - WOLFSSL_MAX_CHAIN_DEPTH macro (default 20) - Self-loop and A->B->A cycle termination - CN-as-DNS fallback in ConfirmNameConstraints gated on !cert->isCA --- wolfcrypt/src/asn.c | 132 +++++++++++++++++++++++++++++++++++----- wolfssl/wolfcrypt/asn.h | 22 +++++-- 2 files changed, 134 insertions(+), 20 deletions(-) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 7e855986f6..a2cab5e3bd 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -18588,9 +18588,10 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) case ASN_DNS_TYPE: name = cert->altNames; - /* When no SAN is present, apply DNS name constraints to the - * Subject CN. */ - if (cert->subjectCN != NULL && cert->altNames == NULL) { + /* 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; @@ -23149,6 +23150,71 @@ Signer* findSignerByName(Signer *list, byte *hash) return NULL; } +#ifndef IGNORE_NAME_CONSTRAINTS +/* Find a signer for cert in cm and extraCAList. Prefers AKID->SKID + * with name-hash validation. Fall back to name-only when AKID is + * absent. */ +static Signer* FindSignerByAkidOrName(void* cm, Signer* extraCAList, + Signer* cert) +{ + Signer* signer = NULL; +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 + #ifndef NO_SKID + Signer* exCaSigner; + #endif +#else + (void)extraCAList; +#endif + +#ifndef NO_SKID + if (cert->authKeyIdSet) { + signer = GetCA(cm, cert->authKeyIdHash); + if (signer != NULL && + XMEMCMP(signer->subjectNameHash, cert->issuerNameHash, + SIGNER_DIGEST_SIZE) != 0) { + signer = NULL; + } + /* AKID is authoritative; do not fall back to name when AKID + * is set (could substitute a same-DN sibling). */ + } + else { + signer = GetCAByName(cm, cert->issuerNameHash); + } +#else + signer = GetCA(cm, cert->issuerNameHash); +#endif + +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 + if (signer == NULL && extraCAList != NULL) { + #ifndef NO_SKID + if (cert->authKeyIdSet) { + for (exCaSigner = extraCAList; exCaSigner != NULL; + exCaSigner = exCaSigner->next) { + if (XMEMCMP(exCaSigner->subjectKeyIdHash, + cert->authKeyIdHash, + SIGNER_DIGEST_SIZE) == 0 && + XMEMCMP(exCaSigner->subjectNameHash, + cert->issuerNameHash, + SIGNER_DIGEST_SIZE) == 0) { + signer = exCaSigner; + break; + } + } + /* AKID is authoritative; do not fall back to name. */ + } + else { + signer = findSignerByName(extraCAList, cert->issuerNameHash); + } + #else + signer = findSignerByName(extraCAList, cert->issuerNameHash); + #endif + } +#endif + + return signer; +} +#endif /* !IGNORE_NAME_CONSTRAINTS */ + int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, Signer *extraCAList) { @@ -23163,6 +23229,12 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, int idx = 0; #endif byte* sce_tsip_encRsaKeyIdx; +#ifndef IGNORE_NAME_CONSTRAINTS + int ncDepth = 0; + Signer* ncSigner = NULL; + Signer* ncParent = NULL; + Signer* ncPrev = NULL; +#endif (void)extraCAList; if (cert == NULL) { @@ -23795,18 +23867,6 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, } #endif /* WOLFSSL_DUAL_ALG_CERTS */ } - #ifndef IGNORE_NAME_CONSTRAINTS - if (verify == VERIFY || verify == VERIFY_OCSP || - verify == VERIFY_NAME || verify == VERIFY_SKIP_DATE) { - /* check that this cert's name is permitted by the signer's - * name constraints */ - if (!ConfirmNameConstraints(cert->ca, cert)) { - WOLFSSL_MSG("Confirm name constraint failed"); - WOLFSSL_ERROR_VERBOSE(ASN_NAME_INVALID_E); - return ASN_NAME_INVALID_E; - } - } - #endif /* IGNORE_NAME_CONSTRAINTS */ } /* cert->ca */ #ifdef WOLFSSL_CERT_REQ else if (type == CERTREQ_TYPE) { @@ -23888,6 +23948,38 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, } } /* verify != NO_VERIFY && type != CA_TYPE && type != TRUSTED_PEER_TYPE */ +#ifndef IGNORE_NAME_CONSTRAINTS + /* Apply each ancestor CA's name constraints to this cert. + * Signer pointers between lookups are not lock-protected + * (see wolfssl_cm_get_certs_der). */ + if ((verify == VERIFY || verify == VERIFY_OCSP || + verify == VERIFY_NAME || verify == VERIFY_SKIP_DATE) && + type != TRUSTED_PEER_TYPE && cert->ca != NULL) { + ncSigner = cert->ca; + while (ncSigner != NULL) { + if (!ConfirmNameConstraints(ncSigner, cert)) { + WOLFSSL_MSG("Confirm name constraint failed"); + WOLFSSL_ERROR_VERBOSE(ASN_NAME_INVALID_E); + return ASN_NAME_INVALID_E; + } + /* Stop at trust anchor (self-issued). */ + if (ncSigner->selfSigned) + break; + ncParent = FindSignerByAkidOrName(cm, extraCAList, ncSigner); + /* Stop on missing parent, self-loop, or A->B->A cycle. */ + if (ncParent == NULL || ncParent == ncSigner || + ncParent == ncPrev) + break; + if (++ncDepth >= WOLFSSL_MAX_CHAIN_DEPTH) { + WOLFSSL_MSG("NC ancestor walk exceeded WOLFSSL_MAX_CHAIN_DEPTH"); + WOLFSSL_ERROR_VERBOSE(ASN_PATHLEN_SIZE_E); + return ASN_PATHLEN_SIZE_E; + } + ncPrev = ncSigner; + ncSigner = ncParent; + } + } +#endif /* IGNORE_NAME_CONSTRAINTS */ #if defined(WOLFSSL_NO_TRUSTED_CERTS_VERIFY) && !defined(NO_SKID) exit_pcr: #endif @@ -23966,10 +24058,18 @@ int FillSigner(Signer* signer, DecodedCert* cert, int type, DerBuffer *der) #ifndef NO_SKID XMEMCPY(signer->subjectKeyIdHash, cert->extSubjKeyId, SIGNER_DIGEST_SIZE); + #ifndef IGNORE_NAME_CONSTRAINTS + if (cert->extAuthKeyIdSet) { + XMEMCPY(signer->authKeyIdHash, cert->extAuthKeyId, + SIGNER_DIGEST_SIZE); + signer->authKeyIdSet = 1; + } + #endif #endif XMEMCPY(signer->subjectNameHash, cert->subjectHash, SIGNER_DIGEST_SIZE); - #if defined(HAVE_OCSP) || defined(HAVE_CRL) || defined(WOLFSSL_AKID_NAME) + #if defined(HAVE_OCSP) || defined(HAVE_CRL) || \ + defined(WOLFSSL_AKID_NAME) || !defined(IGNORE_NAME_CONSTRAINTS) XMEMCPY(signer->issuerNameHash, cert->issuerHash, SIGNER_DIGEST_SIZE); #endif diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 45993f0638..e17903653f 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -1736,6 +1736,11 @@ typedef struct EncodedName { #define WOLFSSL_MAX_PATH_LEN 127 #endif +#ifndef WOLFSSL_MAX_CHAIN_DEPTH + /* Max cert chain depth for ancestor walks. */ + #define WOLFSSL_MAX_CHAIN_DEPTH 20 +#endif + typedef struct DecodedName DecodedName; typedef struct DecodedCert DecodedCert; typedef struct Signer Signer; @@ -2207,6 +2212,10 @@ struct Signer { */ WC_BITFIELD extNameConstraintCrit:1; WC_BITFIELD extNameConstraintHasUnsupported:1; +#ifndef NO_SKID + WC_BITFIELD authKeyIdSet:1; /* true when authKeyIdHash holds the + * AKID extension's keyId */ +#endif #endif const byte* publicKey; int nameLen; @@ -2218,15 +2227,20 @@ struct Signer { #endif byte subjectNameHash[SIGNER_DIGEST_SIZE]; /* sha hash of names in certificate */ -#if defined(HAVE_OCSP) || defined(HAVE_CRL) || defined(WOLFSSL_AKID_NAME) +#if defined(HAVE_OCSP) || defined(HAVE_CRL) || defined(WOLFSSL_AKID_NAME) || \ + !defined(IGNORE_NAME_CONSTRAINTS) byte issuerNameHash[SIGNER_DIGEST_SIZE]; - /* sha hash of issuer names in certificate. - * Used in OCSP to check for authorized - * responders. */ + /* sha hash of issuer names; used by + * OCSP and name-constraint walk */ #endif #ifndef NO_SKID byte subjectKeyIdHash[SIGNER_DIGEST_SIZE]; /* sha hash of key in certificate */ +#ifndef IGNORE_NAME_CONSTRAINTS + byte authKeyIdHash[SIGNER_DIGEST_SIZE]; + /* sha hash of AKID; locates signer + * during name-constraint walk */ +#endif #endif #ifdef HAVE_OCSP byte subjectKeyHash[KEYID_SIZE];