diff --git a/ChangeLog.md b/ChangeLog.md index 2c74b06564..8972836eec 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,30 @@ ## Behavioral Changes +* **Behavioral change (loading a certificate or key on a context from inside a + callback)**: the sni callback set with `wolfSSL_CTX_set_servername_callback()` + and the certificate setup callback set with `wolfSSL_CTX_set_cert_cb()` run in + the middle of a handshake, at which point every session made from that context + is pointing at the context's certificate, chain and key. Replacing one of + those frees what those handshakes are reading. Such a load is now refused + while the calling thread is inside one of those callbacks, and the reason, + `BAD_STATE_E`, is left where `wolfSSL_get_error()` and the OpenSSL error queue + can report it. The calls affected are `wolfSSL_CTX_use_certificate()`, its + `_file` and `_buffer` forms, `wolfSSL_CTX_use_PrivateKey_file()`, `_buffer`, + `_Id` and `_Label`, the `wolfSSL_CTX_use_AltPrivateKey_*` pair, the + `wolfSSL_CTX_use_certificate_chain_*` family, and + `wolfSSL_CTX_add0_chain_cert()`, `wolfSSL_CTX_add1_chain_cert()` and + `wolfSSL_CTX_add_extra_chain_cert()`. An application that set a certificate + this way should set it on the session instead, with + `wolfSSL_use_certificate_file()` and its relatives, or hand the session a + different context with `wolfSSL_set_SSL_CTX()`; both are untouched, as are + loads into the trust store such as `wolfSSL_CTX_load_verify_locations()` and + anything called outside a callback. The note that a callback is running is + kept per thread, so one thread's callback does not refuse another thread's + loads; on a build without thread local storage it is shared, where overlapping + callbacks can refuse a load that would have been allowed, or allow one that + would have been refused. + * **Behavioral change (`wc_PufReadSram` health tests the raw SRAM readout)**: the raw readout is now health tested before the context accepts it, and a readout that cannot be SRAM power-on noise is rejected with `PUF_READ_E` diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index b1b2ebe50e..125606f4c3 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -5959,6 +5959,13 @@ void wolfSSL_CTX_set_client_cert_cb(WOLFSSL_CTX *ctx, client_cert_cb cb); application can inspect, set or clear certificates - for example to react to a CA list sent by the peer. + Set the certificate on the WOLFSSL object, with wolfSSL_use_certificate_file + and friends, or hand it a different context with wolfSSL_set_SSL_CTX. + Loading one on the WOLFSSL_CTX the handshake is running against is refused + from inside the callback and returns failure with BAD_STATE_E: sessions + already made from that context point at its certificate, and replacing it + would free what they are reading. + \param ctx The WOLFSSL_CTX object. \param cb The callback function for certificate setup. \param arg User argument to pass to the callback. diff --git a/src/internal.c b/src/internal.c index afa472608b..3fd99dc02a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -7704,8 +7704,70 @@ static int SetSSL_CTX_CertsAndKeys(WOLFSSL* ssl, WOLFSSL_CTX* ctx) return ret; } + #endif /* NO_CERTS */ +/* Context this thread is currently inside a callback for, if any. + * + * Kept per thread rather than on the context: the context is shared between + * threads, so a flag on it would race with other handshakes and would be + * written to a context the callback may have swapped out from under us. Only + * ever compared, never followed, so a context freed during the callback does + * no harm. Where the build has no thread local storage this is one shared + * pointer, which costs the guard accuracy when threads overlap but still + * cannot corrupt anything. + */ +static THREAD_LS_T WOLFSSL_CTX* inCbCtx = NULL; + +/* Note that this thread is entering a callback on a context. + * + * @param [in] ctx SSL context object the callback belongs to. + * @return What was noted before, to hand back to CtxCallbackExit(). + */ +WOLFSSL_CTX* CtxCallbackEnter(WOLFSSL_CTX* ctx) +{ + WOLFSSL_CTX* prev = inCbCtx; + + inCbCtx = ctx; + + return prev; +} + +/* Note that this thread has left the callback. + * + * @param [in] prev What CtxCallbackEnter() handed back. + */ +void CtxCallbackExit(WOLFSSL_CTX* prev) +{ + inCbCtx = prev; +} + +#ifndef NO_CERTS +/* Refuse to replace a certificate or key on a context from its own callback. + * + * Sessions made from a context point at its buffers, so replacing one frees + * what handshakes already under way are reading. Setting a certificate on the + * session alone, or handing it a different context, is what the callbacks are + * for. + * + * @param [in] ctx SSL context object. May be NULL. + * @return 0 when the load may go ahead. + * @return BAD_STATE_E while this thread is in a callback on the context. + */ +int CheckCtxCertLoad(WOLFSSL_CTX* ctx) +{ + int ret = 0; + + if ((ctx != NULL) && (ctx == inCbCtx)) { + WOLFSSL_MSG("Certificate load refused: callback running on context"); + ret = BAD_STATE_E; + WOLFSSL_ERROR_VERBOSE(ret); + } + + return ret; +} +#endif /* !NO_CERTS */ + int SetSSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) { int ret = WOLFSSL_SUCCESS; /* set default ret */ @@ -45375,8 +45437,14 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], /* Stunnel supports a custom sni callback to switch an SSL's ctx * when SNI is received. Call it now if exists */ if(ssl && ssl->ctx && ssl->ctx->sniRecvCb) { + WOLFSSL_CTX* prevCbCtx; + WOLFSSL_MSG("Calling custom sni callback"); + prevCbCtx = CtxCallbackEnter(ssl->ctx); sniRet = ssl->ctx->sniRecvCb(ssl, &ad, ssl->ctx->sniRecvCbArg); + /* The callback may have switched this session to another context, + * so put back what was noted rather than reading ssl->ctx again. */ + CtxCallbackExit(prevCbCtx); switch (sniRet) { case warning_return: WOLFSSL_MSG("Error in custom sni callback. Warning alert"); diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 9c036fac88..9c08c57381 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -2812,8 +2812,13 @@ int CertSetupCbWrapper(WOLFSSL* ssl) int ret = 0; if (ssl->ctx->certSetupCb != NULL) { + WOLFSSL_CTX* prevCbCtx; + WOLFSSL_MSG("Calling user cert setup callback"); + prevCbCtx = CtxCallbackEnter(ssl->ctx); ret = ssl->ctx->certSetupCb(ssl, ssl->ctx->certSetupCbArg); + /* The callback may have switched contexts; restore what was noted. */ + CtxCallbackExit(prevCbCtx); if (ret == 1) { WOLFSSL_MSG("User cert callback returned success"); ret = 0; diff --git a/src/ssl_load.c b/src/ssl_load.c index e0bca5ab38..c1e60b5a9d 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -2677,6 +2677,11 @@ int ProcessBuffer(WOLFSSL_CTX* ctx, const unsigned char* buff, long sz, if ((ret == 0) && (sz < 0)) { ret = BAD_FUNC_ARG; } + /* Sessions made from this context hold these by pointer. */ + if ((ret == 0) && (ssl == NULL) && ((type == CERT_TYPE) || + (type == PRIVATEKEY_TYPE) || (type == ALT_PRIVATEKEY_TYPE))) { + ret = CheckCtxCertLoad(ctx); + } #ifdef WOLFSSL_SMALL_STACK if (ret == 0) { @@ -4464,6 +4469,10 @@ int wolfSSL_CTX_use_PrivateKey_Id(WOLFSSL_CTX* ctx, const unsigned char* id, return 0; } + if (CheckCtxCertLoad(ctx) != 0) { + return 0; + } + /* Dispose of old private key and allocate and copy in id. */ FreeDer(&ctx->privateKey); if (AllocCopyDer(&ctx->privateKey, id, (word32)sz, PRIVATEKEY_TYPE, @@ -4542,6 +4551,10 @@ int wolfSSL_CTX_use_PrivateKey_Label(WOLFSSL_CTX* ctx, const char* label, sz = (word32)XSTRLEN(label) + 1; + if (CheckCtxCertLoad(ctx) != 0) { + return 0; + } + /* Dispose of old private key and allocate and copy in label. */ FreeDer(&ctx->privateKey); if (AllocCopyDer(&ctx->privateKey, (const byte*)label, (word32)sz, @@ -5206,6 +5219,11 @@ static int wolfssl_ctx_add_to_chain(WOLFSSL_CTX* ctx, const byte* der, int ret; DerBuffer* derBuffer = NULL; + /* Sessions made from this context hold the chain by pointer. */ + if (CheckCtxCertLoad(ctx) != 0) { + return 0; + } + /* Create a DER buffer from DER encoding. */ ret = AllocCopyDer(&derBuffer, der, (word32)derSz, CERT_TYPE, ctx->heap); if (ret != 0) { @@ -5313,6 +5331,10 @@ int wolfSSL_CTX_use_certificate(WOLFSSL_CTX *ctx, WOLFSSL_X509 *x) res = 0; } + if ((res == 1) && (CheckCtxCertLoad(ctx) != 0)) { + res = 0; + } + if (res == 1) { /* Replace certificate buffer with one holding the new certificate. */ FreeDer(&ctx->certificate); @@ -5400,6 +5422,11 @@ int wolfSSL_CTX_add1_chain_cert(WOLFSSL_CTX* ctx, WOLFSSL_X509* x509) ret = 0; } + /* Sessions made from this context hold the chain by pointer. */ + if ((ret == 1) && (CheckCtxCertLoad(ctx) != 0)) { + ret = 0; + } + /* Check if we already have set a certificate. */ if ((ret == 1) && (ctx->certificate == NULL)) { /* Use the certificate. */ diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index c9c0c8a44b..026443cf95 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -2426,6 +2426,383 @@ int test_tls13_session_resumption_sni_mismatch(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13)) && \ + (defined(HAVE_SNI) || defined(WOLFSSL_CERT_SETUP_CB)) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) + +/* What the callback under test does before it returns. The CB_CERT_CTX_ ones + * each reach a different context buffer. */ +enum { + CB_CERT_NO_CHANGE = 0, /* leaves everything as it found it */ + CB_CERT_ON_SSL, /* sets a certificate on this session alone */ + CB_CERT_CTX_CERT, /* loads a certificate on the context in use */ + CB_CERT_CTX_KEY, /* loads a key on the context in use */ +#ifdef OPENSSL_EXTRA + CB_CERT_CTX_CHAIN, /* adds to the chain on the context in use */ + CB_CERT_CTX_X509, /* sets the certificate from an X509 object */ +#endif +#ifdef WOLF_PRIVATE_KEY_ID + CB_CERT_CTX_KEYID, /* points the key at an id held elsewhere */ + CB_CERT_CTX_KEYLABEL, /* points the key at a label held elsewhere */ +#endif + CB_CERT_MODE_CNT +}; + +/* Everything a case needs, handed to the callback through its user argument + * so that cases share no state. */ +typedef struct CbCertCase { + WOLFSSL_CTX* ctx; /* context the handshake is running against */ + int mode; /* one of the CB_CERT_* values */ + int called; /* how many times the callback ran */ + int loadRet; /* what the load call returned */ + const void* ctxCertBefore; + const void* ctxCertAfter; +} CbCertCase; + +/* Whether the case has the callback reach for the context. */ +static int cb_cert_touches_ctx(int mode) +{ + return (mode != CB_CERT_NO_CHANGE) && (mode != CB_CERT_ON_SSL); +} + +/* Carry out what the case asks for, recording what the load returned. + * + * @param [in] ssl SSL object the callback was called on. + * @param [in, out] test Case being run. + */ +static void cb_cert_action(WOLFSSL* ssl, CbCertCase* test) +{ + test->called++; + test->ctxCertBefore = (const void*)test->ctx->certificate; + + switch (test->mode) { + case CB_CERT_ON_SSL: + test->loadRet = wolfSSL_use_certificate_file(ssl, svrCertFile, + CERT_FILETYPE); + break; + case CB_CERT_CTX_CERT: + test->loadRet = wolfSSL_CTX_use_certificate_file(test->ctx, + svrCertFile, CERT_FILETYPE); + break; + case CB_CERT_CTX_KEY: + test->loadRet = wolfSSL_CTX_use_PrivateKey_file(test->ctx, + svrKeyFile, CERT_FILETYPE); + break; + #ifdef WOLF_PRIVATE_KEY_ID + case CB_CERT_CTX_KEYID: { + static const byte keyId[] = { 0x01, 0x02, 0x03, 0x04 }; + + test->loadRet = wolfSSL_CTX_use_PrivateKey_Id(test->ctx, keyId, + (long)sizeof(keyId), INVALID_DEVID); + break; + } + case CB_CERT_CTX_KEYLABEL: + test->loadRet = wolfSSL_CTX_use_PrivateKey_Label(test->ctx, + "a-label", INVALID_DEVID); + break; + #endif + #ifdef OPENSSL_EXTRA + case CB_CERT_CTX_X509: { + WOLFSSL_X509* x509 = wolfSSL_X509_load_certificate_file(svrCertFile, + WOLFSSL_FILETYPE_PEM); + + if (x509 != NULL) { + test->loadRet = wolfSSL_CTX_use_certificate(test->ctx, x509); + wolfSSL_X509_free(x509); + } + break; + } + case CB_CERT_CTX_CHAIN: { + WOLFSSL_X509* x509 = wolfSSL_X509_load_certificate_file(svrCertFile, + WOLFSSL_FILETYPE_PEM); + + if (x509 != NULL) { + test->loadRet = wolfSSL_CTX_add1_chain_cert(test->ctx, x509); + wolfSSL_X509_free(x509); + } + break; + } + #endif + default: + test->loadRet = WOLFSSL_SUCCESS; + break; + } + + test->ctxCertAfter = (const void*)test->ctx->certificate; +} + +#ifdef HAVE_SNI +static int sni_cb_cert_swap(WOLFSSL* ssl, int* ad, void* arg) +{ + (void)ad; + cb_cert_action(ssl, (CbCertCase*)arg); + return 0; +} +#endif + +#ifdef WOLFSSL_CERT_SETUP_CB +/* This one reports success with 1. */ +static int cert_setup_cb_cert_swap(WOLFSSL* ssl, void* arg) +{ + cb_cert_action(ssl, (CbCertCase*)arg); + return 1; +} +#endif + +/* Drive one handshake whose callback does what mode asks for. + * + * @param [in] method_c Client method to handshake with. + * @param [in] method_s Server method to handshake with. + * @param [in] useCertCb Use the certificate setup callback rather than the + * sni callback. + * @param [in] mode One of the CB_CERT_* values. + * @return TEST_SUCCESS on success. + */ +static int test_cb_cert_swap(method_provider method_c, + method_provider method_s, int useCertCb, int mode) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + CbCertCase test; +#ifdef HAVE_SNI + const char* sni = "example.com"; +#endif + + XMEMSET(&test, 0, sizeof(test)); + test.mode = mode; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* The server object is built here rather than by test_memio_setup so the + * chain is on the context before the session takes a pointer to it. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + method_c, method_s), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx_s, svrCertFile), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + test.ctx = ctx_s; + + if (useCertCb) { + #ifdef WOLFSSL_CERT_SETUP_CB + wolfSSL_CTX_set_cert_cb(ctx_s, cert_setup_cb_cert_swap, &test); + #endif + } + else { + #ifdef HAVE_SNI + wolfSSL_CTX_set_servername_callback(ctx_s, sni_cb_cert_swap); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, &test), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, sni, + (word16)XSTRLEN(sni)), WOLFSSL_SUCCESS); + #endif + } + + /* Refusing the load leaves the handshake with everything it needs, so it + * completes either way. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The whole test rests on the callback having run. */ + ExpectIntGT(test.called, 0); + + if (cb_cert_touches_ctx(mode)) { + /* The load was turned away and the context kept what it had, so no + * session was left pointing at a freed buffer. */ + ExpectIntNE(test.loadRet, WOLFSSL_SUCCESS); + ExpectPtrEq(test.ctxCertAfter, test.ctxCertBefore); + } + else { + ExpectIntEQ(test.loadRet, WOLFSSL_SUCCESS); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + + return EXPECT_RESULT(); +} + +/* Run every mode against whichever protocol versions are built in. */ +static int test_cb_cert_swap_modes(int useCertCb) +{ + EXPECT_DECLS; + int mode; + + for (mode = 0; mode < CB_CERT_MODE_CNT; mode++) { + #ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(test_cb_cert_swap(wolfTLSv1_2_client_method, + wolfTLSv1_2_server_method, useCertCb, mode), TEST_SUCCESS); + #endif + #ifdef WOLFSSL_TLS13 + ExpectIntEQ(test_cb_cert_swap(wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method, useCertCb, mode), TEST_SUCCESS); + #endif + } + + return EXPECT_RESULT(); +} +#endif + +/* A callback cannot replace the certificate or key on the context its own + * handshake is running against. Sessions hold those buffers by pointer, so + * replacing one frees what handshakes in flight are reading. The load is + * refused, which leaves every session untouched. Setting a certificate on the + * session alone still works, as that is the session's own. + * + * This is the sni callback; the certificate setup callback is checked by + * test_cert_setup_cb_ctx_cert_swap_refused. */ +int test_sni_cb_ctx_cert_swap_refused(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13)) && \ + defined(HAVE_SNI) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) + ExpectIntEQ(test_cb_cert_swap_modes(0), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* The same for the certificate setup callback, the other place a handshake + * hands control to the application while holding the context's certificate. */ +int test_cert_setup_cb_ctx_cert_swap_refused(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13)) && \ + defined(WOLFSSL_CERT_SETUP_CB) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) + ExpectIntEQ(test_cb_cert_swap_modes(1), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SNI) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) +/* Hands the session a different context, which is what this callback is for. */ +static int sni_cb_switch_ctx(WOLFSSL* ssl, int* ad, void* arg) +{ + (void)ad; + return (wolfSSL_set_SSL_CTX(ssl, (WOLFSSL_CTX*)arg) != NULL) ? + 0 : fatal_return; +} +#endif + +/* Switching contexts from the callback must leave neither context marked as + * being in one: the note is per thread and is put back as it was, so both + * contexts can still be loaded once the handshake is over. */ +int test_sni_cb_switch_ctx_unblocked(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SNI) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL, *ctx_s2 = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + const char* sni = "example.com"; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* The context the callback hands over to, set up the same way. */ + ExpectNotNull(ctx_s2 = wolfSSL_CTX_new(wolfTLSv1_2_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s2, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s2, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_CTX_set_servername_callback(ctx_s, sni_cb_switch_ctx); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, ctx_s2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, sni, + (word16)XSTRLEN(sni)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Both contexts take a load afterwards. The one the session started on + * would stay refused if the note had been left behind on it. */ + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s2, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_s2); +#endif + return EXPECT_RESULT(); +} + +/* A second session on the same context must be unharmed by a callback that + * reached for the context's certificate. Detecting the swap after the fact + * only ever protected the session whose callback made the call; refusing the + * load protects the rest of them too. */ +int test_cb_ctx_cert_swap_other_session(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SNI) && \ + !defined(NO_RSA) && !defined(NO_FILESYSTEM) + struct test_memio_ctx test_ctx1; + struct test_memio_ctx test_ctx2; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c1 = NULL, *ssl_s1 = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + CbCertCase test; + const char* sni = "example.com"; + + XMEMSET(&test, 0, sizeof(test)); + test.mode = CB_CERT_CTX_CERT; + XMEMSET(&test_ctx1, 0, sizeof(test_ctx1)); + XMEMSET(&test_ctx2, 0, sizeof(test_ctx2)); + + ExpectIntEQ(test_memio_setup(&test_ctx1, &ctx_c, &ctx_s, &ssl_c1, &ssl_s1, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + test.ctx = ctx_s; + + /* Both sessions exist before anything is loaded, so both are holding the + * context's certificate. */ + ExpectNotNull(ssl_c2 = wolfSSL_new(ctx_c)); + wolfSSL_SetIOWriteCtx(ssl_c2, &test_ctx2); + wolfSSL_SetIOReadCtx(ssl_c2, &test_ctx2); + ExpectNotNull(ssl_s2 = wolfSSL_new(ctx_s)); + wolfSSL_SetIOWriteCtx(ssl_s2, &test_ctx2); + wolfSSL_SetIOReadCtx(ssl_s2, &test_ctx2); + + wolfSSL_CTX_set_servername_callback(ctx_s, sni_cb_cert_swap); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, &test), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c1, WOLFSSL_SNI_HOST_NAME, sni, + (word16)XSTRLEN(sni)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c1, ssl_s1, 10, NULL), 0); + ExpectIntGT(test.called, 0); + ExpectIntNE(test.loadRet, WOLFSSL_SUCCESS); + + /* The second session runs no callback of its own and must still find its + * certificate where it left it. */ + wolfSSL_CTX_set_servername_callback(ctx_s, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + + wolfSSL_free(ssl_c1); + wolfSSL_free(ssl_s1); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#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 6c3fda8996..3d3fe5eec3 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -52,6 +52,10 @@ int test_tls12_resume_ticket_decline_fallback(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_sni_cb_ctx_cert_swap_refused(void); +int test_cert_setup_cb_ctx_cert_swap_refused(void); +int test_cb_ctx_cert_swap_other_session(void); +int test_sni_cb_switch_ctx_unblocked(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); @@ -99,6 +103,10 @@ int test_wolfSSL_get_shared_ciphers(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_sni_cb_ctx_cert_swap_refused), \ + TEST_DECL_GROUP("tls", test_cert_setup_cb_ctx_cert_swap_refused), \ + TEST_DECL_GROUP("tls", test_cb_ctx_cert_swap_other_session), \ + TEST_DECL_GROUP("tls", test_sni_cb_switch_ctx_unblocked), \ 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 2f96892e06..4cf89096f3 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2347,6 +2347,17 @@ WOLFSSL_LOCAL int HashOutput(WOLFSSL* ssl, const byte* output, int sz, int ivSz); WOLFSSL_LOCAL int HashInput(WOLFSSL* ssl, const byte* input, int sz); +/* Bracket a call out to an application callback, so that a certificate or key + * load reaching back into the same context can be refused. */ +WOLFSSL_LOCAL WOLFSSL_CTX* CtxCallbackEnter(WOLFSSL_CTX* ctx); +WOLFSSL_LOCAL void CtxCallbackExit(WOLFSSL_CTX* prev); + +#ifndef NO_CERTS +/* Call before replacing a certificate or key on a context, to refuse the + * change while this thread is inside a callback on it. */ +WOLFSSL_LOCAL int CheckCtxCertLoad(WOLFSSL_CTX* ctx); +#endif + #ifdef HAVE_SNI #ifndef NO_WOLFSSL_SERVER WOLFSSL_LOCAL int SNI_Callback(WOLFSSL* ssl);