diff --git a/ChangeLog.md b/ChangeLog.md index 55652f4971..a222dd2b65 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -150,6 +150,33 @@ ## Fixes +* **Fix (Extended Key Usage not enforced on chain-supplied intermediate CAs)**: + the TLS peer certificate was checked for the `serverAuth` or `clientAuth` + Extended Key Usage, but the intermediate CAs sent alongside it were not. A + certificate authority restricted to another purpose by a critical EKU, a code + signing, S/MIME or timestamping subordinate CA for example, could therefore + issue a `serverAuth` leaf for any name and have wolfSSL complete the + handshake, defeating the isolation such a constrained CA exists to provide. + `ProcessPeerCerts()` now applies the same purpose check to every + chain-supplied CA it validates, whether or not the certificate manager + already holds it, and fails the handshake with `EXTKEYUSE_AUTH_E` when the CA + does not carry the purpose being validated. Per RFC 5280 4.2.1.12 an absent + extension leaves all purposes valid and `anyExtendedKeyUsage` removes the + restriction, so neither is rejected, and a self-signed certificate is exempt + because it can only take part in a path as a trust anchor the operator chose + to load. The check applies to the certificates the peer transmits; an issuer + resolved from the certificate manager because the peer did not send it is + not covered. This is stricter than before, in three cases that previously + succeeded: a chain whose intermediate asserts an Extended Key Usage without + the purpose in use, `serverAuth` only on a CA that also issues client + certificates for instance; a chain whose intermediate asserts only + KeyPurposeIds wolfSSL does not recognise, since those set no bit; and a + chain whose intermediate the operator loaded as a trusted CA, which is held + to the same rule as any other chain CA. `IGNORE_KEY_EXTENSIONS` opts out, as + it already did for the peer certificate. Adds + `WOLFSSL_X509_V_ERR_INVALID_PURPOSE`, reported through + `wolfSSL_get_verify_result()` and to verify callbacks. + * **Fix (certificate manager left pointing at a released store)**: `wolfSSL_CTX_set_cert_store()` pairs the store handed to it with the context's certificate manager, which keeps a pointer back to that store. diff --git a/src/internal.c b/src/internal.c index 271c941e98..dafec02b35 100644 --- a/src/internal.c +++ b/src/internal.c @@ -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); + 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"; diff --git a/src/x509_str.c b/src/x509_str.c index e9ee28e45b..857fa44566 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -324,6 +324,8 @@ int GetX509Error(int e) return WOLFSSL_X509_V_ERR_CERT_REVOKED; case WC_NO_ERR_TRACE(CRL_MISSING): return WOLFSSL_X509_V_ERR_UNABLE_TO_GET_CRL; + case WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E): + return WOLFSSL_X509_V_ERR_INVALID_PURPOSE; /* is an internal wolfSSL return code, not an X509_V_* code, so 1 * here is WOLFSSL_SUCCESS - it does not collide with * WOLFSSL_X509_V_ERR_UNSPECIFIED, which shares the value but never diff --git a/tests/api.c b/tests/api.c index 5ba7f90b8f..08a06ba9b3 100644 --- a/tests/api.c +++ b/tests/api.c @@ -31269,7 +31269,7 @@ static int error_test(void) {17, 15}, {19, 19}, {24, 24}, - {27, 26 }, + {27, 27}, {61, 30}, {63, 63}, {78, 65}, diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 22cdc54803..11a0de201b 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -32,6 +32,7 @@ #include #include +#include #include /* Tests for the certificate APIs in src/ssl_api_cert.c (moved from ssl.c). */ @@ -1496,3 +1497,396 @@ int test_wolfSSL_cert_unload(void) #endif return EXPECT_RESULT(); } + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_RSA) && \ + !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) + +#define TEST_EKU_CERT_BUF_SZ (2 * FOURK_BUF) + +/* Keys and scratch buffers shared by every case. */ +typedef struct test_eku_fixture { + RsaKey* caKey; /* the 2048-bit test root */ + RsaKey* midKey; /* CA between the root and the CA under test */ + RsaKey* intKey; /* the CA whose Extended Key Usage is under test */ + RsaKey* leafKey; + WC_RNG* rng; + byte* midDer; + byte* interDer; + byte* chainDer; +} test_eku_fixture; + +/* One chain Extended Key Usage scenario. */ +typedef struct test_eku_case { + const char* extKeyUsage; /* Extended Key Usage on the CA under test, + * NULL omits the extension */ + int selfSignedCa; /* CA under test is self-signed and is itself + * the trust anchor */ + int clientPresents; /* client sends the chain, server verifies it */ + int pinCa; /* CA under test is also loaded as a trusted + * CA before the handshake */ + int extraLevel; /* insert a CA between the root and the CA + * under test, putting it at depth 2 */ + int overrideCb; /* verify callback accepts what the library + * rejected */ + int expectRet; /* expected handshake result */ +} test_eku_case; + +/* Stand in for an application that knowingly accepts a rejected certificate. */ +static int test_eku_override_cb(int preverify, WOLFSSL_X509_STORE_CTX* store) +{ + (void)preverify; + (void)store; + return 1; +} + +/* 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. Returns the + * DER length, or < 0 on failure. */ +static int test_eku_gen_ca(byte* out, int outMax, RsaKey* subjKey, + const byte* issuerDer, int issuerDerSz, RsaKey* issuerKey, WC_RNG* rng, + const char* extKeyUsage, const char* cn) +{ + Cert cert; + int ret = 0; + + if (wc_InitCert(&cert) != 0) + return -1; + cert.isCA = 1; + cert.sigType = CTC_SHA256wRSA; + XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.org, "wolfSSL_test", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.commonName, cn, CTC_NAME_SIZE - 1); + if (wc_SetSubjectKeyIdFromPublicKey(&cert, subjKey, NULL) != 0) + ret = -1; + if (ret == 0 && wc_SetKeyUsage(&cert, "keyCertSign,cRLSign") != 0) + ret = -1; + if (ret == 0 && extKeyUsage != NULL && + wc_SetExtKeyUsage(&cert, extKeyUsage) != 0) + ret = -1; + /* wc_InitCert() leaves selfSigned set, so naming no issuer is what makes + * the generated certificate self-signed. */ + if (ret == 0 && issuerDer != NULL) { + if (wc_SetAuthKeyIdFromCert(&cert, issuerDer, issuerDerSz) != 0) + ret = -1; + if (ret == 0 && wc_SetIssuerBuffer(&cert, issuerDer, issuerDerSz) != 0) + ret = -1; + } + if (ret == 0) + ret = wc_MakeCert(&cert, out, (word32)outMax, subjKey, NULL, rng); + if (ret >= 0) + ret = wc_SignCert(cert.bodySz, cert.sigType, out, (word32)outMax, + (issuerDer == NULL) ? subjKey : issuerKey, NULL, rng); +#ifdef WOLFSSL_CERT_GEN_CACHE + wc_SetCert_Free(&cert); +#endif + return ret; +} + +/* Build a TLS leaf signed by the given CA. The leaf carries both serverAuth + * and clientAuth so only the issuing CA's purpose is tested. */ +static int test_eku_gen_leaf(byte* out, int outMax, RsaKey* leafKey, + const byte* issuerDer, int issuerDerSz, RsaKey* issuerKey, WC_RNG* rng) +{ + Cert cert; + int ret = 0; + + if (wc_InitCert(&cert) != 0) + return -1; + cert.isCA = 0; + cert.sigType = CTC_SHA256wRSA; + XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.org, "wolfSSL_test", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.commonName, "EKU Leaf", CTC_NAME_SIZE - 1); + if (wc_SetSubjectKeyIdFromPublicKey(&cert, leafKey, NULL) != 0) + ret = -1; + if (ret == 0 && wc_SetAuthKeyIdFromCert(&cert, issuerDer, issuerDerSz) != 0) + ret = -1; + if (ret == 0 && + wc_SetKeyUsage(&cert, "digitalSignature,keyEncipherment") != 0) + ret = -1; + if (ret == 0 && wc_SetExtKeyUsage(&cert, "serverAuth,clientAuth") != 0) + ret = -1; + if (ret == 0 && wc_SetIssuerBuffer(&cert, issuerDer, issuerDerSz) != 0) + ret = -1; + if (ret == 0) + ret = wc_MakeCert(&cert, out, (word32)outMax, leafKey, NULL, rng); + if (ret >= 0) + ret = wc_SignCert(cert.bodySz, cert.sigType, out, (word32)outMax, + issuerKey, NULL, rng); +#ifdef WOLFSSL_CERT_GEN_CACHE + wc_SetCert_Free(&cert); +#endif + return ret; +} + +/* Run a memio handshake in which one side presents the given chain and the + * peer verifies it against rootDer. When clientPresents is set the client sends + * the chain and the server verifies it, which is the client authentication + * direction. A non-NULL pinnedDer is loaded as an additional trusted CA on the + * verifying side, so the chain CA is already a known signer when it arrives. + * The handshake result and the compatibility-layer verify result are returned + * 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) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL_CTX* verifyCtx = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL* verifySsl = NULL; + VerifyCallback verifyCb = NULL; + struct test_memio_ctx test_ctx; + + if (tc->overrideCb) + verifyCb = test_eku_override_cb; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* The server always presents the generated chain so a single set of + * credentials covers both directions. */ + ExpectIntEQ(test_memio_setup_ex(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfSSLv23_server_method, + (byte*)rootDer, rootSz, (byte*)chainDer, chainSz, + (byte*)client_key_der_2048, (int)sizeof_client_key_der_2048), 0); + + if (tc->clientPresents) { + /* Leave the server chain unverified so only the server's view of the + * client chain decides the handshake. */ + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_use_certificate_chain_buffer_format(ssl_c, chainDer, + (long)chainSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_buffer(ssl_c, client_key_der_2048, + (long)sizeof_client_key_der_2048, WOLFSSL_FILETYPE_ASN1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_buffer(ctx_s, rootDer, + (long)rootSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, verifyCb); + verifyCtx = ctx_s; + verifySsl = ssl_s; + } + else { + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, verifyCb); + verifyCtx = ctx_c; + verifySsl = ssl_c; + } + + if (pinnedDer != NULL) { + ExpectIntEQ(wolfSSL_CTX_load_verify_buffer(verifyCtx, pinnedDer, + (long)pinnedSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + } + + /* The harness only reports pass or fail, so read the reason from the side + * that did the verifying. */ + if (EXPECT_SUCCESS()) { + if (test_memio_do_handshake(ssl_c, ssl_s, 10, NULL) == 0) + *hsRet = 0; + else + *hsRet = wolfSSL_get_error(verifySsl, WOLFSSL_FATAL_ERROR); + #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + *verifyRet = wolfSSL_get_verify_result(verifySsl); + #endif + } + (void)verifyRet; + + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + + return EXPECT_RESULT(); +} + +/* Build the chain for one scenario, run the handshake and check both the + * handshake result and the verify result it is reported as. */ +static int test_eku_chain_case(const test_eku_fixture* f, + const test_eku_case* tc) +{ + EXPECT_DECLS; + const byte* rootDer = ca_cert_der_2048; + const byte* issuerDer = ca_cert_der_2048; + int rootSz = (int)sizeof_ca_cert_der_2048; + int issuerSz = (int)sizeof_ca_cert_der_2048; + RsaKey* issuerKey = f->caKey; + int midSz = 0; + int caSz = 0; + int leafSz = 0; + int chainSz = 0; + int hsRet = -1; + long verifyRet = -1; + + /* An extra level puts the CA under test at depth 2, below a CA that does + * carry the TLS purposes. */ + if (tc->extraLevel) { + ExpectIntGT((midSz = test_eku_gen_ca(f->midDer, TEST_EKU_CERT_BUF_SZ, + f->midKey, ca_cert_der_2048, (int)sizeof_ca_cert_der_2048, + f->caKey, f->rng, "serverAuth,clientAuth", "EKU Middle CA")), 0); + issuerDer = f->midDer; + issuerSz = midSz; + issuerKey = f->midKey; + } + + ExpectIntGT((caSz = test_eku_gen_ca(f->interDer, TEST_EKU_CERT_BUF_SZ, + f->intKey, tc->selfSignedCa ? NULL : issuerDer, issuerSz, issuerKey, + f->rng, tc->extKeyUsage, + tc->selfSignedCa ? "EKU Self Signed CA" : "EKU Intermediate")), 0); + ExpectIntGT((leafSz = test_eku_gen_leaf(f->chainDer, TEST_EKU_CERT_BUF_SZ, + f->leafKey, f->interDer, caSz, f->intKey, f->rng)), 0); + /* The chain buffer holds the leaf first, then its issuers in order. */ + ExpectIntLE(leafSz + caSz + midSz, TEST_EKU_CERT_BUF_SZ); + + if (EXPECT_SUCCESS()) { + XMEMCPY(f->chainDer + leafSz, f->interDer, (size_t)caSz); + chainSz = leafSz + caSz; + if (tc->extraLevel) { + XMEMCPY(f->chainDer + chainSz, f->midDer, (size_t)midSz); + chainSz += midSz; + } + 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, + &hsRet, &verifyRet), TEST_SUCCESS); + } + + ExpectIntEQ(hsRet, tc->expectRet); +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + /* An override leaves the rejection already recorded in place, so only the + * cases the library decided on its own have a predictable verify result. */ + if (!tc->overrideCb) { + ExpectIntEQ(verifyRet, (tc->expectRet == 0) ? + WOLFSSL_X509_V_OK : WOLFSSL_X509_V_ERR_INVALID_PURPOSE); + } +#endif + + return EXPECT_RESULT(); +} +#endif /* chain EKU test dependencies */ + +/* Test that the Extended Key Usage of a chain-supplied intermediate CA is + * enforced against the TLS purpose being validated, per RFC 5280 4.2.1.12. + * + * A CA restricted to some other purpose, code signing here, must not be able to + * authenticate a TLS peer even though the leaf below it asks for serverAuth. + * An absent extension and anyExtendedKeyUsage both leave every purpose valid + * and must still complete the handshake, and a self-signed trust anchor is + * exempt whatever its Extended Key Usage says. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_chain_ca_ext_key_usage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_RSA) && \ + !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[] = { + /* eku, selfSigned, clientPresents, pinCa, extraLevel, overrideCb, + * expected handshake result. */ + + /* Server authentication: a CA restricted to code signing, or to + * client authentication, must be refused. */ + { "codeSigning", 0, 0, 0, 0, 0, WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E) }, + { "clientAuth", 0, 0, 0, 0, 0, WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E) }, + /* A CA that carries the purpose, or leaves it unrestricted, must not + * be over-rejected. */ + { "serverAuth", 0, 0, 0, 0, 0, 0 }, + { NULL, 0, 0, 0, 0, 0, 0 }, + { "any", 0, 0, 0, 0, 0, 0 }, + /* The rule applies to a CA the certificate manager already holds, not + * only to one seen for the first time. */ + { "codeSigning", 0, 0, 1, 0, 0, WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E) }, + { "serverAuth", 0, 0, 1, 0, 0, 0 }, + /* A self-signed trust anchor is exempt: here it is the selfSigned + * test, not the Extended Key Usage, that decides. */ + { "codeSigning", 1, 0, 0, 0, 0, 0 }, + /* Enforcement is not limited to the CA directly above the leaf. */ + { "codeSigning", 0, 0, 0, 1, 0, WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E) }, + { "serverAuth", 0, 0, 0, 1, 0, 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, 1, 0 }, +#ifndef WOLFSSL_NO_CLIENT_AUTH + /* Client authentication: the server applies the same rule with the + * clientAuth purpose. */ + { "codeSigning", 0, 1, 0, 0, 0, WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E) }, + { "serverAuth", 0, 1, 0, 0, 0, WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E) }, + { "clientAuth", 0, 1, 0, 0, 0, 0 }, + { NULL, 0, 1, 0, 0, 0, 0 }, + { "any", 0, 1, 0, 0, 0, 0 }, +#endif + }; + test_eku_fixture fixture; + WC_RNG rng; + RsaKey caKey; + RsaKey midKey; + RsaKey intKey; + RsaKey leafKey; + int rngInit = 0; + int caInit = 0; + int midInit = 0; + int intInit = 0; + int leafInit = 0; + word32 idx; + size_t i; + + XMEMSET(&fixture, 0, sizeof(fixture)); + ExpectNotNull(fixture.midDer = (byte*)XMALLOC(TEST_EKU_CERT_BUF_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(fixture.interDer = (byte*)XMALLOC(TEST_EKU_CERT_BUF_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(fixture.chainDer = (byte*)XMALLOC(TEST_EKU_CERT_BUF_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) rngInit = 1; + ExpectIntEQ(wc_InitRsaKey(&caKey, NULL), 0); + if (EXPECT_SUCCESS()) caInit = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(ca_key_der_2048, &idx, &caKey, + (word32)sizeof_ca_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&midKey, NULL), 0); + if (EXPECT_SUCCESS()) midInit = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(rsa_key_der_2048, &idx, &midKey, + (word32)sizeof_rsa_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&intKey, NULL), 0); + if (EXPECT_SUCCESS()) intInit = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(server_key_der_2048, &idx, &intKey, + (word32)sizeof_server_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&leafKey, NULL), 0); + if (EXPECT_SUCCESS()) leafInit = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(client_key_der_2048, &idx, &leafKey, + (word32)sizeof_client_key_der_2048), 0); + + fixture.caKey = &caKey; + fixture.midKey = &midKey; + fixture.intKey = &intKey; + fixture.leafKey = &leafKey; + fixture.rng = &rng; + + for (i = 0; i < XELEM_CNT(cases) && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_eku_chain_case(&fixture, &cases[i]), TEST_SUCCESS); + } + + if (rngInit) wc_FreeRng(&rng); + if (caInit) wc_FreeRsaKey(&caKey); + if (midInit) wc_FreeRsaKey(&midKey); + if (intInit) wc_FreeRsaKey(&intKey); + if (leafInit) wc_FreeRsaKey(&leafKey); + XFREE(fixture.midDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(fixture.interDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(fixture.chainDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_ssl_cert.h b/tests/api/test_ssl_cert.h index c57cfef820..07f3bc4e75 100644 --- a/tests/api/test_ssl_cert.h +++ b/tests/api/test_ssl_cert.h @@ -47,6 +47,7 @@ int test_wolfSSL_CTX_cert_store_manager_link(void); int test_wolfSSL_cert_cb_ctx(void); int test_wolfSSL_get_certificate_api(void); int test_wolfSSL_cert_unload(void); +int test_wolfSSL_chain_ca_ext_key_usage(void); #define TEST_SSL_CERT_DECLS \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_get_verify_mode), \ @@ -75,6 +76,8 @@ int test_wolfSSL_cert_unload(void); test_wolfSSL_CTX_cert_store_manager_link), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_cert_cb_ctx), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_get_certificate_api), \ - TEST_DECL_GROUP("ssl_cert", test_wolfSSL_cert_unload) + TEST_DECL_GROUP("ssl_cert", test_wolfSSL_cert_unload), \ + TEST_DECL_GROUP("ssl_cert", \ + test_wolfSSL_chain_ca_ext_key_usage) #endif /* TESTS_API_SSL_CERT_H */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 8e6c7560ee..64436ec03c 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -169,10 +169,12 @@ ASN Options: * IGNORE_KEY_EXTENSIONS: Opt-in, RFC non-conformant. Suppress all key-usage * and extended-key-usage enforcement: the TLS * keyEncipherment/digitalSignature checks and the - * serverAuth/clientAuth EKU checks (ProcessPeerCerts, - * src/internal.c), and the cRLSign requirement on a - * CRL-signing CA (VerifyCRL_Signature, below). Off by - * default; enforcement is active in a stock build. + * serverAuth/clientAuth EKU checks on both the peer + * certificate and the chain-supplied intermediate + * CAs (ProcessPeerCerts, src/internal.c), and the + * cRLSign requirement on a CRL-signing CA + * (VerifyCRL_Signature, below). Off by default; + * enforcement is active in a stock build. * IGNORE_NETSCAPE_CERT_TYPE: Ignore Netscape cert type extension * WOLFSSL_ALLOW_CRIT_AIA: Allow critical Authority Info Access * WOLFSSL_ALLOW_CRIT_AKID: Allow critical Auth Key Identifier diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index e1e12f3d34..6672ac536a 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2825,6 +2825,7 @@ enum { WOLFSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG = 22, WOLFSSL_X509_V_ERR_CERT_REVOKED = 23, WOLFSSL_X509_V_ERR_PATH_LENGTH_EXCEEDED = 25, + WOLFSSL_X509_V_ERR_INVALID_PURPOSE = 26, WOLFSSL_X509_V_ERR_CERT_REJECTED = 28, WOLFSSL_X509_V_ERR_SUBJECT_ISSUER_MISMATCH = 29, WOLFSSL_X509_V_ERR_APPLICATION_VERIFICATION = 50,