Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 109 additions & 1 deletion src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -17941,6 +17941,66 @@ static int CheckChainCAExtKeyUsage(const WOLFSSL* ssl, const DecodedCert* cert)

return 0;
}

/* Record the chain index of a CA whose Extended Key Usage error the verify
* callback accepted. The leaf is index 0. Its issuer is index 1. */
static void RememberWaivedCaEku(ProcPeerCertArgs* args)
{
if (args == NULL || args->certIdx < 0 || args->certIdx >= MAX_CHAIN_DEPTH)
return;
args->ekuWaiveMask |= (word16)(1U << args->certIdx);
}

/* Check the Extended Key Usage of each CA from ca up to the self-signed trust
* anchor against the TLS purpose. waiveMask names chain indexes whose error
* this handshake already accepted. The first signer is the leaf issuer, chain
* index 1. A waived CA is skipped and the walk continues. With
* IGNORE_NAME_CONSTRAINTS there is no issuer lookup, so only ca is checked.
* pending holds Certificate Status Request v2 signers that are not in the
* certificate manager yet.
* Returns 0, EXTKEYUSE_AUTH_E or MAX_CHAIN_ERROR. */
static int CheckSignerExtKeyUsage(const WOLFSSL* ssl, Signer* ca,
Signer* pending, unsigned waiveMask)
{
byte purpose;
Signer* prev = NULL;
Signer* parent;
int depth = 0;
int certIdx = 1;

if (ssl->options.side == WOLFSSL_CLIENT_END)
purpose = EXTKEYUSE_SERVER_AUTH;
else
purpose = EXTKEYUSE_CLIENT_AUTH;

while (ca != NULL && !ca->selfSigned) {
int waived = (certIdx < MAX_CHAIN_DEPTH) &&
((waiveMask & (1U << certIdx)) != 0);

if (ca->extExtKeyUsageSet &&
(ca->extKeyUsage & (EXTKEYUSE_ANY | purpose)) == 0 &&
!waived) {
WOLFSSL_MSG("Issuer CA ExtKeyUse doesn't allow TLS peer auth");
return EXTKEYUSE_AUTH_E;
}
#ifndef IGNORE_NAME_CONSTRAINTS
parent = wolfssl_local_FindSignerByAkidOrName(SSL_CM(ssl), pending, ca);
#else
(void)pending;
parent = NULL;
#endif
/* Stop on missing parent, self-loop, or A->B->A cycle. */
if (parent == NULL || parent == ca || parent == prev)
break;
if (++depth >= WOLFSSL_MAX_CHAIN_DEPTH)
return MAX_CHAIN_ERROR;
prev = ca;
ca = parent;
certIdx++;
}

return 0;
}
#endif /* IGNORE_KEY_EXTENSIONS */

#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)
Expand Down Expand Up @@ -19570,7 +19630,21 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
#endif /* defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */

/* Do verify callback */
ret = DoVerifyCallback(SSL_CM(ssl), ssl, ret, args);
{
int cbErr = ret;

ret = DoVerifyCallback(SSL_CM(ssl), ssl, cbErr, args);
#ifndef IGNORE_KEY_EXTENSIONS
/* The later issuer walk repeats this CA. Remember an
* accepted purpose error so that walk does not reject
* it again. Ancestors are still checked. */
if (ret == 0 &&
cbErr == WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E) &&
args->dCert->isCA) {
RememberWaivedCaEku(args);
}
#endif
}
if (ssl->options.verifyNone &&
(ret == WC_NO_ERR_TRACE(CRL_MISSING) ||
ret == WC_NO_ERR_TRACE(CRL_CERT_REVOKED) ||
Expand Down Expand Up @@ -20096,6 +20170,40 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
}
}
}

/* CAs the peer did not send were not covered by
* CheckChainCAExtKeyUsage(). Chain CAs whose purpose error
* this handshake already accepted are skipped. */
if (ret == 0 && !ssl->options.verifyNone &&
#ifdef WOLFSSL_TRUST_PEER_CERT
!args->haveTrustPeer &&
#endif
!UsingChainVerifyCb(ssl)) {
Signer* ekuPending = NULL;

#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && \
!defined(NO_TLS)
if (TLSX_CSR2_IsMulti(ssl->extensions)) {
ekuPending = TLSX_CSR2_GetPendingSigners(
ssl->extensions);
}
#endif
ret = CheckSignerExtKeyUsage(ssl, args->dCert->ca,
ekuPending, args->ekuWaiveMask);
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 =
(ret == WC_NO_ERR_TRACE(MAX_CHAIN_ERROR)) ?
WOLFSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG :
WOLFSSL_X509_V_ERR_INVALID_PURPOSE;
}
#endif
}
}
#endif /* IGNORE_KEY_EXTENSIONS */

if (args->fatal) {
Expand Down
136 changes: 106 additions & 30 deletions tests/api/test_ssl_cert.c
Original file line number Diff line number Diff line change
Expand Up @@ -2604,12 +2604,15 @@ typedef struct test_eku_case {
* depth 2 */
int overrideCb; /* verify callback installed on the verifying
* side: 0 none, 1 accepts every error,
* 2 waives only the purpose error */
* 2 waives only the purpose error,
* 3 waives the purpose error only at depth 1 */
int verifyDepth; /* verify depth pinned on the verifying side,
* 0 leaves the default in place */
int expectRet; /* expected handshake result */
const char* keyUsage; /* Key Usage on the CA under test, NULL uses
* keyCertSign,cRLSign */
int leafOnly; /* peer sends only the leaf; with extraLevel
* the middle CA is also loaded to verify */
} test_eku_case;

/* Stand in for an application that knowingly accepts a rejected certificate. */
Expand All @@ -2636,6 +2639,25 @@ static int test_eku_purpose_only_cb(int preverify,
return preverify;
}

/* Accept a purpose error only for the intermediate at depth 1. A later report
* of the same error at depth 0, with the leaf as the current certificate,
* must not be required for the handshake to succeed. */
static int test_eku_depth_purpose_cb(int preverify,
WOLFSSL_X509_STORE_CTX* store)
{
if (preverify)
return 1;
if (store->error_depth == 1 &&
(store->error == WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E)
#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL)
|| store->error == WOLFSSL_X509_V_ERR_INVALID_PURPOSE
#endif
)) {
return 1;
}
return 0;
}

/* Build a CA:TRUE certificate for subjKey. A NULL issuerDer makes it
* self-signed, otherwise it is signed by issuerKey in the name of issuerDer. A
* NULL extKeyUsage omits the Extended Key Usage extension entirely. When
Expand Down Expand Up @@ -2743,7 +2765,8 @@ static int test_eku_gen_leaf(byte* out, int outMax, RsaKey* leafKey,
* through hsRet and verifyRet. */
static int test_eku_chain_handshake(const byte* chainDer, int chainSz,
const byte* rootDer, int rootSz, const byte* pinnedDer, int pinnedSz,
const test_eku_case* tc, int* hsRet, long* verifyRet)
const byte* pinned2Der, int pinned2Sz, const test_eku_case* tc,
int* hsRet, long* verifyRet)
{
EXPECT_DECLS;
WOLFSSL_CTX* ctx_c = NULL;
Expand All @@ -2759,6 +2782,8 @@ static int test_eku_chain_handshake(const byte* chainDer, int chainSz,
verifyCb = test_eku_override_cb;
else if (tc->overrideCb == 2)
verifyCb = test_eku_purpose_only_cb;
else if (tc->overrideCb == 3)
verifyCb = test_eku_depth_purpose_cb;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
/* The server always presents the generated chain so a single set of
Expand Down Expand Up @@ -2799,6 +2824,10 @@ static int test_eku_chain_handshake(const byte* chainDer, int chainSz,
ExpectIntEQ(wolfSSL_CTX_load_verify_buffer(verifyCtx, pinnedDer,
(long)pinnedSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
}
if (pinned2Der != NULL) {
ExpectIntEQ(wolfSSL_CTX_load_verify_buffer(verifyCtx, pinned2Der,
(long)pinned2Sz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
}

/* The harness only reports pass or fail, so read the reason from the side
* that did the verifying. */
Expand Down Expand Up @@ -2869,18 +2898,21 @@ static int test_eku_chain_case(const test_eku_fixture* f,

if (EXPECT_SUCCESS()) {
chainSz = leafSz;
if (tc->extraLevel) {
if (tc->extraLevel && !tc->leafOnly) {
XMEMCPY(f->chainDer + chainSz, f->midDer, (size_t)midSz);
chainSz += midSz;
}
XMEMCPY(f->chainDer + chainSz, f->interDer, (size_t)caSz);
chainSz += caSz;
if (!tc->leafOnly) {
XMEMCPY(f->chainDer + chainSz, f->interDer, (size_t)caSz);
chainSz += caSz;
}
if (tc->selfSignedCa) {
rootDer = f->interDer;
rootSz = caSz;
}
ExpectIntEQ(test_eku_chain_handshake(f->chainDer, chainSz,
rootDer, rootSz, tc->pinCa ? f->interDer : NULL, caSz, tc,
rootDer, rootSz, tc->pinCa ? f->interDer : NULL, caSz,
(tc->leafOnly && tc->extraLevel) ? f->midDer : NULL, midSz, tc,
&hsRet, &verifyRet), TEST_SUCCESS);
}

Expand Down Expand Up @@ -2921,84 +2953,128 @@ int test_wolfSSL_chain_ca_ext_key_usage(void)
defined(USE_CERT_BUFFERS_2048) && !defined(IGNORE_KEY_EXTENSIONS)
static const test_eku_case cases[] = {
/* eku, selfSigned, selfIssued, clientPresents, pinCa, extraLevel,
* overrideCb, verifyDepth, expected handshake result, keyUsage. */
* overrideCb, verifyDepth, expected handshake result, keyUsage,
* leafOnly. */

/* Server authentication: a CA restricted to code signing, or to
* client authentication, must be refused. */
{ "codeSigning", 0, 0, 0, 0, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
NULL, 0 },
{ "clientAuth", 0, 0, 0, 0, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
NULL, 0 },
/* A CA that carries the purpose, or leaves it unrestricted, must not
* be over-rejected. */
{ "serverAuth", 0, 0, 0, 0, 0, 0, 0, 0, NULL },
{ NULL, 0, 0, 0, 0, 0, 0, 0, 0, NULL },
{ "any", 0, 0, 0, 0, 0, 0, 0, 0, NULL },
{ "serverAuth", 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0 },
{ NULL, 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0 },
{ "any", 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0 },
/* The rule applies to a CA the certificate manager already holds, not
* only to one seen for the first time. */
{ "codeSigning", 0, 0, 0, 1, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
{ "serverAuth", 0, 0, 0, 1, 0, 0, 0, 0, NULL },
NULL, 0 },
{ "serverAuth", 0, 0, 0, 1, 0, 0, 0, 0, NULL, 0 },
/* The peer sends only the leaf, so the CA comes from the certificate
* manager. The result must match the case where the CA is sent. */
{ "codeSigning", 0, 0, 0, 1, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL, 1 },
{ "clientAuth", 0, 0, 0, 1, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL, 1 },
{ "serverAuth", 0, 0, 0, 1, 0, 0, 0, 0, NULL, 1 },
{ NULL, 0, 0, 0, 1, 0, 0, 0, 0, NULL, 1 },
{ "any", 0, 0, 0, 1, 0, 0, 0, 0, NULL, 1 },
{ "codeSigning", 0, 0, 0, 1, 0, 1, 0, 0, NULL, 1 },
{ "codeSigning", 0, 0, 0, 1, 0, 2, 0, 0, NULL, 1 },
{ "codeSigning", 1, 0, 0, 0, 0, 0, 0, 0, NULL, 1 },
#ifndef IGNORE_NAME_CONSTRAINTS
/* The CA under test is above the middle CA and neither is sent. */
{ "codeSigning", 0, 0, 0, 1, 1, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL, 1 },
{ "serverAuth", 0, 0, 0, 1, 1, 0, 0, 0, NULL, 1 },
#endif
/* A self-signed trust anchor is exempt: here it is the anchor test,
* not the Extended Key Usage, that decides. */
{ "codeSigning", 1, 0, 0, 0, 0, 0, 0, 0, NULL },
{ "codeSigning", 1, 0, 0, 0, 0, 0, 0, 0, NULL, 0 },
/* A self-issued CA key rollover certificate is not an anchor. It
* carries the anchor's issuer and subject names, so DecodedCert's
* selfIssued flag is set, but it holds a different key and is signed
* by the anchor. The Extended Key Usage must still be enforced. */
{ "codeSigning", 0, 1, 0, 0, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
{ "serverAuth", 0, 1, 0, 0, 0, 0, 0, 0, NULL },
NULL, 0 },
{ "serverAuth", 0, 1, 0, 0, 0, 0, 0, 0, NULL, 0 },
/* Key Usage, not Extended Key Usage: the same rollover certificate
* cannot sign the leaf when its Key Usage omits keyCertSign. */
#ifndef ALLOW_INVALID_CERTSIGN
{ "serverAuth", 0, 1, 0, 0, 0, 0, 0,
WC_NO_ERR_TRACE(NOT_CA_ERROR),
"digitalSignature" },
"digitalSignature", 0 },
#endif
/* Enforcement is not limited to the CA directly above the leaf: here
* the CA under test issues "EKU Middle CA", which issues the leaf. */
{ "codeSigning", 0, 0, 0, 0, 1, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
{ "serverAuth", 0, 0, 0, 0, 1, 0, 0, 0, NULL },
NULL, 0 },
{ "serverAuth", 0, 0, 0, 0, 1, 0, 0, 0, NULL, 0 },
/* An application that installs a verify callback keeps the last word,
* as it does for every other chain error. */
{ "codeSigning", 0, 0, 0, 0, 0, 1, 0, 0, NULL },
{ "codeSigning", 0, 0, 0, 0, 0, 1, 0, 0, NULL, 0 },
/* The purpose error is accepted only at depth 1, where the chain CA
* is presented. The issuer walk must not ask again at depth 0. */
{ "codeSigning", 0, 0, 0, 0, 0, 3, 0, 0, NULL, 0 },
/* The same callback does not cover a CA the peer never sent. That
* error is reported for the leaf, at depth 0. */
{ "codeSigning", 0, 0, 0, 1, 0, 3, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL, 1 },
#ifdef OPENSSL_EXTRA
/* The purpose check must not stand in for the checks that run before
* it. With the depth limit pinned below the chain, a callback that
* waives only the purpose error still has to see the chain length
* rejection, whether or not the CA under test carries the purpose. */
{ "serverAuth", 0, 0, 0, 0, 1, 2, 1,
WC_NO_ERR_TRACE(MAX_CHAIN_ERROR),
NULL },
NULL, 0 },
{ "codeSigning", 0, 0, 0, 0, 1, 2, 1,
WC_NO_ERR_TRACE(MAX_CHAIN_ERROR),
NULL },
NULL, 0 },
#endif
#ifndef WOLFSSL_NO_CLIENT_AUTH
/* Client authentication: the server applies the same rule with the
* clientAuth purpose. */
{ "codeSigning", 0, 0, 1, 0, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
NULL, 0 },
{ "serverAuth", 0, 0, 1, 0, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
{ "clientAuth", 0, 0, 1, 0, 0, 0, 0, 0, NULL },
{ NULL, 0, 0, 1, 0, 0, 0, 0, 0, NULL },
{ "any", 0, 0, 1, 0, 0, 0, 0, 0, NULL },
NULL, 0 },
{ "clientAuth", 0, 0, 1, 0, 0, 0, 0, 0, NULL, 0 },
{ NULL, 0, 0, 1, 0, 0, 0, 0, 0, NULL, 0 },
{ "any", 0, 0, 1, 0, 0, 0, 0, 0, NULL, 0 },
{ "codeSigning", 0, 0, 1, 1, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL, 1 },
{ "serverAuth", 0, 0, 1, 1, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL, 1 },
{ "clientAuth", 0, 0, 1, 1, 0, 0, 0, 0, NULL, 1 },
{ NULL, 0, 0, 1, 1, 0, 0, 0, 0, NULL, 1 },
#ifndef IGNORE_NAME_CONSTRAINTS
{ "codeSigning", 0, 0, 1, 1, 1, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL, 1 },
{ "clientAuth", 0, 0, 1, 1, 1, 0, 0, 0, NULL, 1 },
#endif
/* The rollover shape is rejected in the client authentication
* direction too. */
{ "serverAuth", 0, 1, 1, 0, 0, 0, 0,
WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E),
NULL },
{ "clientAuth", 0, 1, 1, 0, 0, 0, 0, 0, NULL },
NULL, 0 },
{ "clientAuth", 0, 1, 1, 0, 0, 0, 0, 0, NULL, 0 },
#endif
};
test_eku_fixture fixture;
Expand Down
Loading
Loading