-
Notifications
You must be signed in to change notification settings - Fork 1k
Enforce Extended Key Usage on chain-supplied intermediate CAs #11145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16965,6 +16965,40 @@ static int DoCertReqCtx(WOLFSSL* ssl, ProcPeerCertArgs* args, | |
| } | ||
| #endif /* WOLFSSL_TLS13 */ | ||
|
|
||
| /* Enforced by default (RFC 5280 4.2.1.12: when an Extended Key Usage extension | ||
| * is present the certificate may only be used for one of the indicated | ||
| * purposes). IGNORE_KEY_EXTENSIONS is a deliberate, RFC-non-conformant opt-out; | ||
| * see the macro list at the top of wolfcrypt/src/asn.c. */ | ||
| #ifndef IGNORE_KEY_EXTENSIONS | ||
| /* Check that a chain-supplied CA is authorized for the TLS purpose currently | ||
| * being validated: serverAuth when this side is authenticating a server, | ||
| * clientAuth when authenticating a client. An absent extension leaves every | ||
| * purpose valid, and anyExtendedKeyUsage removes the restriction. A | ||
| * self-signed certificate is exempt: it can only take part in a path as a | ||
| * trust anchor the operator chose to load, matching the exemption AddCA() | ||
| * makes for the Key Usage of a root. Returns 0 when the CA may be used, | ||
| * EXTKEYUSE_AUTH_E when it may not. */ | ||
| static int CheckChainCAExtKeyUsage(const WOLFSSL* ssl, const DecodedCert* cert) | ||
| { | ||
| byte purpose; | ||
|
|
||
| if (!cert->extExtKeyUsageSet || cert->selfSigned) | ||
| return 0; | ||
|
|
||
| if (ssl->options.side == WOLFSSL_CLIENT_END) | ||
| purpose = EXTKEYUSE_SERVER_AUTH; | ||
| else | ||
| purpose = EXTKEYUSE_CLIENT_AUTH; | ||
|
|
||
| if ((cert->extExtKeyUsage & (EXTKEYUSE_ANY | purpose)) == 0) { | ||
| WOLFSSL_MSG("Chain CA ExtKeyUse doesn't allow TLS peer authentication"); | ||
| return EXTKEYUSE_AUTH_E; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
| #endif /* IGNORE_KEY_EXTENSIONS */ | ||
|
|
||
| #if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) | ||
| /* Parse a chain certificate as a CA and add it to the pending signers list | ||
| * for Certificate Status Request v2. */ | ||
|
|
@@ -17016,6 +17050,11 @@ static int ProcessPeerCertAddPendingCA(WOLFSSL* ssl, buffer* cert) | |
| goto exit_req_v2; | ||
| } | ||
| #endif | ||
| /* The Extended Key Usage purpose check is deliberately not repeated here. | ||
| * ProcessPeerCerts() applies it to this same certificate before offering it | ||
| * to the pool, and AddCA() does not apply it either, so repeating it would | ||
| * only take effect after a verify callback had already overridden the | ||
| * rejection, silently undoing that decision in CSR v2 builds alone. */ | ||
| ret = AllocDer(&derBuffer, cert->length, CA_TYPE, ssl->heap); | ||
| if (ret != 0 || derBuffer == NULL) { | ||
| goto exit_req_v2; | ||
|
|
@@ -18093,14 +18132,39 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, | |
| "not adding as CA"); | ||
| } | ||
| else if (ret == 0) { | ||
| #ifdef OPENSSL_EXTRA | ||
| if (args->certIdx > args->untrustedDepth) { | ||
| args->untrustedDepth = (char)args->certIdx + 1; | ||
| #ifndef IGNORE_KEY_EXTENSIONS | ||
| /* A CA restricted to some other purpose by its | ||
| * Extended Key Usage must not authenticate this peer, | ||
| * whether or not the certificate manager already | ||
| * holds it. */ | ||
| ret = CheckChainCAExtKeyUsage(ssl, args->dCert); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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 |
||
| if (ret != 0) { | ||
| WOLFSSL_ERROR_VERBOSE(ret); | ||
| #if defined(OPENSSL_EXTRA) || \ | ||
| defined(OPENSSL_EXTRA_X509_SMALL) | ||
| /* Return first cert error here */ | ||
| if (ssl->peerVerifyRet == 0) { | ||
| ssl->peerVerifyRet = | ||
| WOLFSSL_X509_V_ERR_INVALID_PURPOSE; | ||
| } | ||
| #endif | ||
| } | ||
| #endif | ||
| #endif /* IGNORE_KEY_EXTENSIONS */ | ||
| /* A CA turned away above is neither part of the | ||
| * verified chain nor something to report as verified, | ||
| * so leave the depth and the log to the accepted | ||
| * case. */ | ||
| if (ret == 0) { | ||
| #ifdef OPENSSL_EXTRA | ||
| if (args->certIdx > args->untrustedDepth) { | ||
| args->untrustedDepth = (char)args->certIdx + 1; | ||
| } | ||
| #endif | ||
|
|
||
| if (alreadySigner) { | ||
| WOLFSSL_MSG("Verified CA from chain and already had it"); | ||
| if (alreadySigner) { | ||
| WOLFSSL_MSG("Verified CA from chain and " | ||
| "already had it"); | ||
| } | ||
| } | ||
| } | ||
| else { | ||
|
|
@@ -29457,6 +29521,9 @@ static const char* wolfSSL_ERR_reason_error_string_OpenSSL(unsigned long e) | |
| case WOLFSSL_X509_V_ERR_PATH_LENGTH_EXCEEDED: | ||
| return "path length constraint exceeded"; | ||
|
|
||
| case WOLFSSL_X509_V_ERR_INVALID_PURPOSE: | ||
| return "unsupported certificate purpose"; | ||
|
|
||
| case WOLFSSL_X509_V_ERR_CERT_REJECTED: | ||
| return "certificate rejected"; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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_certificatealert, 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
skipAddCAfor an extraneous purpose-mismatched CA instead of failing the handshake.