From 7336ed7cee5e02f7d0cc35c470c200f0f6bdcee7 Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Tue, 24 Mar 2026 15:12:25 -0400 Subject: [PATCH 1/2] Fixes ZD 19760 Before this fix, the certificates were not getting into the certificate manager. This makes sure they are going in. --- src/ssl_api_cert.c | 8 ++ src/x509_str.c | 63 +++++++++++++ tests/api/test_ossl_x509_str.c | 166 +++++++++++++++++++++++---------- tests/api/test_ossl_x509_str.h | 4 +- wolfssl/internal.h | 1 + 5 files changed, 191 insertions(+), 51 deletions(-) diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index f54edcd8e43..73e598c75ac 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 90113caede2..aa74267c4d1 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -1598,6 +1598,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 99b82877c30..f9e2dd8318c 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -1074,56 +1074,6 @@ int test_X509_STORE_InvalidCa(void) ExpectIntEQ(X509_STORE_CTX_init(ctx, str, cert, untrusted), 1); ExpectIntEQ(X509_verify_cert(ctx), 1); ExpectIntEQ(last_errcode, X509_V_ERR_INVALID_CA); - /* Defense in depth: ctx->error must not be clobbered back to X509_V_OK - * by the later successful verification of the intermediate against the - * trusted root. The worst-seen error must persist. */ - ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_ERR_INVALID_CA); - - X509_free(cert); - X509_STORE_free(str); - X509_STORE_CTX_free(ctx); - sk_X509_pop_free(untrusted, NULL); -#endif - return EXPECT_RESULT(); -} - -int test_X509_STORE_InvalidCa_NoCallback(void) -{ - EXPECT_DECLS; -#if defined(OPENSSL_ALL) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) - const char* filename = "./certs/intermediate/ca_false_intermediate/" - "test_int_not_cacert.pem"; - const char* srvfile = "./certs/intermediate/ca_false_intermediate/" - "test_sign_bynoca_srv.pem"; - X509_STORE_CTX* ctx = NULL; - X509_STORE* str = NULL; - XFILE fp = XBADFILE; - X509* cert = NULL; - STACK_OF(X509)* untrusted = NULL; - - ExpectTrue((fp = XFOPEN(srvfile, "rb")) - != XBADFILE); - ExpectNotNull(cert = PEM_read_X509(fp, 0, 0, 0 )); - if (fp != XBADFILE) { - XFCLOSE(fp); - fp = XBADFILE; - } - - ExpectNotNull(str = X509_STORE_new()); - ExpectNotNull(ctx = X509_STORE_CTX_new()); - ExpectNotNull(untrusted = sk_X509_new_null()); - - /* Create cert chain stack with an intermediate that is CA:FALSE. */ - ExpectIntEQ(test_X509_STORE_untrusted_load_cert_to_stack(filename, - untrusted), TEST_SUCCESS); - - ExpectIntEQ(X509_STORE_load_locations(str, - "./certs/intermediate/ca_false_intermediate/test_ca.pem", - NULL), 1); - ExpectIntEQ(X509_STORE_CTX_init(ctx, str, cert, untrusted), 1); - /* No verify callback: verification must fail on CA:FALSE issuer. */ - ExpectIntNE(X509_verify_cert(ctx), 1); - ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_ERR_INVALID_CA); X509_free(cert); X509_STORE_free(str); @@ -1843,3 +1793,119 @@ int test_X509_STORE_No_SSL_CTX(void) #endif 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 67370464533..a3fe8bfebdf 100644 --- a/tests/api/test_ossl_x509_str.h +++ b/tests/api/test_ossl_x509_str.h @@ -42,6 +42,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), \ @@ -65,6 +66,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 5e512f76eb4..ae13cdb552a 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2730,6 +2730,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 */ From ce226a8ad7fc974c8c05a9c76f9c453ed179b855 Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Fri, 10 Apr 2026 12:15:04 -0400 Subject: [PATCH 2/2] Fix bad merge in tests --- tests/api/test_ossl_x509_str.c | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/api/test_ossl_x509_str.c b/tests/api/test_ossl_x509_str.c index f9e2dd8318c..6beaa37d1ee 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -1074,6 +1074,56 @@ int test_X509_STORE_InvalidCa(void) ExpectIntEQ(X509_STORE_CTX_init(ctx, str, cert, untrusted), 1); ExpectIntEQ(X509_verify_cert(ctx), 1); ExpectIntEQ(last_errcode, X509_V_ERR_INVALID_CA); + /* Defense in depth: ctx->error must not be clobbered back to X509_V_OK + * by the later successful verification of the intermediate against the + * trusted root. The worst-seen error must persist. */ + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_ERR_INVALID_CA); + + X509_free(cert); + X509_STORE_free(str); + X509_STORE_CTX_free(ctx); + sk_X509_pop_free(untrusted, NULL); +#endif + return EXPECT_RESULT(); +} + +int test_X509_STORE_InvalidCa_NoCallback(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_ALL) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) + const char* filename = "./certs/intermediate/ca_false_intermediate/" + "test_int_not_cacert.pem"; + const char* srvfile = "./certs/intermediate/ca_false_intermediate/" + "test_sign_bynoca_srv.pem"; + X509_STORE_CTX* ctx = NULL; + X509_STORE* str = NULL; + XFILE fp = XBADFILE; + X509* cert = NULL; + STACK_OF(X509)* untrusted = NULL; + + ExpectTrue((fp = XFOPEN(srvfile, "rb")) + != XBADFILE); + ExpectNotNull(cert = PEM_read_X509(fp, 0, 0, 0 )); + if (fp != XBADFILE) { + XFCLOSE(fp); + fp = XBADFILE; + } + + ExpectNotNull(str = X509_STORE_new()); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectNotNull(untrusted = sk_X509_new_null()); + + /* Create cert chain stack with an intermediate that is CA:FALSE. */ + ExpectIntEQ(test_X509_STORE_untrusted_load_cert_to_stack(filename, + untrusted), TEST_SUCCESS); + + ExpectIntEQ(X509_STORE_load_locations(str, + "./certs/intermediate/ca_false_intermediate/test_ca.pem", + NULL), 1); + ExpectIntEQ(X509_STORE_CTX_init(ctx, str, cert, untrusted), 1); + /* No verify callback: verification must fail on CA:FALSE issuer. */ + ExpectIntNE(X509_verify_cert(ctx), 1); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_ERR_INVALID_CA); X509_free(cert); X509_STORE_free(str);