From 3e30e69c35b4aee3f8cf95449baae820c5175bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 16 Jun 2026 14:02:00 +0200 Subject: [PATCH] certman: enforce keyCertSign usage on chain-supplied intermediate CAs Require the keyCertSign key usage on non-root intermediate CAs added during path building when a KeyUsage extension is present, per RFC 5280. Adds a regression test. --- src/ssl_certman.c | 11 +- tests/api/test_ossl_x509_str.c | 183 +++++++++++++++++++++++++++++++++ tests/api/test_ossl_x509_str.h | 3 + 3 files changed, 194 insertions(+), 3 deletions(-) diff --git a/src/ssl_certman.c b/src/ssl_certman.c index d10e79d56f..094051d290 100644 --- a/src/ssl_certman.c +++ b/src/ssl_certman.c @@ -3205,10 +3205,15 @@ int AddCA(WOLFSSL_CERT_MANAGER* cm, DerBuffer** pDer, int type, int verify) } #ifndef ALLOW_INVALID_CERTSIGN else if (ret == 0 && cert->isCA == 1 && type != WOLFSSL_USER_CA && - type != WOLFSSL_TEMP_CA && !cert->selfSigned && + !cert->selfSigned && cert->extKeyUsageSet && (cert->extKeyUsage & KEYUSE_KEY_CERT_SIGN) == 0) { - /* Intermediate CA certs are required to have the keyCertSign - * extension set. User loaded root certs are not. */ + /* Intermediate CA certs - including chain-supplied temporary CAs + * (WOLFSSL_TEMP_CA) added while building a path - are required to have + * the keyCertSign key usage when a Key Usage extension is present. + * Only operator-loaded root certs (WOLFSSL_USER_CA) and self-signed + * roots are exempt. Per RFC 5280 an absent Key Usage extension implies + * all usages, so only enforce this when the extension is actually + * present (extKeyUsageSet). */ WOLFSSL_MSG("\tDoesn't have key usage certificate signing"); ret = NOT_CA_ERROR; } diff --git a/tests/api/test_ossl_x509_str.c b/tests/api/test_ossl_x509_str.c index 964c862a6a..2f1af0cc37 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -1507,6 +1507,189 @@ int test_X509_verify_cert_untrusted_inter(void) return EXPECT_RESULT(); } +#if defined(OPENSSL_EXTRA) && !defined(NO_RSA) && !defined(NO_CERTS) && \ + defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) && \ + !defined(NO_SHA256) && defined(USE_CERT_BUFFERS_2048) && \ + !defined(NO_ASN_TIME) +/* Build a CA:TRUE intermediate signed by the 2048-bit test root + * (ca_cert_der_2048 / ca_key_der_2048). keyUsage == NULL omits the KeyUsage + * extension entirely. Returns the DER length, or <= 0 on failure. */ +static int gen_ca_int_keyusage(byte* out, int outMax, RsaKey* subjKey, + RsaKey* caKey, WC_RNG* rng, const char* cn, const char* keyUsage) +{ + Cert cert; + int sz; + + 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) + return -1; + if (wc_SetAuthKeyIdFromCert(&cert, ca_cert_der_2048, + (int)sizeof_ca_cert_der_2048) != 0) + return -1; + if (keyUsage != NULL && wc_SetKeyUsage(&cert, keyUsage) != 0) + return -1; + if (wc_SetIssuerBuffer(&cert, ca_cert_der_2048, + (int)sizeof_ca_cert_der_2048) != 0) + return -1; + if ((sz = wc_MakeCert(&cert, out, (word32)outMax, subjKey, NULL, rng)) < 0) + return -1; + return wc_SignCert(cert.bodySz, cert.sigType, out, (word32)outMax, caKey, + NULL, rng); +} + +/* Build a leaf signed by the given intermediate (its DER + private key). */ +static int gen_leaf_under_int(byte* out, int outMax, RsaKey* leafKey, + const byte* issuerDer, int issuerDerSz, RsaKey* issuerKey, WC_RNG* rng, + const char* cn) +{ + Cert cert; + int sz; + + 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, cn, CTC_NAME_SIZE - 1); + if (wc_SetSubjectKeyIdFromPublicKey(&cert, leafKey, NULL) != 0) + return -1; + if (wc_SetAuthKeyIdFromCert(&cert, issuerDer, issuerDerSz) != 0) + return -1; + if (wc_SetKeyUsage(&cert, "digitalSignature") != 0) + return -1; + if (wc_SetIssuerBuffer(&cert, issuerDer, issuerDerSz) != 0) + return -1; + if ((sz = wc_MakeCert(&cert, out, (word32)outMax, leafKey, NULL, rng)) < 0) + return -1; + return wc_SignCert(cert.bodySz, cert.sigType, out, (word32)outMax, + issuerKey, NULL, rng); +} + +/* Verify "leaf <- intermediate <- root(ca-cert)" where the intermediate is + * supplied only as an untrusted candidate. Returns the X509_verify_cert() + * result (1 verified, 0 rejected) via *verifyRet. */ +static int run_int_keyusage_case(const byte* intDer, int intSz, + const byte* leafDer, int leafSz, X509* root, int* verifyRet) +{ + EXPECT_DECLS; + X509* inter = NULL; + X509* leaf = NULL; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* untrusted = NULL; + const byte* p; + + p = intDer; + ExpectNotNull(inter = d2i_X509(NULL, &p, intSz)); + p = leafDer; + ExpectNotNull(leaf = d2i_X509(NULL, &p, leafSz)); + ExpectNotNull(store = X509_STORE_new()); + ExpectIntEQ(X509_STORE_add_cert(store, root), 1); + ExpectNotNull(untrusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(untrusted, inter), 0); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, untrusted), 1); + if (verifyRet != NULL) + *verifyRet = X509_verify_cert(ctx); + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + sk_X509_free(untrusted); + X509_free(leaf); + X509_free(inter); + return EXPECT_RESULT(); +} +#endif + +/* Regression: a chain-supplied (untrusted) intermediate that is CA:TRUE but + * whose KeyUsage extension does NOT assert keyCertSign must NOT be usable to + * sign the leaf - RFC 5280 4.2.1.3. Previously such an intermediate, added as + * a temporary CA during path building, was accepted as a signing CA. + * + * Conversely, an intermediate with NO KeyUsage extension implies all usages + * (including keyCertSign) and must still verify - the fix must not over-reject + * those. */ +int test_X509_verify_cert_ca_no_keycertsign(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_RSA) && !defined(NO_CERTS) && \ + defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) && \ + !defined(NO_SHA256) && defined(USE_CERT_BUFFERS_2048) && \ + !defined(NO_ASN_TIME) + WC_RNG rng; + RsaKey caKey, intKey, leafKey; + int rngI = 0, caI = 0, intI = 0, leafI = 0; + word32 idx; + byte* intDer = NULL; + byte* leafDer = NULL; + int intSz = 0, leafSz = 0; + int verifyRet = -1; + const byte* p; + X509* root = NULL; + + intDer = (byte*)XMALLOC(FOURK_BUF, NULL, DYNAMIC_TYPE_TMP_BUFFER); + leafDer = (byte*)XMALLOC(FOURK_BUF, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(intDer); + ExpectNotNull(leafDer); + + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) rngI = 1; + ExpectIntEQ(wc_InitRsaKey(&caKey, NULL), 0); + if (EXPECT_SUCCESS()) caI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(ca_key_der_2048, &idx, &caKey, + sizeof_ca_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&intKey, NULL), 0); + if (EXPECT_SUCCESS()) intI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(server_key_der_2048, &idx, &intKey, + sizeof_server_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&leafKey, NULL), 0); + if (EXPECT_SUCCESS()) leafI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(client_key_der_2048, &idx, &leafKey, + sizeof_client_key_der_2048), 0); + + p = ca_cert_der_2048; + ExpectNotNull(root = d2i_X509(NULL, &p, (int)sizeof_ca_cert_der_2048)); + + /* Case 1: intermediate CA WITHOUT keyCertSign -> verification must fail. */ + ExpectIntGT((intSz = gen_ca_int_keyusage(intDer, FOURK_BUF, &intKey, &caKey, + &rng, "No keyCertSign Intermediate", "digitalSignature")), 0); + ExpectIntGT((leafSz = gen_leaf_under_int(leafDer, FOURK_BUF, &leafKey, + intDer, intSz, &intKey, &rng, "Leaf under bad int")), 0); + verifyRet = -1; + ExpectIntEQ(run_int_keyusage_case(intDer, intSz, leafDer, leafSz, root, + &verifyRet), 1); + ExpectIntNE(verifyRet, 1); + + /* Case 2: intermediate CA with NO KeyUsage extension -> must verify. */ + ExpectIntGT((intSz = gen_ca_int_keyusage(intDer, FOURK_BUF, &intKey, &caKey, + &rng, "No KeyUsage Intermediate", NULL)), 0); + ExpectIntGT((leafSz = gen_leaf_under_int(leafDer, FOURK_BUF, &leafKey, + intDer, intSz, &intKey, &rng, "Leaf under noKU int")), 0); + verifyRet = -1; + ExpectIntEQ(run_int_keyusage_case(intDer, intSz, leafDer, leafSz, root, + &verifyRet), 1); + ExpectIntEQ(verifyRet, 1); + + X509_free(root); + if (rngI) wc_FreeRng(&rng); + if (caI) wc_FreeRsaKey(&caKey); + if (intI) wc_FreeRsaKey(&intKey); + if (leafI) wc_FreeRsaKey(&leafKey); + XFREE(intDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(leafDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + #if defined(OPENSSL_EXTRA) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) static int test_X509_STORE_untrusted_load_cert_to_stack(const char* filename, STACK_OF(X509)* chain) diff --git a/tests/api/test_ossl_x509_str.h b/tests/api/test_ossl_x509_str.h index e1024deadd..3b13fa13b7 100644 --- a/tests/api/test_ossl_x509_str.h +++ b/tests/api/test_ossl_x509_str.h @@ -30,6 +30,7 @@ int test_wolfSSL_X509_STORE_CTX_get0_store(void); int test_wolfSSL_X509_STORE_CTX(void); int test_wolfSSL_X509_STORE_CTX_ex(void); int test_X509_verify_cert_untrusted_inter(void); +int test_X509_verify_cert_ca_no_keycertsign(void); int test_X509_STORE_untrusted(void); int test_X509_STORE_InvalidCa(void); int test_X509_STORE_InvalidCa_NoCallback(void); @@ -53,6 +54,8 @@ int test_wolfSSL_CTX_set_cert_store(void); TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_STORE_CTX), \ TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_STORE_CTX_ex), \ TEST_DECL_GROUP("ossl_x509_store", test_X509_verify_cert_untrusted_inter), \ + TEST_DECL_GROUP("ossl_x509_store", \ + test_X509_verify_cert_ca_no_keycertsign), \ TEST_DECL_GROUP("ossl_x509_store", test_X509_STORE_untrusted), \ TEST_DECL_GROUP("ossl_x509_store", test_X509_STORE_InvalidCa), \ TEST_DECL_GROUP("ossl_x509_store", test_X509_STORE_InvalidCa_NoCallback), \