Skip to content

Enforce Extended Key Usage on chain-supplied intermediate CAs - #11145

Open
embhorn wants to merge 2 commits into
wolfSSL:masterfrom
embhorn:zd22309
Open

Enforce Extended Key Usage on chain-supplied intermediate CAs#11145
embhorn wants to merge 2 commits into
wolfSSL:masterfrom
embhorn:zd22309

Conversation

@embhorn

@embhorn embhorn commented Aug 11, 2026

Copy link
Copy Markdown
Member

Description

The peer certificate is checked for the serverAuth/clientAuth EKU, but the intermediate CAs sent with it were not. A CA constrained to another purpose by a critical EKU, a code signing sub-CA for example, could issue a serverAuth leaf for any hostname and wolfSSL would complete the handshake.

ProcessPeerCerts() now applies the same purpose rule to every chain CA it validates and fails with EXTKEYUSE_AUTH_E. An absent extension and anyExtendedKeyUsage leave all purposes valid per RFC 5280 4.2.1.12, and a self-signed certificate is exempt since it can only serve as an operator-loaded trust anchor. IGNORE_KEY_EXTENSIONS opts out, as it already did for the peer certificate.

Fixes zd22309

Testing

Added test_wolfSSL_chain_ca_ext_key_usage()

Checklist

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

@embhorn embhorn self-assigned this Aug 11, 2026
Copilot AI lite review requested due to automatic review settings August 11, 2026 20:43

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.

Pull request overview

This PR tightens TLS certificate-chain validation by enforcing RFC 5280 Extended Key Usage (EKU) constraints not just on the peer leaf certificate, but also on chain-supplied intermediate CA certificates validated in ProcessPeerCerts(). It also adds an OpenSSL-compat verify-result mapping for this failure and introduces a targeted regression test.

Changes:

  • Enforce serverAuth/clientAuth EKU purpose checks on chain-supplied intermediate CAs during peer authentication, failing with EXTKEYUSE_AUTH_E (unless IGNORE_KEY_EXTENSIONS is defined).
  • Add OpenSSL-compatibility reporting via WOLFSSL_X509_V_ERR_INVALID_PURPOSE and map EXTKEYUSE_AUTH_E to it.
  • Add test_wolfSSL_chain_ca_ext_key_usage() to cover allowed/denied intermediate-CA EKU scenarios in both server- and client-auth directions.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
wolfssl/ssl.h Adds WOLFSSL_X509_V_ERR_INVALID_PURPOSE verify-result code.
wolfcrypt/src/asn.c Updates IGNORE_KEY_EXTENSIONS documentation to include chain-CA EKU enforcement.
src/internal.c Implements chain-CA EKU purpose check and adds OpenSSL reason string for the new verify-result code.
src/x509_str.c Maps EXTKEYUSE_AUTH_E to WOLFSSL_X509_V_ERR_INVALID_PURPOSE for compatibility-layer error reporting.
tests/api/test_ssl_cert.c Adds a memio-based regression test that generates CA/leaf certs with different EKUs and validates handshake outcomes.
tests/api/test_ssl_cert.h Registers the new API test in the ssl_cert group.
tests/api.c Updates error-string test “missing ranges” to account for the new X509 verify-result value.
ChangeLog.md Documents the security fix and behavior change, including the new verify-result code.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/internal.c Outdated
Comment on lines 18153 to 18155
#ifdef OPENSSL_EXTRA
if (args->certIdx > args->untrustedDepth) {
args->untrustedDepth = (char)args->certIdx + 1;

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-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.

Fenrir Automated Review — PR #11145

Scan targets checked: wolfcrypt-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

Comment thread src/internal.c
* Extended Key Usage must not authenticate this peer,
* whether or not the certificate manager already
* holds it. */
ret = CheckChainCAExtKeyUsage(ssl, args->dCert);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 [Low] Chain CA purpose check applies only to transmitted CAs, evadable by omitting the intermediate · Certificate and trust chain validation bypass

CheckChainCAExtKeyUsage() runs only inside the transmitted-chain loop. A peer that omits a purpose-constrained intermediate the verifier can already resolve from its certificate manager (operator-loaded, or cached earlier by AddCA(..., WOLFSSL_CHAIN_CA, ...)) gets the leaf accepted with no purpose check.

Related known finding #1814 (similar but distinct): Both concern extended-key-usage enforcement in ProcessPeerCerts, but #1814 suppresses peer usage checks under OPENSSL_EXTRA plus verifyNone, whereas this check is skipped when an intermediate is locally resolved rather than transmitted. The faulting operation, root cause, and required patch differ.

Fix: Enforce the purpose on the Signer resolved during leaf validation too, adding an "EKU present" sentinel to Signer.extKeyUsage as Signer.keyUsage already uses 0xFFFF.

Comment thread src/internal.c

if ((cert->extExtKeyUsage & (EXTKEYUSE_ANY | purpose)) == 0) {
WOLFSSL_MSG("Chain CA ExtKeyUse doesn't allow TLS peer authentication");
return EXTKEYUSE_AUTH_E;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 [Low] Purpose check rejects extraneous chain certificates that are not in the validated path · Cryptographic correctness

The check is applied to every CA the peer transmits, not only to certificates in the leaf's certification path. A peer that bundles an extraneous but locally verifiable CA (timestamping or OCSP-signing sub-CA of the same root) now gets a fatal bad_certificate alert, although RFC 8446 4.4.2 permits extraneous certificates.

Related known finding #5814 (similar but distinct): Both concern certificate usage-constraint enforcement during peer processing, but #5814 omits leaf client keyUsage validation for static-RSA suites, while this applies CA extended-key-usage validation to extraneous transmitted certificates. They involve different certificate roles, operations, root causes, and fixes.

Fix: Limit enforcement to certificates on the leaf's issuer path, or set skipAddCA for an extraneous purpose-mismatched CA instead of failing the handshake.

Comment thread tests/api/test_ssl_cert.c
!defined(NO_TLS) && !defined(NO_SHA256) && !defined(NO_ASN_TIME) && \
defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) && \
defined(USE_CERT_BUFFERS_2048) && !defined(IGNORE_KEY_EXTENSIONS)
static const test_eku_case cases[] = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚪ [Info] New chain-EKU test covers only depth-1 intermediates and no verify-callback override · Missing edge-case coverage on a function the PR also changed

Every case builds a two-certificate chain, so enforcement at chain depth greater than one is unexercised, and no case installs a verify callback — the override path that the new comment in ProcessPeerCertAddPendingCA relies on for CSR v2 correctness is untested.

Fix: Add a three-level chain case with the constrained CA at depth 2, and a case whose verify callback overrides EXTKEYUSE_AUTH_E.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m3

  • FLASH: .text +64 B (+0.1%, 123,587 B / 262,144 B, total: 47% used)

gcc-arm-cortex-m4

  • FLASH: .text +64 B (+0.0%, 201,903 B / 262,144 B, total: 77% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +64 B (+0.0%, 182,972 B / 1,048,576 B, total: 17% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .rodata +32 B, .text +64 B (+0.0%, 775,580 B / 1,048,576 B, total: 74% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +64 B (+0.0%, 214,766 B / 262,144 B, total: 82% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +64 B (+0.0%, 297,628 B / 1,048,576 B, total: 28% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +64 B (+0.0%, 328,176 B / 1,048,576 B, total: 31% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +64 B (+0.1%, 124,339 B / 262,144 B, total: 47% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +64 B (+0.0%, 238,369 B / 262,144 B, total: 91% used)

gcc-arm-cortex-m7

  • FLASH: .text +64 B (+0.0%, 201,903 B / 262,144 B, total: 77% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +128 B (+0.0%, 298,588 B / 1,048,576 B, total: 28% used)

gcc-arm-cortex-m7-tls13

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.

3 participants