From bc26ce05cd79fb58d3b3faffb5b1084740d77ade Mon Sep 17 00:00:00 2001 From: Ruby Martin Date: Wed, 10 Jun 2026 17:09:43 -0600 Subject: [PATCH 1/2] 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 7e855986f67..a2cab5e3bd0 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 45993f0638a..e17903653f0 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]; From 0052ec44dd70fdfc9b26815c937d31148d3d6e52 Mon Sep 17 00:00:00 2001 From: Ruby Martin Date: Wed, 10 Jun 2026 17:10:43 -0600 Subject: [PATCH 2/2] add regression tests for name-constraint ancestor walk - test_wolfSSL_CertManagerNameConstraint_valid_chain - test_wolfSSL_CertManagerNameConstraint_skid_disambiguates - Cert/key fixtures under certs/test/nc-ancestor/ - gen-nc-ancestor.sh to regenerate from committed keys --- certs/test/include.am | 15 ++ certs/test/nc-ancestor/00-root-cert.pem | 11 ++ certs/test/nc-ancestor/00-root-key.pem | 5 + .../00-uri-permit-ca-permissive-cert.pem | 11 ++ .../00-uri-permit-ca-permissive-key.pem | 5 + .../nc-ancestor/01-uri-permit-ca-cert.pem | 13 ++ .../test/nc-ancestor/01-uri-permit-ca-key.pem | 5 + .../nc-ancestor/02-benign-sub-ca-cert.pem | 12 ++ .../test/nc-ancestor/02-benign-sub-ca-key.pem | 5 + .../test/nc-ancestor/03-leaf-attacker-key.pem | 5 + certs/test/nc-ancestor/03-leaf-chain.pem | 38 +++++ certs/test/nc-ancestor/03-valid-leaf-cert.pem | 13 ++ certs/test/nc-ancestor/03-valid-leaf-key.pem | 5 + certs/test/nc-ancestor/gen-nc-ancestor.sh | 161 ++++++++++++++++++ tests/api/test_certman.c | 90 ++++++++++ tests/api/test_certman.h | 8 +- 16 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 certs/test/nc-ancestor/00-root-cert.pem create mode 100644 certs/test/nc-ancestor/00-root-key.pem create mode 100644 certs/test/nc-ancestor/00-uri-permit-ca-permissive-cert.pem create mode 100644 certs/test/nc-ancestor/00-uri-permit-ca-permissive-key.pem create mode 100644 certs/test/nc-ancestor/01-uri-permit-ca-cert.pem create mode 100644 certs/test/nc-ancestor/01-uri-permit-ca-key.pem create mode 100644 certs/test/nc-ancestor/02-benign-sub-ca-cert.pem create mode 100644 certs/test/nc-ancestor/02-benign-sub-ca-key.pem create mode 100644 certs/test/nc-ancestor/03-leaf-attacker-key.pem create mode 100644 certs/test/nc-ancestor/03-leaf-chain.pem create mode 100644 certs/test/nc-ancestor/03-valid-leaf-cert.pem create mode 100644 certs/test/nc-ancestor/03-valid-leaf-key.pem create mode 100755 certs/test/nc-ancestor/gen-nc-ancestor.sh diff --git a/certs/test/include.am b/certs/test/include.am index a173248f22e..e5b06eebf58 100644 --- a/certs/test/include.am +++ b/certs/test/include.am @@ -104,3 +104,18 @@ EXTRA_DIST += \ certs/test/expired/expired-ca.der \ certs/test/expired/expired-cert.pem \ certs/test/expired/expired-cert.der + +EXTRA_DIST += \ + certs/test/nc-ancestor/gen-nc-ancestor.sh \ + certs/test/nc-ancestor/00-root-cert.pem \ + certs/test/nc-ancestor/00-root-key.pem \ + certs/test/nc-ancestor/00-uri-permit-ca-permissive-cert.pem \ + certs/test/nc-ancestor/00-uri-permit-ca-permissive-key.pem \ + certs/test/nc-ancestor/01-uri-permit-ca-cert.pem \ + certs/test/nc-ancestor/01-uri-permit-ca-key.pem \ + certs/test/nc-ancestor/02-benign-sub-ca-cert.pem \ + certs/test/nc-ancestor/02-benign-sub-ca-key.pem \ + certs/test/nc-ancestor/03-leaf-attacker-key.pem \ + certs/test/nc-ancestor/03-leaf-chain.pem \ + certs/test/nc-ancestor/03-valid-leaf-cert.pem \ + certs/test/nc-ancestor/03-valid-leaf-key.pem diff --git a/certs/test/nc-ancestor/00-root-cert.pem b/certs/test/nc-ancestor/00-root-cert.pem new file mode 100644 index 00000000000..0e6c82fd015 --- /dev/null +++ b/certs/test/nc-ancestor/00-root-cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBnjCCAUWgAwIBAgIBEDAKBggqhkjOPQQDAjA3MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFTATBgNVBAMMDE5DIFRlc3QgUm9vdDAeFw0yNjA2MTcx +NjQ5MTNaFw00NjA2MTIxNjQ5MTNaMDcxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhO +QyBUZXN0czEVMBMGA1UEAwwMTkMgVGVzdCBSb290MFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAEH8XA+HpY8YL8kkIzgHQKUudoe4ZACBd/d0stYnvyQDiko5rOjTEY +Ayha6yaf1oYaZqGdE7LrEXN0+mNplh/fuKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFFgBv14PFBEmIEUkSYhOelfJG8QtMAoG +CCqGSM49BAMCA0cAMEQCIFdTyWY40+eB0OfRni+daV3gSO0+57bwzqtbbIkR+UTS +AiA91cFwnImuRN8cf/sfoow7u91f8YTW/3wzCwNBc4axnA== +-----END CERTIFICATE----- diff --git a/certs/test/nc-ancestor/00-root-key.pem b/certs/test/nc-ancestor/00-root-key.pem new file mode 100644 index 00000000000..92ecc809092 --- /dev/null +++ b/certs/test/nc-ancestor/00-root-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIjlWhdRFLGgcr6mtVVFKs38XvU06zb7y0hS2wan70rkoAoGCCqGSM49 +AwEHoUQDQgAEH8XA+HpY8YL8kkIzgHQKUudoe4ZACBd/d0stYnvyQDiko5rOjTEY +Ayha6yaf1oYaZqGdE7LrEXN0+mNplh/fuA== +-----END EC PRIVATE KEY----- diff --git a/certs/test/nc-ancestor/00-uri-permit-ca-permissive-cert.pem b/certs/test/nc-ancestor/00-uri-permit-ca-permissive-cert.pem new file mode 100644 index 00000000000..907a4f02f29 --- /dev/null +++ b/certs/test/nc-ancestor/00-uri-permit-ca-permissive-cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBoDCCAUegAwIBAgIBEjAKBggqhkjOPQQDAjA4MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFjAUBgNVBAMMDVVSSSBQZXJtaXQgQ0EwHhcNMjYwNjE3 +MTY0OTEzWhcNNDYwNjEyMTY0OTEzWjA4MQswCQYDVQQGEwJVUzERMA8GA1UECgwI +TkMgVGVzdHMxFjAUBgNVBAMMDVVSSSBQZXJtaXQgQ0EwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAATo9NHaL2G3EUr/H8b80VjTMpaG6wYlwr0O12WJnhc2rbx5OXTj +NCoHZiv1tP9LX4tzDNItcvtTQ4KqucauIVZao0IwQDAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUbRiM8y/EUI+f3CfZNVab5WsNQSQw +CgYIKoZIzj0EAwIDRwAwRAIgVL6GP7rNVB9/TiHMvb65bMupvmS4D8QmOBTv5wJc +JokCIHLUUM5jO2iequaJ0RW5phjkem74R+2J/KJgKVcUGS+x +-----END CERTIFICATE----- diff --git a/certs/test/nc-ancestor/00-uri-permit-ca-permissive-key.pem b/certs/test/nc-ancestor/00-uri-permit-ca-permissive-key.pem new file mode 100644 index 00000000000..41f4690a788 --- /dev/null +++ b/certs/test/nc-ancestor/00-uri-permit-ca-permissive-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDIvBXdwFSH5kRKKGmPAIfkBuiSDZtOzBQIxP8xz2gc4oAoGCCqGSM49 +AwEHoUQDQgAE6PTR2i9htxFK/x/G/NFY0zKWhusGJcK9DtdliZ4XNq28eTl04zQq +B2Yr9bT/S1+LcwzSLXL7U0OCqrnGriFWWg== +-----END EC PRIVATE KEY----- diff --git a/certs/test/nc-ancestor/01-uri-permit-ca-cert.pem b/certs/test/nc-ancestor/01-uri-permit-ca-cert.pem new file mode 100644 index 00000000000..6b3b27fec09 --- /dev/null +++ b/certs/test/nc-ancestor/01-uri-permit-ca-cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB4zCCAYmgAwIBAgIBETAKBggqhkjOPQQDAjA3MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFTATBgNVBAMMDE5DIFRlc3QgUm9vdDAeFw0yNjA2MTcx +NjQ5MTNaFw00NjA2MTIxNjQ5MTNaMDgxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhO +QyBUZXN0czEWMBQGA1UEAwwNVVJJIFBlcm1pdCBDQTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABFrlMrMGfsgCYAIrm6Txj6XX89Xij2nCextBHk3fb3UDhnnYlnY5 +0uwhnOGDbuyaoOWdd6xQOi/hLoFUERSArDOjgYQwgYEwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNe8LgUsg1wiiv2LAqB+mHkWZc10 +MB8GA1UdIwQYMBaAFFgBv14PFBEmIEUkSYhOelfJG8QtMB4GA1UdHgEB/wQUMBKg +EDAOhgwuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDSAAwRQIgTxJPzS5x3CijTVJn +hZVklkfJdHT/FZsUoq/c7p7Byl0CIQDixmRi8yTjmprhAu+nQCod1m6psWpw1irW +UAdvBlM+EA== +-----END CERTIFICATE----- diff --git a/certs/test/nc-ancestor/01-uri-permit-ca-key.pem b/certs/test/nc-ancestor/01-uri-permit-ca-key.pem new file mode 100644 index 00000000000..82a81232a4e --- /dev/null +++ b/certs/test/nc-ancestor/01-uri-permit-ca-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEzfFkozUo56l+WktIla45isYf6tsAZZg5b1Ph7Ou9e3oAoGCCqGSM49 +AwEHoUQDQgAEWuUyswZ+yAJgAiubpPGPpdfz1eKPacJ7G0EeTd9vdQOGediWdjnS +7CGc4YNu7Jqg5Z13rFA6L+EugVQRFICsMw== +-----END EC PRIVATE KEY----- diff --git a/certs/test/nc-ancestor/02-benign-sub-ca-cert.pem b/certs/test/nc-ancestor/02-benign-sub-ca-cert.pem new file mode 100644 index 00000000000..416aec12cbc --- /dev/null +++ b/certs/test/nc-ancestor/02-benign-sub-ca-cert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBwTCCAWigAwIBAgIBIDAKBggqhkjOPQQDAjA4MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFjAUBgNVBAMMDVVSSSBQZXJtaXQgQ0EwHhcNMjYwNjE3 +MTY0OTEzWhcNNDYwNjEyMTY0OTEzWjA4MQswCQYDVQQGEwJVUzERMA8GA1UECgwI +TkMgVGVzdHMxFjAUBgNVBAMMDUJlbmlnbiBTdWIgQ0EwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAASZKAPbUSJU9u4tTNkubWqSXWAPOhANvNoZ/prsWQWDLyWVKbbV +Vbo3AdX+O/VZKUPEhPrAegWkGnAp+BIIwEMgo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUFYNbX112ryF4JdDlgMeqY0Uo6kkw +HwYDVR0jBBgwFoAU17wuBSyDXCKK/YsCoH6YeRZlzXQwCgYIKoZIzj0EAwIDRwAw +RAIgBfGaiofozmQUMuMo4pEW+hGMAONyTkKR6IXDVVIX5RUCIEXrIy0oDP/ETKfi ++4VOsiMiEeOSBUOmdQpAaQfHf0hZ +-----END CERTIFICATE----- diff --git a/certs/test/nc-ancestor/02-benign-sub-ca-key.pem b/certs/test/nc-ancestor/02-benign-sub-ca-key.pem new file mode 100644 index 00000000000..46c04d77b15 --- /dev/null +++ b/certs/test/nc-ancestor/02-benign-sub-ca-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIESDNs2dwdV/XRjqhCbWYYrQqvWq9fDHf7Xzm0xIyj1IoAoGCCqGSM49 +AwEHoUQDQgAEmSgD21EiVPbuLUzZLm1qkl1gDzoQDbzaGf6a7FkFgy8llSm21VW6 +NwHV/jv1WSlDxIT6wHoFpBpwKfgSCMBDIA== +-----END EC PRIVATE KEY----- diff --git a/certs/test/nc-ancestor/03-leaf-attacker-key.pem b/certs/test/nc-ancestor/03-leaf-attacker-key.pem new file mode 100644 index 00000000000..9800e1e65b8 --- /dev/null +++ b/certs/test/nc-ancestor/03-leaf-attacker-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAZeIz1p8VFPywN6Z6DMurHlIa3aQ1XrnGus1WMEYnk5oAoGCCqGSM49 +AwEHoUQDQgAE0DMqgWwITs3KxGRHGYd0jrUcQIdoyEietpnezW8auDJhFe+Z6wCP +Nkh8KmbnySNEp0mlwokUvvi5ol4z8bOyXw== +-----END EC PRIVATE KEY----- diff --git a/certs/test/nc-ancestor/03-leaf-chain.pem b/certs/test/nc-ancestor/03-leaf-chain.pem new file mode 100644 index 00000000000..4a3d181bec0 --- /dev/null +++ b/certs/test/nc-ancestor/03-leaf-chain.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIICBzCCAa2gAwIBAgIBMDAKBggqhkjOPQQDAjA4MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFjAUBgNVBAMMDUJlbmlnbiBTdWIgQ0EwHhcNMjYwNjE3 +MTY0OTEzWhcNNDYwNjEyMTY0OTEzWjBAMQswCQYDVQQGEwJVUzERMA8GA1UECgwI +TkMgVGVzdHMxHjAcBgNVBAMMFU5DIFRlc3QgQXR0YWNrZXIgTGVhZjBZMBMGByqG +SM49AgEGCCqGSM49AwEHA0IABNAzKoFsCE7NysRkRxmHdI61HECHaMhInraZ3s1v +GrgyYRXvmesAjzZIfCpm58kjRKdJpcKJFL74uaJeM/Gzsl+jgZ8wgZwwDAYDVR0T +AQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYD +VR0OBBYEFO6GdfO6MvjzgDGAs71HPqOXaCtBMB8GA1UdIwQYMBaAFBWDW19ddq8h +eCXQ5YDHqmNFKOpJMCcGA1UdEQEB/wQdMBuGGWh0dHBzOi8vYXR0YWNrZXIuY29t +L2xlYWYwCgYIKoZIzj0EAwIDSAAwRQIhAL/dvlCeChSzMyecV6fV7ecfKccFY1RA +NL/g+05/4lyiAiAO4PyaBBz5t/0U/TOInIzjtWqaO1cIL6IzE3xPoqj5KQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBwTCCAWigAwIBAgIBIDAKBggqhkjOPQQDAjA4MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFjAUBgNVBAMMDVVSSSBQZXJtaXQgQ0EwHhcNMjYwNjE3 +MTY0OTEzWhcNNDYwNjEyMTY0OTEzWjA4MQswCQYDVQQGEwJVUzERMA8GA1UECgwI +TkMgVGVzdHMxFjAUBgNVBAMMDUJlbmlnbiBTdWIgQ0EwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAASZKAPbUSJU9u4tTNkubWqSXWAPOhANvNoZ/prsWQWDLyWVKbbV +Vbo3AdX+O/VZKUPEhPrAegWkGnAp+BIIwEMgo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUFYNbX112ryF4JdDlgMeqY0Uo6kkw +HwYDVR0jBBgwFoAU17wuBSyDXCKK/YsCoH6YeRZlzXQwCgYIKoZIzj0EAwIDRwAw +RAIgBfGaiofozmQUMuMo4pEW+hGMAONyTkKR6IXDVVIX5RUCIEXrIy0oDP/ETKfi ++4VOsiMiEeOSBUOmdQpAaQfHf0hZ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB4zCCAYmgAwIBAgIBETAKBggqhkjOPQQDAjA3MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFTATBgNVBAMMDE5DIFRlc3QgUm9vdDAeFw0yNjA2MTcx +NjQ5MTNaFw00NjA2MTIxNjQ5MTNaMDgxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhO +QyBUZXN0czEWMBQGA1UEAwwNVVJJIFBlcm1pdCBDQTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABFrlMrMGfsgCYAIrm6Txj6XX89Xij2nCextBHk3fb3UDhnnYlnY5 +0uwhnOGDbuyaoOWdd6xQOi/hLoFUERSArDOjgYQwgYEwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNe8LgUsg1wiiv2LAqB+mHkWZc10 +MB8GA1UdIwQYMBaAFFgBv14PFBEmIEUkSYhOelfJG8QtMB4GA1UdHgEB/wQUMBKg +EDAOhgwuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDSAAwRQIgTxJPzS5x3CijTVJn +hZVklkfJdHT/FZsUoq/c7p7Byl0CIQDixmRi8yTjmprhAu+nQCod1m6psWpw1irW +UAdvBlM+EA== +-----END CERTIFICATE----- diff --git a/certs/test/nc-ancestor/03-valid-leaf-cert.pem b/certs/test/nc-ancestor/03-valid-leaf-cert.pem new file mode 100644 index 00000000000..9d5ca8c25e6 --- /dev/null +++ b/certs/test/nc-ancestor/03-valid-leaf-cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICBTCCAaygAwIBAgIBMTAKBggqhkjOPQQDAjA4MQswCQYDVQQGEwJVUzERMA8G +A1UECgwITkMgVGVzdHMxFjAUBgNVBAMMDUJlbmlnbiBTdWIgQ0EwHhcNMjYwNjE3 +MTY0OTEzWhcNNDYwNjEyMTY0OTEzWjA9MQswCQYDVQQGEwJVUzERMA8GA1UECgwI +TkMgVGVzdHMxGzAZBgNVBAMMEk5DIFRlc3QgVmFsaWQgTGVhZjBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABNcVnczEUQaQMcxlrButtTd8HqQEoMLGBl8XLPdI/eZs +42jj3B4l6txsi/zPG+9klJad5sPkp2Uqf9PiHowizW+jgaEwgZ4wDAYDVR0TAQH/ +BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0O +BBYEFLG1JSabNebv3xs8ZTuMVceg4FmrMB8GA1UdIwQYMBaAFBWDW19ddq8heCXQ +5YDHqmNFKOpJMCkGA1UdEQEB/wQfMB2GG2h0dHBzOi8vYmVuaWduLmV4YW1wbGUu +Y29tLzAKBggqhkjOPQQDAgNHADBEAiBWs+jRkdeHkfw7XuJ/v7qEXHFEojWJFu9z +Qhy+9ekdtwIgKRC5gM+FDlcDD2ULdCsHXtU9N/9801gx3gowxldxzA8= +-----END CERTIFICATE----- diff --git a/certs/test/nc-ancestor/03-valid-leaf-key.pem b/certs/test/nc-ancestor/03-valid-leaf-key.pem new file mode 100644 index 00000000000..3c36712b1bf --- /dev/null +++ b/certs/test/nc-ancestor/03-valid-leaf-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJKBX7gzH3pL3Cjgw5Z623D/6Jo/RrFZXKyu4IobX5DroAoGCCqGSM49 +AwEHoUQDQgAE1xWdzMRRBpAxzGWsG621N3wepASgwsYGXxcs90j95mzjaOPcHiXq +3GyL/M8b72SUlp3mw+SnZSp/0+IejCLNbw== +-----END EC PRIVATE KEY----- diff --git a/certs/test/nc-ancestor/gen-nc-ancestor.sh b/certs/test/nc-ancestor/gen-nc-ancestor.sh new file mode 100755 index 00000000000..e645bc0ae0e --- /dev/null +++ b/certs/test/nc-ancestor/gen-nc-ancestor.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# +# gen-nc-ancestor.sh +# Re-sign the NameConstraints ancestor-walk test certs from committed +# keys. Cert SKIDs are stable across runs; 01-uri-permit-ca and its +# permissive sibling are pinned to satisfy the AKID-disambiguation test. + +set -e + +check_result(){ + if [ $1 -ne 0 ]; then + echo "$2 Failed, Abort" + exit 1 + else + echo "$2 Succeeded!" + fi +} + +DIR="$(cd "$(dirname "$0")" && pwd)" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +# Issue a child cert from $issuer_cert/$issuer_key. +# $1 child-key $2 subject-CN $3 out-cert $4 ext-file $5 ext-section $6 serial +mkchild(){ + local child_key=$1 cn=$2 out=$3 extfile=$4 extsec=$5 serial=$6 + openssl req -new -key "$child_key" -out "$WORK/child.csr" \ + -subj "/C=US/O=NC Tests/CN=$cn" -config "$extfile" + check_result $? "$(basename "$out"): csr" + openssl x509 -req -in "$WORK/child.csr" \ + -CA "$issuer_cert" -CAkey "$issuer_key" \ + -set_serial "$serial" -out "$out" -days 7300 -sha256 \ + -extfile "$extfile" -extensions "$extsec" + check_result $? "$(basename "$out"): sign" + rm -f "$WORK/child.csr" +} + +# ---- ext configs ---- + +cat > "$WORK/root.cnf" <<'EOF' +[req] +distinguished_name = dn +prompt = no +[dn] +[v3_ca] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyCertSign, cRLSign +subjectKeyIdentifier = hash +EOF + +cat > "$WORK/uri-permit-ca.cnf" <<'EOF' +[req] +distinguished_name = dn +prompt = no +[dn] +[v3_uri_permit] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyCertSign, cRLSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +nameConstraints = critical, permitted;URI:.example.com +EOF + +cat > "$WORK/sub-ca-nonc.cnf" <<'EOF' +[req] +distinguished_name = dn +prompt = no +[dn] +[v3_sub_ca] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyCertSign, cRLSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +EOF + +cat > "$WORK/leaf-attacker.cnf" <<'EOF' +[req] +distinguished_name = dn +prompt = no +[dn] +[v3_leaf_attacker] +basicConstraints = critical, CA:FALSE +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +subjectAltName = critical, URI:https://attacker.com/leaf +EOF + +cat > "$WORK/leaf-valid.cnf" <<'EOF' +[req] +distinguished_name = dn +prompt = no +[dn] +[v3_leaf_valid] +basicConstraints = critical, CA:FALSE +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +subjectAltName = critical, URI:https://benign.example.com/ +EOF + +# ---- 00 root (self-signed) ---- + +openssl req -new -x509 -key "$DIR/00-root-key.pem" \ + -out "$DIR/00-root-cert.pem" \ + -subj "/C=US/O=NC Tests/CN=NC Test Root" \ + -config "$WORK/root.cnf" -extensions v3_ca \ + -set_serial 0x10 -days 7300 -sha256 +check_result $? "00-root-cert.pem" + +# ---- 01 uri-permit-ca (permits URI:.example.com), issued by root ---- + +issuer_cert="$DIR/00-root-cert.pem" +issuer_key="$DIR/00-root-key.pem" +mkchild "$DIR/01-uri-permit-ca-key.pem" "URI Permit CA" \ + "$DIR/01-uri-permit-ca-cert.pem" "$WORK/uri-permit-ca.cnf" \ + v3_uri_permit 0x11 + +# ---- 00 uri-permit-ca-permissive (same-DN distractor, self-signed) ---- + +openssl req -new -x509 -key "$DIR/00-uri-permit-ca-permissive-key.pem" \ + -out "$DIR/00-uri-permit-ca-permissive-cert.pem" \ + -subj "/C=US/O=NC Tests/CN=URI Permit CA" \ + -config "$WORK/root.cnf" -extensions v3_ca \ + -set_serial 0x12 -days 7300 -sha256 +check_result $? "00-uri-permit-ca-permissive-cert.pem" + +# ---- 02 benign-sub-ca (no NC), issued by uri-permit-ca ---- + +issuer_cert="$DIR/01-uri-permit-ca-cert.pem" +issuer_key="$DIR/01-uri-permit-ca-key.pem" +mkchild "$DIR/02-benign-sub-ca-key.pem" "Benign Sub CA" \ + "$DIR/02-benign-sub-ca-cert.pem" "$WORK/sub-ca-nonc.cnf" \ + v3_sub_ca 0x20 + +# ---- 03 leaf-attacker (URI violates grandparent's permit), issued by sub-ca ---- + +issuer_cert="$DIR/02-benign-sub-ca-cert.pem" +issuer_key="$DIR/02-benign-sub-ca-key.pem" +mkchild "$DIR/03-leaf-attacker-key.pem" "NC Test Attacker Leaf" \ + "$WORK/03-leaf-cert.pem" "$WORK/leaf-attacker.cnf" \ + v3_leaf_attacker 0x30 + +# ---- 03 valid-leaf (URI inside permit), issued by sub-ca ---- + +mkchild "$DIR/03-valid-leaf-key.pem" "NC Test Valid Leaf" \ + "$DIR/03-valid-leaf-cert.pem" "$WORK/leaf-valid.cnf" \ + v3_leaf_valid 0x31 + +# ---- Concatenated bundle: attacker leaf + benign-sub-ca + uri-permit-ca ---- + +cat "$WORK/03-leaf-cert.pem" \ + "$DIR/02-benign-sub-ca-cert.pem" \ + "$DIR/01-uri-permit-ca-cert.pem" \ + > "$DIR/03-leaf-chain.pem" +check_result $? "03-leaf-chain.pem" + +echo "Generated chain in $DIR/" +ls -la "$DIR/" diff --git a/tests/api/test_certman.c b/tests/api/test_certman.c index 66ca862167b..95da65be9f3 100644 --- a/tests/api/test_certman.c +++ b/tests/api/test_certman.c @@ -3524,3 +3524,93 @@ int test_wolfSSL_X509_V_ERR_strings(void) #endif return EXPECT_RESULT(); } + +/* Leaf must satisfy a grandparent CA's NCs even when its direct issuer + * carries no constraints. */ +int test_wolfSSL_CertManagerNameConstraint_valid_chain(void) +{ + EXPECT_DECLS; +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_WOLFSSL_CM_VERIFY) && defined(HAVE_ECC) && \ + !defined(IGNORE_NAME_CONSTRAINTS) && !defined(NO_SHA256) && \ + defined(WOLFSSL_ALT_NAMES) && \ + (defined(WOLFSSL_PEM_TO_DER) || defined(OPENSSL_EXTRA)) + WOLFSSL_CERT_MANAGER* cm = NULL; + const char* root_cert = + "./certs/test/nc-ancestor/00-root-cert.pem"; + const char* uri_permit_ca_cert = + "./certs/test/nc-ancestor/01-uri-permit-ca-cert.pem"; + const char* benign_sub_ca_cert = + "./certs/test/nc-ancestor/02-benign-sub-ca-cert.pem"; + const char* valid_leaf_cert = + "./certs/test/nc-ancestor/03-valid-leaf-cert.pem"; + const char* attacker_leaf_chain = + "./certs/test/nc-ancestor/03-leaf-chain.pem"; + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, root_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, uri_permit_ca_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, benign_sub_ca_cert, NULL), + WOLFSSL_SUCCESS); + + /* Positive: leaf satisfies the grandparent permit. */ + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, valid_leaf_cert, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + + /* Negative: leaf violates the grandparent permit. */ + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, attacker_leaf_chain, + WOLFSSL_FILETYPE_PEM), + WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + + wolfSSL_CertManagerFree(cm); +#endif + return EXPECT_RESULT(); +} + +/* Same-DN sibling without NCs is loaded alongside the strict CA. The + * walk must use AKID->SKID, not a name-only lookup, to find the real + * signer. */ +int test_wolfSSL_CertManagerNameConstraint_skid_disambiguates(void) +{ + EXPECT_DECLS; +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_WOLFSSL_CM_VERIFY) && defined(HAVE_ECC) && \ + !defined(IGNORE_NAME_CONSTRAINTS) && !defined(NO_SHA256) && \ + !defined(NO_SKID) && defined(WOLFSSL_ALT_NAMES) && \ + (defined(WOLFSSL_PEM_TO_DER) || defined(OPENSSL_EXTRA)) + WOLFSSL_CERT_MANAGER* cm = NULL; + const char* root_cert = + "./certs/test/nc-ancestor/00-root-cert.pem"; + const char* permissive_cert = + "./certs/test/nc-ancestor/00-uri-permit-ca-permissive-cert.pem"; + const char* strict_cert = + "./certs/test/nc-ancestor/01-uri-permit-ca-cert.pem"; + const char* benign_sub_ca_cert = + "./certs/test/nc-ancestor/02-benign-sub-ca-cert.pem"; + const char* attacker_leaf_chain = + "./certs/test/nc-ancestor/03-leaf-chain.pem"; + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, root_cert, NULL), + WOLFSSL_SUCCESS); + /* Load permissive sibling first to favor a name-only lookup. */ + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, permissive_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, strict_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, benign_sub_ca_cert, NULL), + WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, attacker_leaf_chain, + WOLFSSL_FILETYPE_PEM), + WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + + wolfSSL_CertManagerFree(cm); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_certman.h b/tests/api/test_certman.h index 173c0f7e308..35117601452 100644 --- a/tests/api/test_certman.h +++ b/tests/api/test_certman.h @@ -52,6 +52,8 @@ int test_wolfSSL_CertManagerCheckOCSPResponse(void); int test_various_pathlen_chains(void); int test_wolfSSL_CertManagerRejectMD5Cert(void); int test_wolfSSL_X509_V_ERR_strings(void); +int test_wolfSSL_CertManagerNameConstraint_valid_chain(void); +int test_wolfSSL_CertManagerNameConstraint_skid_disambiguates(void); #define TEST_CERTMAN_DECLS \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerAPI), \ @@ -83,7 +85,11 @@ int test_wolfSSL_X509_V_ERR_strings(void); TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerCheckOCSPResponse), \ TEST_DECL_GROUP("certman", test_various_pathlen_chains), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerRejectMD5Cert), \ - TEST_DECL_GROUP("certman", test_wolfSSL_X509_V_ERR_strings) + TEST_DECL_GROUP("certman", test_wolfSSL_X509_V_ERR_strings), \ + TEST_DECL_GROUP("certman", \ + test_wolfSSL_CertManagerNameConstraint_valid_chain), \ + TEST_DECL_GROUP("certman", \ + test_wolfSSL_CertManagerNameConstraint_skid_disambiguates) #endif /* WOLFCRYPT_TEST_CERTMAN_H */