diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index f54edcd8e4..73e598c75a 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -1536,6 +1536,14 @@ void wolfSSL_CTX_set_cert_store(WOLFSSL_CTX* ctx, WOLFSSL_X509_STORE* str) ctx->x509_store_pt = str; /* Context has ownership and free it with context free. */ ctx->cm->x509_store_p = ctx->x509_store_pt; + +#ifdef OPENSSL_EXTRA + /* Non-self-signed certs (intermediates) added via + * X509_STORE_add_cert only go into store->certs, not the + * CertManager. Push them into the CM now so that all + * verification paths can find them. */ + X509StorePushCertsToCM(str); +#endif } } diff --git a/src/x509_str.c b/src/x509_str.c index b224901624..986862c81c 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -1561,6 +1561,69 @@ static int X509StoreAddCa(WOLFSSL_X509_STORE* store, return result; } +/* Push certificates from the store's X509 stacks (certs and trusted) into the + * CertManager, then free and NULL the stacks to signal that this store is now + * owned by an SSL_CTX. + * + * This is needed when an X509_STORE is attached to an SSL_CTX via + * SSL_CTX_set_cert_store: self-signed CAs are already in the CM (added by + * X509StoreAddCa during X509_STORE_add_cert), but non-self-signed intermediates + * are only in store->certs and must be explicitly added to the CM so that all + * verification paths (including CertManagerVerify) can find them. */ +WOLFSSL_LOCAL int X509StorePushCertsToCM(WOLFSSL_X509_STORE* store) +{ + int i; + int num; + int ret; + int anyFail = 0; + WOLFSSL_X509* x509; + + WOLFSSL_ENTER("X509StorePushCertsToCM"); + + if (store == NULL || store->cm == NULL) + return WOLFSSL_SUCCESS; + + /* Push non-self-signed intermediates from store->certs into the CM. */ + if (store->certs != NULL) { + num = wolfSSL_sk_X509_num(store->certs); + for (i = 0; i < num; i++) { + x509 = wolfSSL_sk_X509_value(store->certs, i); + if (x509 != NULL) { + ret = X509StoreAddCa(store, x509, WOLFSSL_USER_CA); + if (ret != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("X509StorePushCertsToCM: failed to add cert"); + anyFail = 1; + } + } + } + /* Free and NULL to mark store as CTX-owned. Future add_cert calls + * will go directly to the CertManager. */ + wolfSSL_sk_X509_pop_free(store->certs, NULL); + store->certs = NULL; + } + + /* Push trusted certs too. Self-signed CAs are typically already in the CM + * (added during X509_STORE_add_cert), but AddCA handles duplicates. */ + if (store->trusted != NULL) { + num = wolfSSL_sk_X509_num(store->trusted); + for (i = 0; i < num; i++) { + x509 = wolfSSL_sk_X509_value(store->trusted, i); + if (x509 != NULL) { + ret = X509StoreAddCa(store, x509, WOLFSSL_USER_CA); + if (ret != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("X509StorePushCertsToCM: failed to add " + "trusted cert"); + anyFail = 1; + } + } + } + wolfSSL_sk_X509_pop_free(store->trusted, NULL); + store->trusted = NULL; + } + + return anyFail ? WOLFSSL_FATAL_ERROR : WOLFSSL_SUCCESS; +} + int wolfSSL_X509_STORE_add_cert(WOLFSSL_X509_STORE* store, WOLFSSL_X509* x509) { int result = WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR); diff --git a/tests/api/test_ossl_x509_str.c b/tests/api/test_ossl_x509_str.c index 111994a347..f9e2dd8318 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -1794,3 +1794,118 @@ int test_X509_STORE_No_SSL_CTX(void) return EXPECT_RESULT(); } +/* Test that SSL_CTX_set_cert_store propagates certificates (including + * non-self-signed intermediates) into the CertManager, and that certs + * added to the store after set_cert_store also reach the CertManager. + * Regression test for ZD 19760 / GitHub PR #8708. + */ +int test_wolfSSL_CTX_set_cert_store(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) && \ + !defined(NO_WOLFSSL_CLIENT) + SSL_CTX* ctx = NULL; + X509_STORE* store = NULL; + X509* rootCa = NULL; + X509* intCa = NULL; + X509* int2Ca = NULL; + X509_STORE_CTX* storeCtx = NULL; + X509* svrCert = NULL; + + const char caCert[] = "./certs/ca-cert.pem"; + const char intCaCert[] = "./certs/intermediate/ca-int-cert.pem"; + const char int2CaCert[] = "./certs/intermediate/ca-int2-cert.pem"; + const char svrIntCert[] = "./certs/intermediate/server-int-cert.pem"; + + /* --- Part 1: Add certs to store BEFORE set_cert_store --- + * Non-self-signed intermediates should be pushed into the CertManager + * when set_cert_store is called. */ + ExpectNotNull(store = X509_STORE_new()); + ExpectNotNull(rootCa = wolfSSL_X509_load_certificate_file(caCert, + SSL_FILETYPE_PEM)); + ExpectNotNull(intCa = wolfSSL_X509_load_certificate_file(intCaCert, + SSL_FILETYPE_PEM)); + ExpectNotNull(int2Ca = wolfSSL_X509_load_certificate_file(int2CaCert, + SSL_FILETYPE_PEM)); + + ExpectIntEQ(X509_STORE_add_cert(store, rootCa), SSL_SUCCESS); + ExpectIntEQ(X509_STORE_add_cert(store, intCa), SSL_SUCCESS); + ExpectIntEQ(X509_STORE_add_cert(store, int2Ca), SSL_SUCCESS); + + ExpectNotNull(ctx = SSL_CTX_new(TLS_client_method())); + + /* This should push intermediates from store->certs into the CM */ + SSL_CTX_set_cert_store(ctx, store); + + /* After set_cert_store, store->certs and store->trusted should be NULLed + * to signal CTX ownership */ + if (EXPECT_SUCCESS()) { + ExpectNull(store->certs); + ExpectNull(store->trusted); + } + + /* Verify using CertManagerVerify - this only checks the CM, not the + * store's certs stack, so it proves the intermediates were pushed */ + ExpectIntEQ(wolfSSL_CertManagerVerify(wolfSSL_CTX_GetCertManager(ctx), + svrIntCert, SSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + + /* Also verify using X509_verify_cert for completeness */ + ExpectNotNull(svrCert = wolfSSL_X509_load_certificate_file(svrIntCert, + SSL_FILETYPE_PEM)); + ExpectNotNull(storeCtx = X509_STORE_CTX_new()); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(X509_STORE_CTX_init(storeCtx, + SSL_CTX_get_cert_store(ctx), svrCert, NULL), SSL_SUCCESS); + ExpectIntEQ(X509_verify_cert(storeCtx), SSL_SUCCESS); + } + + X509_STORE_CTX_free(storeCtx); + storeCtx = NULL; + X509_free(svrCert); + svrCert = NULL; + SSL_CTX_free(ctx); + ctx = NULL; + /* store is freed by SSL_CTX_free */ + store = NULL; + + X509_free(rootCa); + rootCa = NULL; + X509_free(intCa); + intCa = NULL; + X509_free(int2Ca); + int2Ca = NULL; + + /* --- Part 2: Add certs to store AFTER set_cert_store --- + * When store->certs is NULL (CTX-owned), X509_STORE_add_cert should + * route non-self-signed certs directly to the CertManager. */ + ExpectNotNull(store = X509_STORE_new()); + ExpectNotNull(ctx = SSL_CTX_new(TLS_client_method())); + + /* Attach empty store first */ + SSL_CTX_set_cert_store(ctx, store); + + /* Now add certs after ownership transfer */ + ExpectNotNull(rootCa = wolfSSL_X509_load_certificate_file(caCert, + SSL_FILETYPE_PEM)); + ExpectNotNull(intCa = wolfSSL_X509_load_certificate_file(intCaCert, + SSL_FILETYPE_PEM)); + ExpectNotNull(int2Ca = wolfSSL_X509_load_certificate_file(int2CaCert, + SSL_FILETYPE_PEM)); + + ExpectIntEQ(X509_STORE_add_cert(store, rootCa), SSL_SUCCESS); + ExpectIntEQ(X509_STORE_add_cert(store, intCa), SSL_SUCCESS); + ExpectIntEQ(X509_STORE_add_cert(store, int2Ca), SSL_SUCCESS); + + /* Verify that certs added after set_cert_store are in the CM */ + ExpectIntEQ(wolfSSL_CertManagerVerify(wolfSSL_CTX_GetCertManager(ctx), + svrIntCert, SSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + + SSL_CTX_free(ctx); + /* store freed by SSL_CTX_free */ + X509_free(rootCa); + X509_free(intCa); + X509_free(int2Ca); +#endif + return EXPECT_RESULT(); +} + diff --git a/tests/api/test_ossl_x509_str.h b/tests/api/test_ossl_x509_str.h index 9525a3a187..55812de730 100644 --- a/tests/api/test_ossl_x509_str.h +++ b/tests/api/test_ossl_x509_str.h @@ -41,6 +41,7 @@ int test_wolfSSL_X509_STORE_get1_certs(void); int test_wolfSSL_X509_STORE_set_get_crl(void); int test_wolfSSL_X509_CA_num(void); int test_X509_STORE_No_SSL_CTX(void); +int test_wolfSSL_CTX_set_cert_store(void); #define TEST_OSSL_X509_STORE_DECLS \ TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_STORE_CTX_set_time), \ @@ -63,6 +64,7 @@ int test_X509_STORE_No_SSL_CTX(void); TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_STORE_get1_certs), \ TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_STORE_set_get_crl), \ TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_CA_num), \ - TEST_DECL_GROUP("ossl_x509_store", test_X509_STORE_No_SSL_CTX) + TEST_DECL_GROUP("ossl_x509_store", test_X509_STORE_No_SSL_CTX), \ + TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_CTX_set_cert_store) #endif /* WOLFCRYPT_TEST_OSSL_X509_STR_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index e12bdc1239..8ee4ee51da 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2734,6 +2734,7 @@ WOLFSSL_LOCAL void CleanupStoreCtxCallback(WOLFSSL_X509_STORE_CTX* store, #endif /* !defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH) */ WOLFSSL_LOCAL int X509StoreLoadCertBuffer(WOLFSSL_X509_STORE *str, byte *buf, word32 bufLen, int type); +WOLFSSL_LOCAL int X509StorePushCertsToCM(WOLFSSL_X509_STORE* store); #endif /* !defined NO_CERTS */ /* wolfSSL Sock Addr */