Repository navigation
Conversation
The CA Extended Key Usage check in ProcessPeerCerts() ran only for CA certificates in the peer's Certificate message. When the peer sent just its leaf, the issuing CA came from the CertManager and its Extended Key Usage was not looked at, so the result depended on whether the peer included the intermediate. Record in the Signer whether the certificate had an Extended Key Usage extension, and after the leaf checks walk every CA from the leaf's issuer up to the self-signed trust anchor, applying the TLS purpose to each. The walk uses the same AKID-or-name lookup as the name constraints check, which is exported as wolfssl_local_FindSignerByAkidOrName(). Trusted peer certificates are not checked, as their chain is not used. Extend test_wolfSSL_chain_ca_ext_key_usage with leaf-only cases at depth one and two for server and client authentication, verify callback handling and a self-signed trust anchor.
|
There was a problem hiding this comment.
🟡 Changes recommended
Pending signers and restored caches can bypass issuer EKU checks, and repeated checks can invalidate certificate-specific callback overrides.
3 open issues
What changed in this PR
This PR extends TLS issuer Extended Key Usage checks to CAs loaded in the CertManager, aiming to make validation independent of which intermediates the peer sends.
Changes:
- Records EKU extension presence on cached signers.
- Walks issuer ancestors to enforce the TLS purpose while exempting self-signed trust anchors.
- Adds leaf-only chain tests for server/client authentication, callback overrides and trust-anchor handling.
| File | Description |
|---|---|
wolfssl/wolfcrypt/asn.h |
Adds signer EKU presence state and declares the shared issuer lookup. |
wolfcrypt/src/asn.c |
Populates the new state and exposes the existing ancestor lookup. |
tests/api/test_ssl_cert.c |
Extends the handshake harness and adds cached-issuer test cases. |
src/internal.c |
Adds issuer-chain EKU validation during peer certificate processing. |
Review effort: Balanced
| return EXTKEYUSE_AUTH_E; | ||
| } | ||
| #ifndef IGNORE_NAME_CONSTRAINTS | ||
| parent = wolfssl_local_FindSignerByAkidOrName(SSL_CM(ssl), NULL, ca); |
There was a problem hiding this comment.
Fixed in 286b560. When Certificate Status Request v2 multi-stapling is in use, the issuer walk passes TLSX_CSR2_GetPendingSigners() to wolfssl_local_FindSignerByAkidOrName(). That is the same list ProcessPeerCertParse() uses. A pending issuer no longer stops the walk before a cached ancestor.
| byte extKeyUsage; | ||
| word16 maxPathLen; | ||
| WC_BITFIELD selfSigned:1; /* Own key verified the signature */ | ||
| WC_BITFIELD extExtKeyUsageSet:1; /* EKU extension present */ |
There was a problem hiding this comment.
This pull request does not change the cache layout. Pull request 11875 rebuilds each CA from its DER, and FillSigner() already copies extExtKeyUsageSet and extKeyUsage. A second cache version here would conflict with that restore. This change should land after 11875, or be rebased onto it, so a restored codeSigning CA keeps the restriction.
| !args->haveTrustPeer && | ||
| #endif | ||
| !UsingChainVerifyCb(ssl)) { | ||
| ret = CheckSignerExtKeyUsage(ssl, args->dCert->ca); |
There was a problem hiding this comment.
Fixed in 286b560. When the verify callback accepts EXTKEYUSE_AUTH_E for a chain CA, the handshake records that CA's subject-name hash. CheckSignerExtKeyUsage() skips that CA and still checks ancestors that were not presented. The shared Signer is not changed.
The new test accepts the purpose error only at depth 1, and the handshake succeeds. The same callback still rejects a codeSigning CA that the peer did not send.
There was a problem hiding this comment.
78810b7 replaces the subject-hash table with a chain-index mask in the existing flag word.
The table made ProcPeerCertArgs 344 bytes. ssl->async->args is 96 bytes, so the async builds failed the size assert, including WOLFSSL_STATIC_MEMORY. Passing byte[][N] to a const byte[][N] parameter also failed -Wpedantic. The mask records the same accepted chain indexes and leaves the shared Signer unchanged. In the async user_settings build, ProcPeerCertArgs is 56 bytes.
CheckSignerExtKeyUsage() runs after the chain callback and starts at the leaf issuer. A callback that accepts EXTKEYUSE_AUTH_E for that CA was asked again at depth 0, and a depth-specific acceptance failed the handshake. Record the subject-name hash on the handshake when the callback accepts the error. The later walk skips that CA and still checks ancestors. The shared Signer is not changed. Pass the Certificate Status Request v2 pending signer list into the issuer lookup, the same list certificate parsing uses.
The subject-hash table made ProcPeerCertArgs larger than ssl->async->args, and the array argument failed -Wpedantic. Record accepted chain indexes in the existing flag word.
The decoded certificate is allocated before this loop. The extra null test made -Wnull-dereference treat the later isCA use as a null dereference under -Werror.
clang-tidy readability-uppercase-literal-suffix rejected the lowercase 1u literals in RememberWaivedCaEku() and CheckSignerExtKeyUsage().


Description
ProcessPeerCerts()checks the Extended Key Usage of CA certificates against the TLS purpose (serverAuthwhen verifying a server,clientAuthwhen verifying a client), but only for CAs the peer sends in its Certificate message. When the peer sends only its leaf and the issuing CA is already loaded in the CertManager, that CA's Extended Key Usage is not checked. The same chain is therefore rejected withEXTKEYUSE_AUTH_Ewhen the intermediate is sent and accepted when it is omitted.This change makes the result independent of which CAs the peer sends:
Signergains anextExtKeyUsageSetbit, set byFillSigner(), so an absent extension can be told apart from one that grants none of the known purposes.CheckSignerExtKeyUsage()walks from the leaf's issuer up to the self-signed trust anchor and applies the TLS purpose to every CA on the way. The trust anchor stays exempt, as inCheckChainCAExtKeyUsage(). The walk reuses the name constraints ancestor lookup, now exported aswolfssl_local_FindSignerByAkidOrName(), with the same cycle guard andWOLFSSL_MAX_CHAIN_DEPTHlimit.WOLFSSL_TRUST_PEER_CERT), whose chain is not used.IGNORE_NAME_CONSTRAINTStheSignerhas no issuer lookup data, so only the leaf's direct issuer is checked.TLSX_CSR2_GetPendingSigners(), the same listProcessPeerCertParse()uses. A pending issuer does not stop the walk before a cached ancestor.EXTKEYUSE_AUTH_Efor a CA that the peer sent, the handshake records that chain index in a mask inProcPeerCertArgs. The walk skips that CA and still checks the ancestors that the peer did not send. The sharedSigneris not changed.This change does not change the persisted cert cache. #11875 rebuilds each CA from its DER, so a restored CA keeps its Extended Key Usage. Merge this change after #11875.
Testing
test_wolfSSL_chain_ca_ext_key_usagegains aleafOnlyoption, where the peer sends only the leaf and the CAs are loaded into the verifier. New cases:codeSigningandclientAuthCA rejected, andserverAuth, no extension andanyExtendedKeyUsageaccepted, when verifying a servercodeSigningandserverAuthCA rejected, andclientAuthand no extension accepted, when verifying a clientEXTKEYUSE_AUTH_E, showing that the callback is given that errorcodeSigningtrust anchor with a leaf-only chain, which stays acceptedcodeSigningCA that the peer did not sendBuilt and run with the default configure flags plus cert generation, and with each of
OPENSSL_EXTRA,OPENSSL_EXTRA+WOLFSSL_CHAIN_VERIFY_CB,IGNORE_NAME_CONSTRAINTS,WOLFSSL_ASN_ORIGINAL,WOLFSSL_ALT_CERT_CHAINSwith CRL/OCSP and status request,WOLFSSL_TRUST_PEER_CERT, andNO_SKID. A trusted peer leaf whose issuer name matches a restricted CA in the CertManager still connects with the example client (-E). The new cases fail without the library change. The full--apisuite passes in the default andOPENSSL_EXTRAbuilds.NO_SKIDnote: the existing self-issued rollover cases in this test already fail on master withNO_SKID(ASN_SIG_CONFIRM_E); this change does not affect them.How to reproduce
Build and run the test:
The test reports
Failed/Skipped/Passed/All: 0/0/1/1.To see the bug, keep the new test cases and put back the library files from master (
originis the wolfSSL/wolfssl remote):The test reports
Failed/Skipped/Passed/All: 1/0/0/1with:The leaf-only handshake with a restricted CA in the CertManager succeeds, but the test expects
EXTKEYUSE_AUTH_E(-386).Tested on macOS arm64 (Apple clang) at 390159a.
Checklist