Skip to content

Prevent a CA from the peer's chain from being used to satisfy the NameConstraints check. - #12054

Open
kareem-wolfssl wants to merge 2 commits into
wolfSSL:masterfrom
kareem-wolfssl:zd22763
Open

kareem-wolfssl wants to merge 2 commits into
wolfSSL:masterfrom
kareem-wolfssl:zd22763

Conversation

@kareem-wolfssl

@kareem-wolfssl kareem-wolfssl commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes zd#22763, F-17181

Testing

Built in + added tests, provided reproducer

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

…eConstraints check.

Thanks to Bhabani Sankar Das (https://github.com/007bsd) for the report.
@kareem-wolfssl
kareem-wolfssl requested a balanced review from Copilot November 6, 2026 22:44
@kareem-wolfssl kareem-wolfssl self-assigned this Nov 6, 2026
@kareem-wolfssl

Copy link
Copy Markdown
Contributor Author

@claude review

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

gcc-arm-cortex-m0plus

  • FLASH: .text +88 B (+0.1%, 70,807 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m3

  • FLASH: .text +108 B (+0.1%, 131,785 B / 262,144 B, total: 50% used)

gcc-arm-cortex-m4

  • FLASH: .text +64 B (+0.0%, 209,036 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +64 B (+0.1%, 73,527 B / 262,144 B, total: 28% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +64 B (+0.0%, 181,715 B / 262,144 B, total: 69% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +128 B (+0.1%, 196,056 B / 1,048,576 B, total: 19% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +128 B (+0.2%, 68,121 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +192 B (+0.0%, 800,868 B / 1,048,576 B, total: 76% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +128 B (+0.1%, 222,868 B / 262,144 B, total: 85% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +128 B (+0.0%, 312,912 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +64 B (+0.0%, 343,696 B / 1,048,576 B, total: 33% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +128 B (+0.2%, 68,121 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +128 B (+0.1%, 132,561 B / 262,144 B, total: 51% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +128 B (+0.1%, 249,634 B / 262,144 B, total: 95% used)

gcc-arm-cortex-m7

  • FLASH: .text +128 B (+0.1%, 209,100 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +128 B (+0.0%, 313,744 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +128 B (+0.1%, 249,634 B / 262,144 B, total: 95% used)

stm32-sim-stm32h753

  • FLASH: .text +320 B (+0.2%, 204,076 B / 2,097,152 B, total: 10% used)

zephyr-frdm_rw612

  • FLASH: text +16 B (+0.0%, 284,152 B / 67,108,864 B, total: 0% used)

Catch memory regressions in CI with MemBrowse. Free for open source.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

CA ancestor traversal can still bypass name constraints through missing parents or temporary-CA self-loops and incorrectly reject valid leaves beneath self-issued key-rollover CAs.

3 open issues
What changed in this PR

This PR hardens certificate name-constraint validation so a CA cached from a peer’s chain cannot be treated as a trust anchor.

Changes:

  • Distinguishes application-loaded trust anchors from peer-chain CAs during ancestor traversal.
  • Adds regression tests for self-issued CAs and self-looping issuer lookups.
  • Adds certificate fixtures, generation steps, and distribution entries.
File Description
wolfcrypt/​src/​asn.c Adds trust-anchor classification and peer-chain cycle rejection.
tests/​api/​test_certman.h Declares and registers the new regression tests.
tests/​api/​test_certman.c Tests self-issued and self-loop CA handling.
certs/​test/​nc-ancestor/​gen-nc-ancestor.sh Generates the new CA and leaf fixtures.
certs/​test/​nc-ancestor/​03-selfloop-leaf-cert.pem Constraint-violating leaf for the self-loop case.
certs/​test/​nc-ancestor/​03-selfissued-valid-leaf-cert.pem Permitted leaf for the self-issued case.
certs/​test/​nc-ancestor/​03-selfissued-leaf-cert.pem Constraint-violating leaf for the self-issued case.
certs/​test/​nc-ancestor/​01-uri-permit-ca-selfloop-key.pem Test key for the self-loop CA.
certs/​test/​nc-ancestor/​01-uri-permit-ca-selfloop-cert.pem CA fixture with an ambiguous name-based issuer lookup.
certs/​test/​nc-ancestor/​01-uri-permit-ca-selfissued-cert.pem Self-issued CA fixture with a distinct key identifier.
certs/​test/​include.am Includes the new fixtures in source distributions.

Review effort: Balanced

Comment thread wolfcrypt/src/asn.c
static int SignerFromPeerChain(const Signer* signer)
{
#ifndef WOLFCRYPT_ONLY
return signer->type == WOLFSSL_CHAIN_CA;
Comment thread wolfcrypt/src/asn.c Resolved
Comment thread wolfcrypt/src/asn.c Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread wolfcrypt/src/asn.c Resolved
Comment thread wolfcrypt/src/asn.c Resolved
Comment thread wolfcrypt/src/asn.c
static int SignerFromPeerChain(const Signer* signer)
{
#ifndef WOLFCRYPT_ONLY
return signer->type == WOLFSSL_CHAIN_CA;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) Applications that load a self-signed root with the public type WOLFSSL_CHAIN_CA now fail every chain under it with ASN_NAME_INVALID_E, even when no CA has name constraints. SignerFromPeerChain treats the public Signer->type value as proof that a CA came from a peer. wolfSSL_CertManagerLoadCABufferType() lets callers set that value on any CA, roots included. Fix: mark peer-chain CAs with an internal flag set only by ProcessPeerCerts()/ProcessPeerCertAddPendingCA(), or refuse WOLFSSL_CHAIN_CA for self-signed certs in wolfSSL_CertManagerLoadCABufferType(), so that roots the application loads still end the walk.

Why this was flagged

One plausible reason to call wolfSSL_CertManagerLoadCABufferType(cm, root, ..., WOLFSSL_CHAIN_CA) is so wolfSSL_CertManagerUnloadIntermediateCerts() can remove the root later. That function accepts the type (ssl_certman.c:633-665), and SetCAType() allows any value from WOLFSSL_USER_CA to WOLFSSL_USER_INTER (ssl_certman.c:3556). The root then has selfSigned=1 and type=WOLFSSL_CHAIN_CA. In the walk at asn.c:26883, SignerIsTrustAnchor() now returns 0 for it. FindSignerByAkidOrName() then finds the root itself, either by its AKID or by name when it has no AKID. ncParent == ncSigner and SignerFromPeerChain() is true, so asn.c:26888-26892 returns ASN_NAME_INVALID_E. This hits every intermediate or leaf verified under that root, through wolfSSL_CertManagerVerifyBuffer and TLS handshakes alike. On the base branch the walk stopped at any selfSigned signer and these chains verified. A TLS peer cannot trigger this, because AddCA() skips a self-signed copy whose SKID is already present.

Verification: It happens when an application loads a self-signed root through the public wolfSSL_CertManagerLoadCABufferType() with type WOLFSSL_CHAIN_CA. Every leaf or chain under that root then fails with ASN_NAME_INVALID_E at asn.c:26888-26893, even when no CA has name constraints. On the base commit these chains verified.

Comment thread wolfcrypt/src/asn.c Outdated
Comment on lines 26893 to 26896
}
/* Stop on missing parent, self-loop, or A->B->A cycle. */
if (ncParent == NULL || ncParent == ncSigner ||
ncParent == ncPrev)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 pre-existing, not blocking: pre-existing, security-relevant: a peer-chain CA whose parent the walk cannot resolve still ends the walk silently, so its issuer's name constraints are skipped. The new rejection covers ncParent == ncSigner and ncParent == ncPrev but not ncParent == NULL. Fix: also return ASN_NAME_INVALID_E when ncParent == NULL and SignerFromPeerChain(ncSigner). Alternatively, make FindSignerByAkidOrName accept every issuer the cert->ca lookup at asn.c:26496-26541 accepts.
A small fix can ride a push you are already making; otherwise a short reply is enough.

Why this was flagged

The cert->ca lookup that admits a CA into the chain (asn.c:26496-26541) is looser than FindSignerByAkidOrName (asn.c:25835-25885). Under WOLFSSL_ALLOW_AKID_SKID_MATCH it keeps a CA whose subjectNameHash differs from the issuer hash. Under WOLFSSL_AKID_NAME it resolves by AKID issuer+serial. Under HAVE_CERTIFICATE_STATUS_REQUEST_V2 it matches extraCAList by name alone. FindSignerByAkidOrName requires subjectNameHash == issuerNameHash and an AKID->SKID match, so it can return NULL for a WOLFSSL_CHAIN_CA that was legitimately added under its constrained issuer. The walk then hits ncParent == NULL at asn.c:26894 and breaks. The leaf is accepted without the issuer's constraints, exactly as on the base branch. The new check at asn.c:26888 excludes NULL, so the PR's stated goal (a peer-chain CA must not end the walk) is only partly met.

Verification: Verification: pre-existing (security-relevant). The new rejection at asn.c:26888 only fires on (ncParent == ncSigner || ncParent == ncPrev) && SignerFromPeerChain(ncSigner). The next check at asn.c:26895, if (ncParent == NULL || ...) break;, still ends the walk silently. The issuer's name constraints are then never checked against the leaf's names. The base code also broke on ncParent == NULL.

Fix the case where ncParent == NULL by using the already implemented check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants