From d4cab69622ab793813de334de30737c768be81bd Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 10 Jul 2026 15:58:43 -0700 Subject: [PATCH] Return new error CTX_BUSY_E on APIs which would modify aliased buffers --- doc/dox_comments/header_files/ssl.h | 38 +++++++++++- src/internal.c | 3 + src/ssl.c | 4 ++ src/ssl_api_cert.c | 2 +- src/ssl_load.c | 86 ++++++++++++++++++++++++++ tests/api.c | 79 +++++++++++++++++------- tests/api/test_tls13.c | 96 +++++++++++++++++++++++++++++ tests/api/test_tls13.h | 4 ++ wolfssl/error-ssl.h | 5 +- 9 files changed, 293 insertions(+), 24 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 59f44c3a54..5b14268e53 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -1027,6 +1027,8 @@ int wolfSSL_is_static_memory(WOLFSSL* ssl, given using the “format” argument, file doesn’t exist, can’t be read, or is corrupted, an out of memory condition occurs, Base16 decoding fails on the file. + \return CTX_BUSY_E if any WOLFSSL session created from this context is + still active; see the note below. \param ctx a pointer to a WOLFSSL_CTX structure, created using wolfSSL_CTX_new() @@ -1048,9 +1050,17 @@ int wolfSSL_is_static_memory(WOLFSSL* ssl, ... \endcode + \note The context certificate is shared with every WOLFSSL session created + from it, so this function refuses to change it while any such session is + active and returns CTX_BUSY_E. Load the certificate before creating + sessions. To set a certificate for one connection, call + wolfSSL_use_certificate_file() on the WOLFSSL object, or switch the session + to a preconfigured context with wolfSSL_set_SSL_CTX(). + \sa wolfSSL_CTX_use_certificate_buffer \sa wolfSSL_use_certificate_file \sa wolfSSL_use_certificate_buffer + \sa wolfSSL_set_SSL_CTX */ int wolfSSL_CTX_use_certificate_file(WOLFSSL_CTX* ctx, const char* file, int format); @@ -1077,6 +1087,8 @@ int wolfSSL_CTX_use_certificate_file(WOLFSSL_CTX* ctx, const char* file, be read, or is corrupted. An out of memory condition occurs. Base16 decoding fails on the file. The key file is encrypted but no password is provided. + \return CTX_BUSY_E if any WOLFSSL session created from this context is + still active; see the note below. \param ctx pointer to a WOLFSSL_CTX structure. \param file path to the private key file. @@ -1096,11 +1108,19 @@ int wolfSSL_CTX_use_certificate_file(WOLFSSL_CTX* ctx, const char* file, ... \endcode + \note The context private key is shared with every WOLFSSL session created + from it (the server signs with it), so this function refuses to change it + while any such session is active and returns CTX_BUSY_E. Load the key + before creating sessions. To set a key for one connection, call + wolfSSL_use_PrivateKey_file() on the WOLFSSL object, or switch the session + to a preconfigured context with wolfSSL_set_SSL_CTX(). + \sa wolfSSL_CTX_use_PrivateKey_buffer \sa wolfSSL_use_PrivateKey_file \sa wolfSSL_use_PrivateKey_buffer \sa wc_CryptoCb_RegisterDevice \sa wolfSSL_CTX_SetDevId + \sa wolfSSL_set_SSL_CTX */ int wolfSSL_CTX_use_PrivateKey_file(WOLFSSL_CTX* ctx, const char* file, int format); @@ -1543,9 +1563,15 @@ long wolfSSL_CTX_get_verify_depth(WOLFSSL_CTX* ctx); ... \endcode + \note This updates the WOLFSSL session's own buffers and does not touch the + shared WOLFSSL_CTX, so unlike wolfSSL_CTX_use_certificate_file() it is safe + to call while other sessions are active, including from the SNI (server + name) callback to set a certificate for the current connection. + \sa wolfSSL_CTX_use_certificate_buffer \sa wolfSSL_CTX_use_certificate_file \sa wolfSSL_use_certificate_buffer + \sa wolfSSL_set_SSL_CTX */ int wolfSSL_use_certificate_file(WOLFSSL* ssl, const char* file, int format); @@ -1591,11 +1617,17 @@ int wolfSSL_use_certificate_file(WOLFSSL* ssl, const char* file, int format); ... \endcode + \note This updates the WOLFSSL session's own buffers and does not touch the + shared WOLFSSL_CTX, so unlike wolfSSL_CTX_use_PrivateKey_file() it is safe + to call while other sessions are active, including from the SNI (server + name) callback to set a private key for the current connection. + \sa wolfSSL_CTX_use_PrivateKey_buffer \sa wolfSSL_CTX_use_PrivateKey_file \sa wolfSSL_use_PrivateKey_buffer \sa wc_CryptoCb_RegisterDevice \sa wolfSSL_SetDevId + \sa wolfSSL_set_SSL_CTX */ int wolfSSL_use_PrivateKey_file(WOLFSSL* ssl, const char* file, int format); @@ -7707,6 +7739,9 @@ int wolfSSL_SetTmpDH_file(WOLFSSL* ssl, const char* f, int format); than the value of the maxDhKeySz member of the WOLFSSL_CTX struct. \return MEMORY_E returned if the allocation of memory failed in this function or a subroutine. + \return CTX_BUSY_E returned if any WOLFSSL session created from this context + is still active; the shared DH parameters cannot be changed then. Set them + before creating sessions, or use wolfSSL_SetTmpDH() on the WOLFSSL object. \param ctx a pointer to a WOLFSSL_CTX structure, created using wolfSSL_CTX_new(). @@ -8237,7 +8272,8 @@ int wolfSSL_CTX_UnloadCAs(WOLFSSL_CTX* ctx); \return SSL_SUCCESS returned on successful execution of the function. \return BAD_FUNC_ARG returned if the WOLFSSL_CTX struct is NULL or there are otherwise unpermitted argument values passed in a subroutine. - \return BAD_STATE_E returned if the WOLFSSL_CTX has a reference count > 1. + \return CTX_BUSY_E returned if the WOLFSSL_CTX has a reference count > 1 + (active sessions exist). \return BAD_MUTEX_E returned if there was a mutex error. The LockMutex() did not return 0. diff --git a/src/internal.c b/src/internal.c index 3765426a32..34c21a9b6f 100644 --- a/src/internal.c +++ b/src/internal.c @@ -29047,6 +29047,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) case RPK_UNTRUSTED_E: return "RFC 7250 Raw Public Key not trusted"; + + case CTX_BUSY_E: + return "Context in use by active sessions, operation not allowed"; } return "unknown error number"; diff --git a/src/ssl.c b/src/ssl.c index f5a309552c..258c6ae28a 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -12830,6 +12830,10 @@ long wolfSSL_CTX_ctrl(WOLFSSL_CTX* ctx, int cmd, long opt, void* pt) ret = WOLFSSL_FAILURE; break; } + if (wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + break; + } /* Clear certificate chain */ FreeDer(&ctx->certChain); if (sk) { diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index b879e93eeb..e38e1df556 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -1065,7 +1065,7 @@ int wolfSSL_CTX_UnloadIntermediateCerts(WOLFSSL_CTX* ctx) if (ctx->ref.count > 1) { WOLFSSL_MSG("ctx object must have a ref count of 1 before " "unloading intermediate certs"); - ret = BAD_STATE_E; + ret = CTX_BUSY_E; } else { ret = wolfSSL_CertManagerUnloadIntermediateCerts(ctx->cm); diff --git a/src/ssl_load.c b/src/ssl_load.c index b63a3b8430..292938b648 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -2377,6 +2377,35 @@ static int ProcessBufferResetSuites(WOLFSSL_CTX* ctx, WOLFSSL* ssl, int type) ((type) == ALT_PRIVATEKEY_TYPE)) #endif +/* Whether other objects still reference this context's shared buffers. + * + * Every WOLFSSL created from a context aliases the context's certificate, key + * and DH buffers and holds a reference on the context. A reference count above + * one therefore means such aliases exist, so those buffers must not be freed + * or replaced. + * + * @param [in] ctx SSL context object. + * @return 1 when the context is in use by other objects. + * @return 0 otherwise. + */ +static int wolfssl_ctx_resources_in_use(WOLFSSL_CTX* ctx) +{ + int inUse = 0; + + if (ctx != NULL) { + /* Block when the count can't be read so we never free a live alias. */ + if (wolfSSL_RefWithMutexLock(&ctx->ref) != 0) { + inUse = 1; + } + else { + inUse = (ctx->ref.count > 1); + (void)wolfSSL_RefWithMutexUnlock(&ctx->ref); + } + } + + return inUse; +} + /* Process a buffer of data. * * Data type is a private key or a certificate. @@ -2435,6 +2464,11 @@ int ProcessBuffer(WOLFSSL_CTX* ctx, const unsigned char* buff, long sz, if ((ret == 0) && (sz < 0)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (ssl == NULL) && + ((type == CERT_TYPE) || IS_PRIVKEY_TYPE(type)) && + wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + } #ifdef WOLFSSL_SMALL_STACK if (ret == 0) { @@ -3206,6 +3240,10 @@ int wolfSSL_CTX_use_certificate_file(WOLFSSL_CTX* ctx, const char* file, WOLFSSL_ENTER("wolfSSL_CTX_use_certificate_file"); + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } + ret = ProcessFile(ctx, file, format, CERT_TYPE, NULL, 0, NULL, GET_VERIFY_SETTING_CTX(ctx)); @@ -3231,6 +3269,10 @@ int wolfSSL_CTX_use_PrivateKey_file(WOLFSSL_CTX* ctx, const char* file, WOLFSSL_ENTER("wolfSSL_CTX_use_PrivateKey_file"); + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } + ret = ProcessFile(ctx, file, format, PRIVATEKEY_TYPE, NULL, 0, NULL, GET_VERIFY_SETTING_CTX(ctx)); @@ -3255,6 +3297,10 @@ int wolfSSL_CTX_use_AltPrivateKey_file(WOLFSSL_CTX* ctx, const char* file, WOLFSSL_ENTER("wolfSSL_CTX_use_AltPrivateKey_file"); + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } + ret = ProcessFile(ctx, file, format, ALT_PRIVATEKEY_TYPE, NULL, 0, NULL, GET_VERIFY_SETTING_CTX(ctx)); @@ -3279,6 +3325,10 @@ int wolfSSL_CTX_use_certificate_chain_file(WOLFSSL_CTX* ctx, const char* file) /* process up to MAX_CHAIN_DEPTH plus subject cert */ WOLFSSL_ENTER("wolfSSL_CTX_use_certificate_chain_file"); + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } + #ifdef WOLFSSL_PEM_TO_DER ret = ProcessFile(ctx, file, WOLFSSL_FILETYPE_PEM, CERT_TYPE, NULL, 1, NULL, GET_VERIFY_SETTING_CTX(ctx)); @@ -3309,6 +3359,10 @@ int wolfSSL_CTX_use_certificate_chain_file_format(WOLFSSL_CTX* ctx, WOLFSSL_ENTER("wolfSSL_CTX_use_certificate_chain_file_format"); + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } + ret = ProcessFile(ctx, file, format, CERT_TYPE, NULL, 1, NULL, GET_VERIFY_SETTING_CTX(ctx)); @@ -4211,6 +4265,9 @@ int wolfSSL_CTX_use_PrivateKey_Id(WOLFSSL_CTX* ctx, const unsigned char* id, if (ctx == NULL || id == NULL || sz < 0) { return 0; } + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } /* Dispose of old private key and allocate and copy in id. */ FreeDer(&ctx->privateKey); @@ -4287,6 +4344,9 @@ int wolfSSL_CTX_use_PrivateKey_Label(WOLFSSL_CTX* ctx, const char* label, if (ctx == NULL || label == NULL) { return 0; } + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } sz = (word32)XSTRLEN(label) + 1; @@ -4330,6 +4390,9 @@ int wolfSSL_CTX_use_AltPrivateKey_Id(WOLFSSL_CTX* ctx, const unsigned char* id, if ((ctx == NULL) || (id == NULL) || (sz < 0)) { ret = 0; } + if ((ret == 1) && wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + } if (ret == 1) { FreeDer(&ctx->altPrivateKey); @@ -4380,6 +4443,9 @@ int wolfSSL_CTX_use_AltPrivateKey_Label(WOLFSSL_CTX* ctx, const char* label, if ((ctx == NULL) || (label == NULL)) { ret = 0; } + if ((ret == 1) && wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + } if (ret == 1) { sz = (word32)XSTRLEN(label) + 1; @@ -4954,6 +5020,10 @@ static int wolfssl_ctx_add_to_chain(WOLFSSL_CTX* ctx, const byte* der, int ret; DerBuffer* derBuffer = NULL; + if (wolfssl_ctx_resources_in_use(ctx)) { + return CTX_BUSY_E; + } + /* Create a DER buffer from DER encoding. */ ret = AllocCopyDer(&derBuffer, der, (word32)derSz, CERT_TYPE, ctx->heap); if (ret != 0) { @@ -5063,6 +5133,9 @@ int wolfSSL_CTX_use_certificate(WOLFSSL_CTX *ctx, WOLFSSL_X509 *x) WOLFSSL_MSG("Bad parameter"); res = 0; } + if ((res == 1) && wolfssl_ctx_resources_in_use(ctx)) { + res = CTX_BUSY_E; + } if (res == 1) { /* Replace certificate buffer with one holding the new certificate. */ @@ -5305,6 +5378,9 @@ int wolfSSL_CTX_use_PrivateKey(WOLFSSL_CTX *ctx, WOLFSSL_EVP_PKEY *pkey) if ((ctx == NULL) || (pkey == NULL) || (pkey->pkey.ptr == NULL)) { ret = 0; } + if ((ret == 1) && wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + } if (ret == 1) { switch (pkey->type) { @@ -5376,6 +5452,9 @@ int wolfSSL_CTX_use_certificate_ASN1(WOLFSSL_CTX *ctx, int derSz, if ((ctx == NULL) || (der == NULL)) { ret = 0; } + if ((ret == 1) && wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + } /* Load DER encoded certificate into SSL context. */ if ((ret == 1) && (wolfSSL_CTX_use_certificate_buffer(ctx, der, derSz, WOLFSSL_FILETYPE_ASN1) != 1)) { @@ -5409,6 +5488,9 @@ int wolfSSL_CTX_use_RSAPrivateKey(WOLFSSL_CTX* ctx, WOLFSSL_RSA* rsa) WOLFSSL_MSG("one or more inputs were NULL"); ret = BAD_FUNC_ARG; } + if ((ret == 1) && wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + } /* Get DER encoding size. */ if ((ret == 1) && ((derSize = wolfSSL_i2d_RSAPrivateKey(rsa, NULL)) <= 0)) { @@ -5752,6 +5834,10 @@ static int wolfssl_ctx_set_tmp_dh(WOLFSSL_CTX* ctx, unsigned char* p, int pSz, if ((ctx == NULL) || (p == NULL) || (g == NULL)) ret = BAD_FUNC_ARG; + if ((ret == 1) && wolfssl_ctx_resources_in_use(ctx)) { + ret = CTX_BUSY_E; + } + /* Check the size of the prime meets the requirements of the SSL context. */ if (ret == 1) { if (((word16)pSz < ctx->minDhKeySz) || ((word16)pSz > ctx->maxDhKeySz)) { diff --git a/tests/api.c b/tests/api.c index 7bc99949ad..e85848ad0d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3860,6 +3860,7 @@ static int test_wolfSSL_CTX_add1_chain_cert(void) defined(KEEP_OUR_CERT) && !defined(NO_RSA) && !defined(NO_TLS) && \ !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx; + WOLFSSL_CTX* ctx2; WOLFSSL* ssl = NULL; const char *certChain[] = { "./certs/intermediate/client-int-cert.pem", @@ -3873,7 +3874,10 @@ static int test_wolfSSL_CTX_add1_chain_cert(void) WOLF_STACK_OF(X509)* chain = NULL; ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); - ExpectNotNull(ssl = wolfSSL_new(ctx)); + /* ssl uses a separate context so the CTX-level chain adds below run against + * a context with no active session aliasing its buffers. */ + ExpectNotNull(ctx2 = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx2)); ExpectNotNull(x509 = wolfSSL_X509_new()); ExpectIntEQ(SSL_CTX_add1_chain_cert(ctx, x509), 0); @@ -3938,6 +3942,7 @@ static int test_wolfSSL_CTX_add1_chain_cert(void) SSL_free(ssl); SSL_CTX_free(ctx); + SSL_CTX_free(ctx2); #endif return EXPECT_RESULT(); } @@ -4005,6 +4010,7 @@ static int test_wolfSSL_CTX_use_certificate_chain_buffer_format(void) !defined(NO_WOLFSSL_CLIENT) && !defined(NO_RSA) && \ (!defined(NO_FILESYSTEM) || defined(USE_CERT_BUFFERS_2048)) WOLFSSL_CTX* ctx = NULL; + WOLFSSL_CTX* ctx2 = NULL; WOLFSSL* ssl = NULL; #ifndef NO_FILESYSTEM const char* cert = svrCertFile; @@ -4015,7 +4021,11 @@ static int test_wolfSSL_CTX_use_certificate_chain_buffer_format(void) #endif ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); - ExpectNotNull(ssl = wolfSSL_new(ctx)); + /* ssl uses a separate context so the context under test keeps a reference + * count of one - its loaders then exercise argument validation and real + * loads rather than being refused as CTX_BUSY_E. */ + ExpectNotNull(ctx2 = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx2)); /* Invalid parameters. */ #ifndef NO_FILESYSTEM @@ -4069,6 +4079,7 @@ static int test_wolfSSL_CTX_use_certificate_chain_buffer_format(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); + wolfSSL_CTX_free(ctx2); #ifndef NO_FILESYSTEM if (buf != NULL) { XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -12771,23 +12782,35 @@ static int test_wolfSSL_private_keys(void) ExpectIntEQ(wolfSSL_check_private_key(ssl), WOLFSSL_SUCCESS); #endif - /* test loading private key to the WOLFSSL_CTX */ - ExpectIntEQ(SSL_CTX_use_PrivateKey_ASN1(0, ctx, - (unsigned char*)client_key_der_2048, - sizeof_client_key_der_2048), WOLFSSL_SUCCESS); - - #if !defined(NO_CHECK_PRIVATE_KEY) - /* Should mismatch now that a different private key loaded */ - ExpectIntNE(wolfSSL_CTX_check_private_key(ctx), WOLFSSL_SUCCESS); + /* Test loading private key to the WOLFSSL_CTX. Use a fresh context: the + * one above has an active session (ssl) aliasing its buffers, so changing + * its key is refused. */ + { + WOLFSSL_CTX* ctx2 = NULL; + #ifndef NO_WOLFSSL_SERVER + ExpectNotNull(ctx2 = SSL_CTX_new(wolfSSLv23_server_method())); + #else + ExpectNotNull(ctx2 = SSL_CTX_new(wolfSSLv23_client_method())); #endif + ExpectTrue(SSL_CTX_use_certificate_file(ctx2, svrCertFile, + WOLFSSL_FILETYPE_PEM)); + ExpectIntEQ(SSL_CTX_use_PrivateKey_ASN1(0, ctx2, + (unsigned char*)client_key_der_2048, + sizeof_client_key_der_2048), WOLFSSL_SUCCESS); + #if !defined(NO_CHECK_PRIVATE_KEY) + /* Should mismatch now that a different private key loaded */ + ExpectIntNE(wolfSSL_CTX_check_private_key(ctx2), WOLFSSL_SUCCESS); + #endif - ExpectIntEQ(SSL_CTX_use_PrivateKey_ASN1(0, ctx, - (unsigned char*)server_key, - sizeof_server_key_der_2048), WOLFSSL_SUCCESS); - #if !defined(NO_CHECK_PRIVATE_KEY) - /* After loading back in DER format of original key, should match */ - ExpectIntEQ(wolfSSL_CTX_check_private_key(ctx), WOLFSSL_SUCCESS); - #endif + ExpectIntEQ(SSL_CTX_use_PrivateKey_ASN1(0, ctx2, + (unsigned char*)server_key, + sizeof_server_key_der_2048), WOLFSSL_SUCCESS); + #if !defined(NO_CHECK_PRIVATE_KEY) + /* After loading back in DER format of original key, should match */ + ExpectIntEQ(wolfSSL_CTX_check_private_key(ctx2), WOLFSSL_SUCCESS); + #endif + SSL_CTX_free(ctx2); + } /* Invalid parameters. */ ExpectNotNull(pkey = wolfSSL_EVP_PKEY_new()); @@ -13041,6 +13064,7 @@ static int test_wolfSSL_tmp_dh(void) BIO* bio = NULL; SSL* ssl = NULL; SSL_CTX* ctx = NULL; + SSL_CTX* ctxDh = NULL; #ifndef NO_WOLFSSL_CLIENT SSL* ssl_c = NULL; SSL_CTX* ctx_c = NULL; @@ -13099,13 +13123,21 @@ static int test_wolfSSL_tmp_dh(void) WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(NULL, p , 0, g , 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctx , p , 1, g , 1), + /* Prime/size validation must run against a context with no active session; + * a busy one is refused with CTX_BUSY_E before the parameters are ever + * checked (asserted separately below). */ +#ifndef NO_WOLFSSL_SERVER + ExpectNotNull(ctxDh = SSL_CTX_new(wolfSSLv23_server_method())); +#else + ExpectNotNull(ctxDh = SSL_CTX_new(wolfSSLv23_client_method())); +#endif + ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctxDh, p , 1, g , 1), WC_NO_ERR_TRACE(DH_KEY_SIZE_E)); - ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctx , buff, 6000, g , 1), + ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctxDh, buff, 6000, g , 1), WC_NO_ERR_TRACE(DH_KEY_SIZE_E)); #if !defined(WOLFSSL_OLD_PRIME_CHECK) && !defined(HAVE_FIPS) && \ !defined(HAVE_SELFTEST) - ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctx, bad_p, pSz, g, gSz), + ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctxDh, bad_p, pSz, g, gSz), WC_NO_ERR_TRACE(DH_CHECK_PUB_E)); #endif ExpectIntEQ((int)wolfSSL_SetTmpDH(NULL, NULL, 0, NULL, 0), @@ -13151,9 +13183,13 @@ static int test_wolfSSL_tmp_dh(void) DH_free(dh2); dh2 = NULL; + /* Setting DH parameters on the context is refused while it has an active + * session; check that, then exercise the success path on the fresh one. */ ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctx, p, pSz, g, gSz), + WC_NO_ERR_TRACE(CTX_BUSY_E)); + ExpectIntEQ((int)wolfSSL_CTX_SetTmpDH(ctxDh, p, pSz, g, gSz), WOLFSSL_SUCCESS); - ExpectIntEQ((int)SSL_CTX_set_tmp_dh(ctx, dh), WOLFSSL_SUCCESS); + ExpectIntEQ((int)SSL_CTX_set_tmp_dh(ctxDh, dh), WOLFSSL_SUCCESS); #ifndef NO_WOLFSSL_SERVER ExpectIntEQ((int)SSL_set_tmp_dh(ssl, dh), WOLFSSL_SUCCESS); #else @@ -13175,6 +13211,7 @@ static int test_wolfSSL_tmp_dh(void) } #endif SSL_CTX_free(ctx); + SSL_CTX_free(ctxDh); #endif return EXPECT_RESULT(); } diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index ed5ce88edc..80416b7716 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -3691,6 +3691,102 @@ int test_tls13_pha(void) return EXPECT_RESULT(); } + +int test_tls13_ctx_busy_blocks_updates(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + !defined(NO_RSA) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* Loads server cert/key on ctx_s and creates ssl_s, which takes a + * reference on ctx_s so its shared buffers are aliased. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* With ssl_s alive, changing the context cert/key must be refused. The + * file setters go through WS_RC and the buffer setter returns the code + * directly - both surface CTX_BUSY_E. */ + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), CTX_BUSY_E); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), CTX_BUSY_E); + ExpectIntEQ(wolfSSL_CTX_use_certificate_buffer(ctx_s, server_cert_der_2048, + sizeof_server_cert_der_2048, WOLFSSL_FILETYPE_ASN1), CTX_BUSY_E); + + /* Freeing the session drops the last alias; updates are allowed again. */ + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SNI) && !defined(NO_RSA) +/* Try to reload the context certificate from within the SNI callback. The + * in-flight session aliases the context buffers, so the update must be refused + * with CTX_BUSY_E rather than freeing buffers still in use. */ +static int test_ctx_busy_sni_cb(WOLFSSL* ssl, int* ad, void* arg) +{ + WOLFSSL_CTX* ctx = wolfSSL_get_SSL_CTX(ssl); + int* cbRet = (int*)arg; + (void)ad; + /* Feed the reload result back to the parent function below. */ + *cbRet = wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE); + /* 0 means ack the servername and continue the handshake. */ + return 0; +} +#endif + +int test_tls13_sni_cb_ctx_busy(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SNI) && !defined(NO_RSA) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + const char* host = "example.com"; + int cbRet = WOLFSSL_FATAL_ERROR; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Server tries to swap its context certificate when the SNI arrives. */ + wolfSSL_CTX_set_servername_callback(ctx_s, test_ctx_busy_sni_cb); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, &cbRet), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + + /* The reload is refused mid-handshake, so nothing is freed and the + * handshake still completes. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* The context is busy with this handshake, so the reload was rejected. */ + ExpectIntEQ(cbRet, CTX_BUSY_E); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + #if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ !defined(NO_SHA256) diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index fdb733d1ee..1d83c5727b 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -30,6 +30,8 @@ int test_tls13_bad_psk_binder(void); int test_tls13_rpk_handshake(void); int test_tls13_rpk_handshake_no_negotiation(void); int test_tls13_pha(void); +int test_tls13_ctx_busy_blocks_updates(void); +int test_tls13_sni_cb_ctx_busy(void); int test_tls13_rpk_untrusted(void); int test_tls13_rpk_trust(void); int test_tls13_pq_groups(void); @@ -110,6 +112,8 @@ int test_tls13_pqc_hybrid_async_server(void); TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake_no_negotiation), \ TEST_DECL_GROUP("tls13", test_tls13_pha), \ + TEST_DECL_GROUP("tls13", test_tls13_ctx_busy_blocks_updates), \ + TEST_DECL_GROUP("tls13", test_tls13_sni_cb_ctx_busy), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_untrusted), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_trust), \ TEST_DECL_GROUP("tls13", test_tls13_pq_groups), \ diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index 2783f00813..6df29aa813 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -250,7 +250,10 @@ enum wolfSSL_ErrorCodes { RPK_UNTRUSTED_E = -521, /* RFC 7250 Raw Public Key not trusted * out of band */ - WOLFSSL_LAST_E = -521 + CTX_BUSY_E = -522, /* Context in use by active sessions; + * cannot change certs, keys or DH */ + + WOLFSSL_LAST_E = -522 /* codes -1000 to -1999 are reserved for wolfCrypt. */ };