From eb29d725d3276a34756b000f6a64d05654a5e49d Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 23 Oct 2026 14:23:50 -0700 Subject: [PATCH 1/4] Check for modified certs in SNI callback --- src/internal.c | 37 +++++++++++++ tests/api/test_tls.c | 127 +++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/internal.c b/src/internal.c index 95b71ddbfd1..19732740a45 100644 --- a/src/internal.c +++ b/src/internal.c @@ -47399,6 +47399,38 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], #endif /* !WOLFSSL_NO_TLS12 */ #ifdef HAVE_SNI +#ifndef NO_CERTS + static int IsStaleCtxDer(const DerBuffer* der, byte weOwn, + const DerBuffer* ctxDer) + { + return (!weOwn) && (der != NULL) && (der != ctxDer); + } + + /* Check the CTX still has the buffers the SSL points to but does not own. + * Returns BAD_STATE_E when the CTX replaced one. */ + static int CheckSSL_CTX_CertsAndKeys(const WOLFSSL* ssl) + { + const WOLFSSL_CTX* ctx = ssl->ctx; + + if (IsStaleCtxDer(ssl->buffers.certificate, ssl->buffers.weOwnCert, + ctx->certificate) || + IsStaleCtxDer(ssl->buffers.certChain, ssl->buffers.weOwnCertChain, + ctx->certChain) || + IsStaleCtxDer(ssl->buffers.key, ssl->buffers.weOwnKey, + ctx->privateKey) + #ifdef WOLFSSL_DUAL_ALG_CERTS + || IsStaleCtxDer(ssl->buffers.altKey, ssl->buffers.weOwnAltKey, + ctx->altPrivateKey) + #endif + ) { + WOLFSSL_MSG("CTX certificate or key replaced in SNI callback"); + WOLFSSL_ERROR_VERBOSE(BAD_STATE_E); + return BAD_STATE_E; + } + return 0; + } +#endif /* !NO_CERTS */ + int SNI_Callback(WOLFSSL* ssl) { int ad = 0; @@ -47437,6 +47469,11 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], default: break; } + #ifndef NO_CERTS + if (ret == 0) { + ret = CheckSSL_CTX_CertsAndKeys(ssl); + } + #endif } return ret; } diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 24792031363..fba0382e2e6 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -4168,6 +4168,133 @@ int test_tls13_session_resumption_sni_mismatch(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(HAVE_SNI) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) +enum { + SNI_LOAD_CTX_CERT, + SNI_LOAD_CTX_KEY, + SNI_LOAD_SSL_CERT, + SNI_SWITCH_CTX +}; + +typedef struct SniLoadCase { + WOLFSSL_CTX* ctx2; + int action; + int loadRet; +} SniLoadCase; + +static int sni_load_cb(WOLFSSL* ssl, int* ad, void* arg) +{ + SniLoadCase* c = (SniLoadCase*)arg; + WOLFSSL_CTX* ctx = wolfSSL_get_SSL_CTX(ssl); + + (void)ad; + switch (c->action) { + case SNI_LOAD_CTX_CERT: + c->loadRet = wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE); + break; + case SNI_LOAD_CTX_KEY: + c->loadRet = wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + CERT_FILETYPE); + break; + #ifdef OPENSSL_EXTRA + case SNI_SWITCH_CTX: + c->loadRet = (wolfSSL_set_SSL_CTX(ssl, c->ctx2) == c->ctx2) ? + WOLFSSL_SUCCESS : WOLFSSL_FAILURE; + break; + #endif + default: + c->loadRet = wolfSSL_use_certificate_file(ssl, svrCertFile, + CERT_FILETYPE); + break; + } + return 0; +} +#endif + +/* Replacing the certificate or key on the context from the SNI callback fails + * the handshake. Loading one on the session is allowed. */ +int test_tls_sni_cb_ctx_cert_load(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(HAVE_SNI) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) + static const struct { + method_provider client; + method_provider server; + } methods[] = { + #ifndef WOLFSSL_NO_TLS12 + { wolfTLSv1_2_client_method, wolfTLSv1_2_server_method }, + #endif + #ifdef WOLFSSL_TLS13 + { wolfTLSv1_3_client_method, wolfTLSv1_3_server_method }, + #endif + }; + static const struct { + int action; + int err; + } cases[] = { + #ifndef WOLFSSL_COPY_CERT + { SNI_LOAD_CTX_CERT, WC_NO_ERR_TRACE(BAD_STATE_E) }, + #endif + #if !defined(WOLFSSL_COPY_KEY) && !defined(WOLFSSL_BLIND_PRIVATE_KEY) + { SNI_LOAD_CTX_KEY, WC_NO_ERR_TRACE(BAD_STATE_E) }, + #endif + { SNI_LOAD_SSL_CERT, 0 }, + #ifdef OPENSSL_EXTRA + { SNI_SWITCH_CTX, 0 }, + #endif + }; + const char* host = "example.com"; + size_t i; + size_t j; + + for (i = 0; i < XELEM_CNT(methods); i++) { + for (j = 0; j < XELEM_CNT(cases); j++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + SniLoadCase c; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&c, 0, sizeof(c)); + c.action = cases[j].action; + ExpectNotNull(c.ctx2 = wolfSSL_CTX_new(methods[i].server())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(c.ctx2, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(c.ctx2, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, methods[i].client, methods[i].server), 0); + wolfSSL_CTX_set_servername_callback(ctx_s, sni_load_cb); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, &c), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + if (cases[j].err == 0) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), + 0); + } + else { + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), + 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + cases[j].err); + } + ExpectIntEQ(c.loadRet, WOLFSSL_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(c.ctx2); + } + } +#endif + return EXPECT_RESULT(); +} + /* Regression test for the post-ALPN_Select PSK-head check. * When ALPN_Select runs before CheckPreSharedKeys (so the per-PSK * binding check has the negotiated ALPN available), TLSX_SetALPN diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 4824c5ecc88..a8e75deca9f 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -71,6 +71,7 @@ int test_tls12_client_hello_no_tls13_suites(void); int test_tls_set_session_min_downgrade(void); int test_tls12_session_id_resumption_sni_mismatch(void); int test_tls13_session_resumption_sni_mismatch(void); +int test_tls_sni_cb_ctx_cert_load(void); int test_tls13_resumption_with_alpn(void); int test_tls12_session_id_resumption_alpn_mismatch(void); int test_tls13_session_resumption_alpn_mismatch(void); @@ -152,6 +153,7 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls_set_session_min_downgrade), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_sni_mismatch), \ TEST_DECL_GROUP("tls", test_tls13_session_resumption_sni_mismatch), \ + TEST_DECL_GROUP("tls", test_tls_sni_cb_ctx_cert_load), \ TEST_DECL_GROUP("tls", test_tls13_resumption_with_alpn), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_alpn_mismatch),\ TEST_DECL_GROUP("tls", test_tls13_session_resumption_alpn_mismatch), \ From 91ad7ebfa6b757cad103a67e5abf2ecd1646a5d7 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 23 Oct 2026 15:30:02 -0700 Subject: [PATCH 2/4] Check for modified certs in cert setup callback --- src/internal.c | 71 +++++++------- src/ssl_api_cert.c | 6 ++ tests/api/test_tls.c | 214 +++++++++++++++++++++++++++++++------------ tests/api/test_tls.h | 6 +- wolfssl/internal.h | 4 + 5 files changed, 206 insertions(+), 95 deletions(-) diff --git a/src/internal.c b/src/internal.c index 19732740a45..9093fdafb27 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8268,6 +8268,45 @@ static int SetSSL_CTX_CertsAndKeys(WOLFSSL* ssl, WOLFSSL_CTX* ctx) return ret; } + +#if defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB) +static int IsStaleCtxDer(const DerBuffer* der, byte weOwn, + const DerBuffer* ctxDer) +{ + return (!weOwn) && (der != NULL) && (der != ctxDer); +} + +/* Check the CTX still has the buffers the SSL points to but does not own. + * Returns BAD_STATE_E when the CTX replaced one. */ +int CheckSSL_CTX_CertsAndKeys(const WOLFSSL* ssl) +{ + const WOLFSSL_CTX* ctx = ssl->ctx; + const DerBuffer* ctxKey = ctx->privateKey; + int stale = 0; + +#ifdef WOLFSSL_DUAL_ALG_CERTS + if (ssl->buffers.key == ssl->buffers.altKey) { + /* Alternative key swapped in to sign, owned through the key. */ + ctxKey = ctx->altPrivateKey; + } + else { + stale = IsStaleCtxDer(ssl->buffers.altKey, ssl->buffers.weOwnAltKey, + ctx->altPrivateKey); + } +#endif + if (stale || + IsStaleCtxDer(ssl->buffers.certificate, ssl->buffers.weOwnCert, + ctx->certificate) || + IsStaleCtxDer(ssl->buffers.certChain, ssl->buffers.weOwnCertChain, + ctx->certChain) || + IsStaleCtxDer(ssl->buffers.key, ssl->buffers.weOwnKey, ctxKey)) { + WOLFSSL_MSG("CTX certificate or key replaced during handshake"); + WOLFSSL_ERROR_VERBOSE(BAD_STATE_E); + return BAD_STATE_E; + } + return 0; +} +#endif /* HAVE_SNI || WOLFSSL_CERT_SETUP_CB */ #endif /* NO_CERTS */ #ifndef NO_DH @@ -47399,38 +47438,6 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], #endif /* !WOLFSSL_NO_TLS12 */ #ifdef HAVE_SNI -#ifndef NO_CERTS - static int IsStaleCtxDer(const DerBuffer* der, byte weOwn, - const DerBuffer* ctxDer) - { - return (!weOwn) && (der != NULL) && (der != ctxDer); - } - - /* Check the CTX still has the buffers the SSL points to but does not own. - * Returns BAD_STATE_E when the CTX replaced one. */ - static int CheckSSL_CTX_CertsAndKeys(const WOLFSSL* ssl) - { - const WOLFSSL_CTX* ctx = ssl->ctx; - - if (IsStaleCtxDer(ssl->buffers.certificate, ssl->buffers.weOwnCert, - ctx->certificate) || - IsStaleCtxDer(ssl->buffers.certChain, ssl->buffers.weOwnCertChain, - ctx->certChain) || - IsStaleCtxDer(ssl->buffers.key, ssl->buffers.weOwnKey, - ctx->privateKey) - #ifdef WOLFSSL_DUAL_ALG_CERTS - || IsStaleCtxDer(ssl->buffers.altKey, ssl->buffers.weOwnAltKey, - ctx->altPrivateKey) - #endif - ) { - WOLFSSL_MSG("CTX certificate or key replaced in SNI callback"); - WOLFSSL_ERROR_VERBOSE(BAD_STATE_E); - return BAD_STATE_E; - } - return 0; - } -#endif /* !NO_CERTS */ - int SNI_Callback(WOLFSSL* ssl) { int ad = 0; diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 62e8f653d56..17a2c48eb26 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -2949,6 +2949,8 @@ void wolfSSL_CTX_set_cert_cb(WOLFSSL_CTX* ctx, * unrecognized value. A fatal alert is sent when it failed. * @return WOLFSSL_ERROR_WANT_X509_LOOKUP when the callback returned a * negative value to ask to be called again. + * @return BAD_STATE_E when the callback replaced a certificate or key on the + * CTX that the SSL uses. */ int CertSetupCbWrapper(WOLFSSL* ssl) { @@ -2959,7 +2961,11 @@ int CertSetupCbWrapper(WOLFSSL* ssl) ret = ssl->ctx->certSetupCb(ssl, ssl->ctx->certSetupCbArg); if (ret == 1) { WOLFSSL_MSG("User cert callback returned success"); + #ifndef NO_CERTS + ret = CheckSSL_CTX_CertsAndKeys(ssl); + #else ret = 0; + #endif } else if (ret == 0) { SendAlert(ssl, alert_fatal, internal_error); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index fba0382e2e6..612d0918c01 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -4168,38 +4168,46 @@ int test_tls13_session_resumption_sni_mismatch(void) return EXPECT_RESULT(); } -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(HAVE_SNI) && \ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) && \ !defined(NO_RSA) && !defined(NO_FILESYSTEM) + #define TEST_CB_CTX_CERT_LOAD +#endif + +#ifdef TEST_CB_CTX_CERT_LOAD +enum { + CB_SNI, + CB_CERT_SETUP +}; + enum { - SNI_LOAD_CTX_CERT, - SNI_LOAD_CTX_KEY, - SNI_LOAD_SSL_CERT, - SNI_SWITCH_CTX + CB_LOAD_CTX_CERT, + CB_LOAD_CTX_KEY, + CB_LOAD_SSL_CERT, + CB_SWITCH_CTX }; -typedef struct SniLoadCase { +typedef struct CbLoadCase { WOLFSSL_CTX* ctx2; int action; int loadRet; -} SniLoadCase; +} CbLoadCase; -static int sni_load_cb(WOLFSSL* ssl, int* ad, void* arg) +static void cb_load_run(WOLFSSL* ssl, CbLoadCase* c) { - SniLoadCase* c = (SniLoadCase*)arg; WOLFSSL_CTX* ctx = wolfSSL_get_SSL_CTX(ssl); - (void)ad; switch (c->action) { - case SNI_LOAD_CTX_CERT: + case CB_LOAD_CTX_CERT: c->loadRet = wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, CERT_FILETYPE); break; - case SNI_LOAD_CTX_KEY: + case CB_LOAD_CTX_KEY: c->loadRet = wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE); break; #ifdef OPENSSL_EXTRA - case SNI_SWITCH_CTX: + case CB_SWITCH_CTX: c->loadRet = (wolfSSL_set_SSL_CTX(ssl, c->ctx2) == c->ctx2) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; break; @@ -4209,17 +4217,92 @@ static int sni_load_cb(WOLFSSL* ssl, int* ad, void* arg) CERT_FILETYPE); break; } +} + +#ifdef HAVE_SNI +static int sni_load_cb(WOLFSSL* ssl, int* ad, void* arg) +{ + (void)ad; + cb_load_run(ssl, (CbLoadCase*)arg); return 0; } #endif -/* Replacing the certificate or key on the context from the SNI callback fails - * the handshake. Loading one on the session is allowed. */ -int test_tls_sni_cb_ctx_cert_load(void) +#ifdef WOLFSSL_CERT_SETUP_CB +static int cert_setup_load_cb(WOLFSSL* ssl, void* arg) +{ + cb_load_run(ssl, (CbLoadCase*)arg); + return 1; +} +#endif + +static int test_cb_ctx_cert_load_one(int cb, method_provider client, + method_provider server, int action, int err) { EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(HAVE_SNI) && \ - !defined(NO_RSA) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + CbLoadCase c; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&c, 0, sizeof(c)); + c.action = action; + ExpectNotNull(c.ctx2 = wolfSSL_CTX_new(server())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(c.ctx2, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(c.ctx2, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + client, server), 0); +#ifdef HAVE_SNI + if (cb == CB_SNI) { + const char* host = "example.com"; + + wolfSSL_CTX_set_servername_callback(ctx_s, sni_load_cb); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, &c), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + } +#endif +#ifdef WOLFSSL_CERT_SETUP_CB + if (cb == CB_CERT_SETUP) { + wolfSSL_CTX_set_cert_cb(ctx_s, cert_setup_load_cb, &c); + } +#endif + if (err == 0) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + } + else { + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), err); + } + ExpectIntEQ(c.loadRet, WOLFSSL_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(c.ctx2); + return EXPECT_RESULT(); +} +#endif /* TEST_CB_CTX_CERT_LOAD */ + +/* Replacing the certificate or key on the context from the SNI or certificate + * setup callback fails the handshake. Loading one on the session is allowed. */ +int test_tls_cb_ctx_cert_load(void) +{ + EXPECT_DECLS; +#ifdef TEST_CB_CTX_CERT_LOAD + static const int cbs[] = { + #ifdef HAVE_SNI + CB_SNI, + #endif + #ifdef WOLFSSL_CERT_SETUP_CB + CB_CERT_SETUP, + #endif + }; static const struct { method_provider client; method_provider server; @@ -4236,65 +4319,74 @@ int test_tls_sni_cb_ctx_cert_load(void) int err; } cases[] = { #ifndef WOLFSSL_COPY_CERT - { SNI_LOAD_CTX_CERT, WC_NO_ERR_TRACE(BAD_STATE_E) }, + { CB_LOAD_CTX_CERT, WC_NO_ERR_TRACE(BAD_STATE_E) }, #endif #if !defined(WOLFSSL_COPY_KEY) && !defined(WOLFSSL_BLIND_PRIVATE_KEY) - { SNI_LOAD_CTX_KEY, WC_NO_ERR_TRACE(BAD_STATE_E) }, + { CB_LOAD_CTX_KEY, WC_NO_ERR_TRACE(BAD_STATE_E) }, #endif - { SNI_LOAD_SSL_CERT, 0 }, + { CB_LOAD_SSL_CERT, 0 }, #ifdef OPENSSL_EXTRA - { SNI_SWITCH_CTX, 0 }, + { CB_SWITCH_CTX, 0 }, #endif }; - const char* host = "example.com"; size_t i; size_t j; + size_t k; - for (i = 0; i < XELEM_CNT(methods); i++) { - for (j = 0; j < XELEM_CNT(cases); j++) { - WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; - WOLFSSL *ssl_c = NULL, *ssl_s = NULL; - struct test_memio_ctx test_ctx; - SniLoadCase c; - - XMEMSET(&test_ctx, 0, sizeof(test_ctx)); - XMEMSET(&c, 0, sizeof(c)); - c.action = cases[j].action; - ExpectNotNull(c.ctx2 = wolfSSL_CTX_new(methods[i].server())); - ExpectIntEQ(wolfSSL_CTX_use_certificate_file(c.ctx2, svrCertFile, - CERT_FILETYPE), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(c.ctx2, svrKeyFile, - CERT_FILETYPE), WOLFSSL_SUCCESS); - ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, - &ssl_s, methods[i].client, methods[i].server), 0); - wolfSSL_CTX_set_servername_callback(ctx_s, sni_load_cb); - ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, &c), - WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, host, - (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); - if (cases[j].err == 0) { - ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), - 0); - } - else { - ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), - 0); - ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), - cases[j].err); + for (k = 0; k < XELEM_CNT(cbs); k++) { + for (i = 0; i < XELEM_CNT(methods); i++) { + for (j = 0; j < XELEM_CNT(cases); j++) { + ExpectIntEQ(test_cb_ctx_cert_load_one(cbs[k], + methods[i].client, methods[i].server, cases[j].action, + cases[j].err), TEST_SUCCESS); } - ExpectIntEQ(c.loadRet, WOLFSSL_SUCCESS); - - wolfSSL_free(ssl_c); - wolfSSL_free(ssl_s); - wolfSSL_CTX_free(ctx_c); - wolfSSL_CTX_free(ctx_s); - wolfSSL_CTX_free(c.ctx2); } } #endif return EXPECT_RESULT(); } +/* The alternative key swapped in to sign is still the context's buffer. */ +int test_tls_ctx_alt_key_swap(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DUAL_ALG_CERTS) && defined(WOLFSSL_TLS13) && \ + (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) && defined(HAVE_ECC) && !defined(WOLFSSL_COPY_KEY) && \ + !defined(WOLFSSL_BLIND_PRIVATE_KEY) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_AltPrivateKey_file(ctx, eccKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), 0); + + /* Same swap as SendTls13CertificateVerify with the alternative key. */ + if (ssl != NULL) { + ssl->buffers.key = ssl->buffers.altKey; + ssl->buffers.weOwnKey = ssl->buffers.weOwnAltKey; + ssl->buffers.weOwnAltKey = 0; + } + ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), 0); + + ExpectIntEQ(wolfSSL_CTX_use_AltPrivateKey_file(ctx, eccKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* Regression test for the post-ALPN_Select PSK-head check. * When ALPN_Select runs before CheckPreSharedKeys (so the per-PSK * binding check has the negotiated ALPN available), TLSX_SetALPN diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index a8e75deca9f..55064f4a8fc 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -71,7 +71,8 @@ int test_tls12_client_hello_no_tls13_suites(void); int test_tls_set_session_min_downgrade(void); int test_tls12_session_id_resumption_sni_mismatch(void); int test_tls13_session_resumption_sni_mismatch(void); -int test_tls_sni_cb_ctx_cert_load(void); +int test_tls_cb_ctx_cert_load(void); +int test_tls_ctx_alt_key_swap(void); int test_tls13_resumption_with_alpn(void); int test_tls12_session_id_resumption_alpn_mismatch(void); int test_tls13_session_resumption_alpn_mismatch(void); @@ -153,7 +154,8 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls_set_session_min_downgrade), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_sni_mismatch), \ TEST_DECL_GROUP("tls", test_tls13_session_resumption_sni_mismatch), \ - TEST_DECL_GROUP("tls", test_tls_sni_cb_ctx_cert_load), \ + TEST_DECL_GROUP("tls", test_tls_cb_ctx_cert_load), \ + TEST_DECL_GROUP("tls", test_tls_ctx_alt_key_swap), \ TEST_DECL_GROUP("tls", test_tls13_resumption_with_alpn), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_alpn_mismatch),\ TEST_DECL_GROUP("tls", test_tls13_session_resumption_alpn_mismatch), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 6f9c8cddb6e..9861c9c7373 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5684,6 +5684,10 @@ typedef struct Buffers { * not reference counted, so a session must not point at the context's. */ WOLFSSL_LOCAL int CopySSL_CTX_DhParams(WOLFSSL* ssl, WOLFSSL_CTX* ctx); #endif +#if !defined(NO_CERTS) && \ + (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) +WOLFSSL_TEST_VIS int CheckSSL_CTX_CertsAndKeys(const WOLFSSL* ssl); +#endif /* sub-states for send/do key share (key exchange) */ enum asyncState { From df9538b6770e0f92241e41e782ab614be4b32ce2 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 26 Oct 2026 15:14:00 -0700 Subject: [PATCH 3/4] PR feedback: add check in more cases, update check for same-address case, send internal_error on failure path, update tests --- src/internal.c | 22 +++++++---- src/ssl_api_cert.c | 12 +++--- tests/api/test_tls.c | 89 ++++++++++++++++++++++++++++++++------------ tests/api/test_tls.h | 4 +- 4 files changed, 88 insertions(+), 39 deletions(-) diff --git a/src/internal.c b/src/internal.c index 9093fdafb27..d8b7a2633a8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8285,11 +8285,12 @@ int CheckSSL_CTX_CertsAndKeys(const WOLFSSL* ssl) int stale = 0; #ifdef WOLFSSL_DUAL_ALG_CERTS - if (ssl->buffers.key == ssl->buffers.altKey) { - /* Alternative key swapped in to sign, owned through the key. */ + /* Signing with the alternative key swaps it in as the key. */ + if (ssl->buffers.key == ctx->altPrivateKey) { ctxKey = ctx->altPrivateKey; } - else { + /* After the swap, the buffer is owned by the key. */ + if (ssl->buffers.altKey != ssl->buffers.key) { stale = IsStaleCtxDer(ssl->buffers.altKey, ssl->buffers.weOwnAltKey, ctx->altPrivateKey); } @@ -8299,6 +8300,9 @@ int CheckSSL_CTX_CertsAndKeys(const WOLFSSL* ssl) ctx->certificate) || IsStaleCtxDer(ssl->buffers.certChain, ssl->buffers.weOwnCertChain, ctx->certChain) || + /* A chain replaced at the same address can differ in count. */ + (!ssl->buffers.weOwnCertChain && (ssl->buffers.certChain != NULL) && + (ssl->buffers.certChainCnt != ctx->certChainCnt)) || IsStaleCtxDer(ssl->buffers.key, ssl->buffers.weOwnKey, ctxKey)) { WOLFSSL_MSG("CTX certificate or key replaced during handshake"); WOLFSSL_ERROR_VERBOSE(BAD_STATE_E); @@ -47455,6 +47459,13 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], if(ssl && ssl->ctx && ssl->ctx->sniRecvCb) { WOLFSSL_MSG("Calling custom sni callback"); sniRet = ssl->ctx->sniRecvCb(ssl, &ad, ssl->ctx->sniRecvCbArg); + #ifndef NO_CERTS + ret = CheckSSL_CTX_CertsAndKeys(ssl); + if (ret != 0) { + SendAlert(ssl, alert_fatal, internal_error); + return ret; + } + #endif switch (sniRet) { case warning_return: /* TLS 1.3 error alerts are always fatal (RFC 8446 6.2) */ @@ -47476,11 +47487,6 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], default: break; } - #ifndef NO_CERTS - if (ret == 0) { - ret = CheckSSL_CTX_CertsAndKeys(ssl); - } - #endif } return ret; } diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 17a2c48eb26..40ed4ee5f88 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -2950,7 +2950,7 @@ void wolfSSL_CTX_set_cert_cb(WOLFSSL_CTX* ctx, * @return WOLFSSL_ERROR_WANT_X509_LOOKUP when the callback returned a * negative value to ask to be called again. * @return BAD_STATE_E when the callback replaced a certificate or key on the - * CTX that the SSL uses. + * CTX that the SSL uses. A fatal alert is sent. */ int CertSetupCbWrapper(WOLFSSL* ssl) { @@ -2959,13 +2959,15 @@ int CertSetupCbWrapper(WOLFSSL* ssl) if (ssl->ctx->certSetupCb != NULL) { WOLFSSL_MSG("Calling user cert setup callback"); ret = ssl->ctx->certSetupCb(ssl, ssl->ctx->certSetupCbArg); + #ifndef NO_CERTS + if (CheckSSL_CTX_CertsAndKeys(ssl) != 0) { + SendAlert(ssl, alert_fatal, internal_error); + return BAD_STATE_E; + } + #endif if (ret == 1) { WOLFSSL_MSG("User cert callback returned success"); - #ifndef NO_CERTS - ret = CheckSSL_CTX_CertsAndKeys(ssl); - #else ret = 0; - #endif } else if (ret == 0) { SendAlert(ssl, alert_fatal, internal_error); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 612d0918c01..e65e68ccbf5 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -4190,6 +4190,7 @@ enum { typedef struct CbLoadCase { WOLFSSL_CTX* ctx2; int action; + int retry; int loadRet; } CbLoadCase; @@ -4222,22 +4223,26 @@ static void cb_load_run(WOLFSSL* ssl, CbLoadCase* c) #ifdef HAVE_SNI static int sni_load_cb(WOLFSSL* ssl, int* ad, void* arg) { + CbLoadCase* c = (CbLoadCase*)arg; + (void)ad; - cb_load_run(ssl, (CbLoadCase*)arg); - return 0; + cb_load_run(ssl, c); + return c->retry ? warning_return : 0; } #endif #ifdef WOLFSSL_CERT_SETUP_CB static int cert_setup_load_cb(WOLFSSL* ssl, void* arg) { - cb_load_run(ssl, (CbLoadCase*)arg); - return 1; + CbLoadCase* c = (CbLoadCase*)arg; + + cb_load_run(ssl, c); + return c->retry ? -1 : 1; } #endif static int test_cb_ctx_cert_load_one(int cb, method_provider client, - method_provider server, int action, int err) + method_provider server, int action, int retry, int err) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -4248,6 +4253,7 @@ static int test_cb_ctx_cert_load_one(int cb, method_provider client, XMEMSET(&test_ctx, 0, sizeof(test_ctx)); XMEMSET(&c, 0, sizeof(c)); c.action = action; + c.retry = retry; ExpectNotNull(c.ctx2 = wolfSSL_CTX_new(server())); ExpectIntEQ(wolfSSL_CTX_use_certificate_file(c.ctx2, svrCertFile, CERT_FILETYPE), WOLFSSL_SUCCESS); @@ -4316,17 +4322,23 @@ int test_tls_cb_ctx_cert_load(void) }; static const struct { int action; + int retry; /* callback returns its warning or retry value */ int err; } cases[] = { - #ifndef WOLFSSL_COPY_CERT - { CB_LOAD_CTX_CERT, WC_NO_ERR_TRACE(BAD_STATE_E) }, + #ifdef WOLFSSL_COPY_CERT + { CB_LOAD_CTX_CERT, 0, 0 }, + #else + { CB_LOAD_CTX_CERT, 0, WC_NO_ERR_TRACE(BAD_STATE_E) }, + { CB_LOAD_CTX_CERT, 1, WC_NO_ERR_TRACE(BAD_STATE_E) }, #endif - #if !defined(WOLFSSL_COPY_KEY) && !defined(WOLFSSL_BLIND_PRIVATE_KEY) - { CB_LOAD_CTX_KEY, WC_NO_ERR_TRACE(BAD_STATE_E) }, + #if defined(WOLFSSL_COPY_KEY) || defined(WOLFSSL_BLIND_PRIVATE_KEY) + { CB_LOAD_CTX_KEY, 0, 0 }, + #else + { CB_LOAD_CTX_KEY, 0, WC_NO_ERR_TRACE(BAD_STATE_E) }, #endif - { CB_LOAD_SSL_CERT, 0 }, + { CB_LOAD_SSL_CERT, 0, 0 }, #ifdef OPENSSL_EXTRA - { CB_SWITCH_CTX, 0 }, + { CB_SWITCH_CTX, 0, 0 }, #endif }; size_t i; @@ -4338,7 +4350,7 @@ int test_tls_cb_ctx_cert_load(void) for (j = 0; j < XELEM_CNT(cases); j++) { ExpectIntEQ(test_cb_ctx_cert_load_one(cbs[k], methods[i].client, methods[i].server, cases[j].action, - cases[j].err), TEST_SUCCESS); + cases[j].retry, cases[j].err), TEST_SUCCESS); } } } @@ -4346,28 +4358,50 @@ int test_tls_cb_ctx_cert_load(void) return EXPECT_RESULT(); } -/* The alternative key swapped in to sign is still the context's buffer. */ -int test_tls_ctx_alt_key_swap(void) +#if defined(WOLFSSL_DUAL_ALG_CERTS) && defined(HAVE_ECC) && \ + !defined(WOLFSSL_COPY_KEY) && !defined(WOLFSSL_BLIND_PRIVATE_KEY) + #define TEST_SSL_CTX_ALT_KEY +#endif + +/* The buffers the SSL shares with its context are checked by pointer and by + * chain count. The alternative key swapped in to sign is still the context's. + */ +int test_tls_ssl_ctx_certs_check(void) { EXPECT_DECLS; -#if defined(WOLFSSL_DUAL_ALG_CERTS) && defined(WOLFSSL_TLS13) && \ - (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ - !defined(NO_RSA) && defined(HAVE_ECC) && !defined(WOLFSSL_COPY_KEY) && \ - !defined(WOLFSSL_BLIND_PRIVATE_KEY) +#if (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) && \ + !defined(NO_CERTS) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_FILESYSTEM) && defined(WOLFSSL_PEM_TO_DER) && \ + !defined(NO_RSA) && !defined(WOLFSSL_COPY_CERT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; +#ifdef TEST_SSL_CTX_ALT_KEY + byte* altKey = NULL; + size_t altKeySz = 0; +#endif - ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); - ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, - CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx, svrCertFile), + WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE), WOLFSSL_SUCCESS); +#ifdef TEST_SSL_CTX_ALT_KEY ExpectIntEQ(wolfSSL_CTX_use_AltPrivateKey_file(ctx, eccKeyFile, CERT_FILETYPE), WOLFSSL_SUCCESS); +#endif ExpectNotNull(ssl = wolfSSL_new(ctx)); ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), 0); + /* Chain replaced at the same address with a different count. */ + if (ctx != NULL) { + ctx->certChainCnt++; + } + ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), WC_NO_ERR_TRACE(BAD_STATE_E)); + if (ctx != NULL) { + ctx->certChainCnt--; + } + +#ifdef TEST_SSL_CTX_ALT_KEY /* Same swap as SendTls13CertificateVerify with the alternative key. */ if (ssl != NULL) { ssl->buffers.key = ssl->buffers.altKey; @@ -4376,10 +4410,17 @@ int test_tls_ctx_alt_key_swap(void) } ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), 0); + /* A new alternative key on the SSL leaves the swapped key with the CTX. */ + ExpectIntEQ(load_file("./certs/ecc-key.der", &altKey, &altKeySz), 0); + ExpectIntEQ(wolfSSL_use_AltPrivateKey_buffer(ssl, altKey, (long)altKeySz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), 0); + ExpectIntEQ(wolfSSL_CTX_use_AltPrivateKey_file(ctx, eccKeyFile, CERT_FILETYPE), WOLFSSL_SUCCESS); - ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), - WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(CheckSSL_CTX_CertsAndKeys(ssl), WC_NO_ERR_TRACE(BAD_STATE_E)); + XFREE(altKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 55064f4a8fc..a3f24d080c2 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -72,7 +72,7 @@ int test_tls_set_session_min_downgrade(void); int test_tls12_session_id_resumption_sni_mismatch(void); int test_tls13_session_resumption_sni_mismatch(void); int test_tls_cb_ctx_cert_load(void); -int test_tls_ctx_alt_key_swap(void); +int test_tls_ssl_ctx_certs_check(void); int test_tls13_resumption_with_alpn(void); int test_tls12_session_id_resumption_alpn_mismatch(void); int test_tls13_session_resumption_alpn_mismatch(void); @@ -155,7 +155,7 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_sni_mismatch), \ TEST_DECL_GROUP("tls", test_tls13_session_resumption_sni_mismatch), \ TEST_DECL_GROUP("tls", test_tls_cb_ctx_cert_load), \ - TEST_DECL_GROUP("tls", test_tls_ctx_alt_key_swap), \ + TEST_DECL_GROUP("tls", test_tls_ssl_ctx_certs_check), \ TEST_DECL_GROUP("tls", test_tls13_resumption_with_alpn), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_alpn_mismatch),\ TEST_DECL_GROUP("tls", test_tls13_session_resumption_alpn_mismatch), \ From ec5f9216c571b93ade05df30e6e9e5e5640019b0 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 26 Oct 2026 15:45:56 -0700 Subject: [PATCH 4/4] Fix CI failures --- tests/api/test_tls.c | 6 +++++- wolfssl/internal.h | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index e65e68ccbf5..d13c20e7cf7 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -4259,6 +4259,10 @@ static int test_cb_ctx_cert_load_one(int cb, method_provider client, CERT_FILETYPE), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(c.ctx2, svrKeyFile, CERT_FILETYPE), WOLFSSL_SUCCESS); +#ifndef NO_DH + /* Switching contexts takes the DH state from the new one. */ + SetDHCtx(c.ctx2); +#endif ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, client, server), 0); #ifdef HAVE_SNI @@ -4370,7 +4374,7 @@ int test_tls_ssl_ctx_certs_check(void) { EXPECT_DECLS; #if (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) && \ - !defined(NO_CERTS) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && \ !defined(NO_FILESYSTEM) && defined(WOLFSSL_PEM_TO_DER) && \ !defined(NO_RSA) && !defined(WOLFSSL_COPY_CERT) WOLFSSL_CTX* ctx = NULL; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 9861c9c7373..f6111c7d634 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5686,6 +5686,9 @@ WOLFSSL_LOCAL int CopySSL_CTX_DhParams(WOLFSSL* ssl, WOLFSSL_CTX* ctx); #endif #if !defined(NO_CERTS) && \ (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) +#ifdef WOLFSSL_API_PREFIX_MAP + #define CheckSSL_CTX_CertsAndKeys wolfSSL_CheckSSL_CTX_CertsAndKeys +#endif WOLFSSL_TEST_VIS int CheckSSL_CTX_CertsAndKeys(const WOLFSSL* ssl); #endif