diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index a0b8e83cba0..fa6a9be260e 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -177,6 +177,10 @@ {"name": "sniffer-curves-enckeys", "minutes": 2.2, "configure": ["--enable-sniffer", "--enable-curve25519", "--enable-curve448", "--enable-enckeys", "--enable-aesccm", "CPPFLAGS=-DWOLFSSL_DH_EXTRA"]}, +{"name": "session-ticket-no-nonce-malloc", "minutes": 2.0, + "comment": "Only builds without WOLFSSL_TICKET_NONCE_MALLOC compile test_ticket_nonce_too_long, which checks that a ticket whose nonce does not fit the static buffer is dropped without failing the connection. Early data enables its checks that a shared session is not modified.", + "configure": ["--enable-session-ticket", "--enable-earlydata", + "--disable-ticket-nonce-malloc"]}, {"name": "sniffer-keylog-storedata", "minutes": 2.5, "comment": "The sniffer keylog legs of scripts/sniffer-testsuite.test only run with WOLFSSL_SNIFFER_KEYLOGFILE, and the store data callback path is only compiled with WOLFSSL_SNIFFER_STORE_DATA_CB. Neither is set by any other entry.", "configure": ["--enable-sniffer", "--enable-session-ticket", diff --git a/.github/workflows/mbedtls.sh b/.github/workflows/mbedtls.sh index d199fd2e3d1..e887d2434d2 100644 --- a/.github/workflows/mbedtls.sh +++ b/.github/workflows/mbedtls.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -e +set -o pipefail set -x # Basic TLS test @@ -28,6 +29,20 @@ env -C wolfssl ./examples/client/client -p 4433 -g \ kill $SERVER_PID sleep 0.1 +# TLS 1.3 resumption, Mbed TLS sends 32 byte ticket nonces +./mbedtls/build/programs/ssl/ssl_server2 force_version=tls13 tickets=1 \ + crt_file=mbedtls/framework/data_files/server2-sha256.crt \ + key_file=mbedtls/framework/data_files/server2.key.pem > /tmp/server.log 2>&1 & +SERVER_PID=$! +sleep 0.1 +env -C wolfssl ./examples/client/client -p 4433 -g -v 4 -r \ + -A ../mbedtls/framework/data_files/test-ca-sha256.crt \ + -c ../mbedtls/framework/data_files/cli-rsa-sha256.crt \ + -k ../mbedtls/framework/data_files/cli-rsa-sha256.key.pem | tee /tmp/client.log +grep -q "reused session id" /tmp/client.log +kill $SERVER_PID +sleep 0.1 + # Basic DTLS test ./mbedtls/build/programs/ssl/ssl_server2 dtls=1 > /tmp/server.log 2>&1 & SERVER_PID=$! diff --git a/.github/workflows/mbedtls.yml b/.github/workflows/mbedtls.yml index 724dc6cf710..82dc57fca7e 100644 --- a/.github/workflows/mbedtls.yml +++ b/.github/workflows/mbedtls.yml @@ -116,7 +116,7 @@ jobs: uses: wolfSSL/actions-build-autotools-project@v1 with: path: wolfssl - configure: --enable-dtls --enable-dtlscid + configure: --enable-dtls --enable-dtlscid --enable-session-ticket install: false check: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 534714847a1..bdf2169cc27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -387,10 +387,6 @@ if(WOLFSSL_ALL) "-DWOLFSSL_CERT_NAME_ALL" "-DWOLFSSL_VERBOSE_ERRORS" "-DWOLFSSL_HAVE_CERT_SERVICE" "-DHAVE_OCSP_RESPONDER" "-DWOLFSSL_TLS_OCSP_MULTI") - - # configure.ac derives this from session tickets under TLS 1.3; CMake's - # option is explicit, so name it. - default_option(WOLFSSL_TICKET_NONCE_MALLOC "yes") endif() add_option(WOLFSSL_ALL_CRYPTO @@ -2961,12 +2957,17 @@ if(NOT WOLFSSL_TLSV12) endif() add_option("WOLFSSL_TICKET_NONCE_MALLOC" - "Enable dynamic allocation of ticket nonces (default: disabled)" - "no" "yes;no") + "Enable dynamic allocation of ticket nonces (default: undefined, settings.h enables it for eligible TLS 1.3 clients)" + "undefined" "yes;no;undefined") -if(WOLFSSL_TICKET_NONCE_MALLOC) +if("${WOLFSSL_TICKET_NONCE_MALLOC}" STREQUAL "yes") list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_TICKET_NONCE_MALLOC") +elseif("${WOLFSSL_TICKET_NONCE_MALLOC}" STREQUAL "no") + list(APPEND WOLFSSL_DEFINITIONS + "-DWOLFSSL_NO_TICKET_NONCE_MALLOC") +else() + set(WOLFSSL_TICKET_NONCE_MALLOC OFF) # keeps options.h to settings.h endif() # Extended master secret extension diff --git a/cmake/options.h.in b/cmake/options.h.in index 6c35a38de09..c19d8fc163d 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -376,6 +376,8 @@ extern "C" { #cmakedefine WOLFSSL_NO_SHAKE128 #undef WOLFSSL_NO_SHAKE256 #cmakedefine WOLFSSL_NO_SHAKE256 +#undef WOLFSSL_NO_TICKET_NONCE_MALLOC +#cmakedefine WOLFSSL_NO_TICKET_NONCE_MALLOC #undef WOLFSSL_NO_TLS12 #cmakedefine WOLFSSL_NO_TLS12 #undef WOLFSSL_POST_HANDSHAKE_AUTH diff --git a/configure.ac b/configure.ac index 8504097da7f..bd98033d256 100644 --- a/configure.ac +++ b/configure.ac @@ -10105,15 +10105,19 @@ then AM_CFLAGS="$AM_CFLAGS -DHAVE_TLS_EXTENSIONS -DHAVE_SESSION_TICKET" fi +# By default settings.h enables it for TLS 1.3 clients. AC_ARG_ENABLE([ticket-nonce-malloc], - [AS_HELP_STRING([--enable-ticket-nonce-malloc], [Enable dynamic allocation of ticket nonces (default: disabled)])], + [AS_HELP_STRING([--enable-ticket-nonce-malloc], [Enable dynamic allocation of ticket nonces (default: enabled for TLS 1.3 clients with session tickets, except FIPS, selftest, MAXQ10xx and WOLFSSL_NO_MALLOC builds)])], [ ENABLED_TICKET_NONCE_MALLOC=$enableval ], - [ ENABLED_TICKET_NONCE_MALLOC=no_implicit ] + [ ENABLED_TICKET_NONCE_MALLOC=default ] ) if test "$ENABLED_TICKET_NONCE_MALLOC" = "yes" then AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_TICKET_NONCE_MALLOC" +elif test "$ENABLED_TICKET_NONCE_MALLOC" = "no" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_NO_TICKET_NONCE_MALLOC" fi # Extended Master Secret Extension @@ -11224,7 +11228,7 @@ then [AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_DES_ECB"]) # support longer session ticket nonce - if test "$ENABLED_TICKET_NONCE_MALLOC" = "no_implicit" + if test "$ENABLED_TICKET_NONCE_MALLOC" = "default" then ENABLED_TICKET_NONCE_MALLOC="yes" AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_TICKET_NONCE_MALLOC" diff --git a/src/internal.c b/src/internal.c index 81f89bd4c90..be5c0b63a33 100644 --- a/src/internal.c +++ b/src/internal.c @@ -45557,7 +45557,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif /* Resumption master secret. */ XMEMCPY(it->msecret, ssl->session->masterSecret, SECRET_LEN); - if (ssl->session->ticketNonce.len > MAX_TICKET_NONCE_STATIC_SZ) { + if (ssl->session->ticketNonce.len > INTERNAL_TICKET_NONCE_SZ) { WOLFSSL_MSG("Bad ticket nonce value"); ret = BAD_TICKET_MSG_SZ; goto error; @@ -45830,7 +45830,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifdef WOLFSSL_TLS13 /* Check resumption master secret. */ if (IsAtLeastTLSv1_3(it->pv) && - it->ticketNonceLen > MAX_TICKET_NONCE_STATIC_SZ) { + it->ticketNonceLen > INTERNAL_TICKET_NONCE_SZ) { WOLFSSL_MSG("Unsupported ticketNonce len in ticket"); return BAD_TICKET_ENCRYPT; } @@ -46068,7 +46068,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifdef WOLFSSL_TLS13 /* This should have been already checked in * DoClientTicketCheckVersion */ - if (it->ticketNonceLen > MAX_TICKET_NONCE_STATIC_SZ) { + if (it->ticketNonceLen > INTERNAL_TICKET_NONCE_SZ) { WOLFSSL_MSG("Unsupported ticketNonce len in ticket"); return; } @@ -46147,7 +46147,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif c32toa(sess->ticketAdd, it->ageAdd); c16toa(sess->namedGroup, it->namedGroup); - if (sess->ticketNonce.len <= MAX_TICKET_NONCE_STATIC_SZ) { + if (sess->ticketNonce.len <= INTERNAL_TICKET_NONCE_SZ) { it->ticketNonceLen = sess->ticketNonce.len; XMEMCPY(it->ticketNonce, sess->ticketNonce.data, sess->ticketNonce.len); diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 59b6fd84915..678ccc8e8a4 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -424,8 +424,12 @@ static int SessionSanityPointerSet(int row) #endif /* HAVE_SESSION_TICKET */ #if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || \ !defined(NO_PSK)) + /* A nonce that was on the heap is lost, so drop its ticket */ if (s->ticketNonce.len > MAX_TICKET_NONCE_STATIC_SZ) { - s->ticketNonce.len = MAX_TICKET_NONCE_STATIC_SZ; + s->ticketNonce.len = 0; + #ifdef HAVE_SESSION_TICKET + s->ticketLen = 0; + #endif } #if defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && \ @@ -1412,15 +1416,11 @@ static int GetSessionFromCache(WOLFSSL* ssl, WOLFSSL_SESSION* output, output->ticketNonce.data = output->ticketNonce.dataStatic; output->ticketNonce.len = 0; } - error = SessionTicketNoncePrealloc(&preallocNonce, &preallocNonceLen, - output->heap); - if (error != 0) { - if (tmpBufSet) { - output->ticket = output->staticTicket; - output->ticketLenAlloc = 0; - } - WC_FREE_VAR_EX(tmpTicket, output->heap, DYNAMIC_TYPE_TMP_BUFFER); - return WOLFSSL_FAILURE; + /* Only client sessions can hold a nonce longer than the static buffer. + * Without the buffer only such a session fails to copy. */ + if (ssl->options.side == WOLFSSL_CLIENT_END) { + (void)SessionTicketNoncePrealloc(&preallocNonce, &preallocNonceLen, + output->heap); } #endif /* WOLFSSL_TLS13 && HAVE_SESSION_TICKET*/ diff --git a/src/tls13.c b/src/tls13.c index 1a4e34e73c6..c5813af87c1 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -83,7 +83,10 @@ * TLS 1.3 Session Tickets: * WOLFSSL_TICKET_HAVE_ID: Session tickets include ID default: off * Forced on when WOLFSSL_EARLY_DATA is set. - * WOLFSSL_TICKET_NONCE_MALLOC: Dynamically allocate ticket nonce default: off + * WOLFSSL_TICKET_NONCE_MALLOC: Allocate nonces longer than default: on + * TLS13_TICKET_NONCE_STATIC_SZ. Off for FIPS, + * selftest, MAXQ10xx and WOLFSSL_NO_MALLOC + * builds, or with WOLFSSL_NO_TICKET_NONCE_MALLOC. * WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES: Withhold NewSessionTicket default: off * when the ClientHello advertised no usable * psk_key_exchange_modes, as RFC 9846 Sections @@ -1660,18 +1663,25 @@ int DeriveResumptionPSK(WOLFSSL* ssl, byte* nonce, byte nonceLen, byte* secret) #if defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) - PRIVATE_KEY_UNLOCK(); - ret = wc_Tls13_HKDF_Expand_Label_Alloc(secret, ssl->specs.hash_size, - ssl->session->masterSecret, ssl->specs.hash_size, protocol, protocolLen, - resumptionLabel, RESUMPTION_LABEL_SZ, nonce, nonceLen, digestAlg, - ssl->heap); - PRIVATE_KEY_LOCK(); -#else - ret = Tls13HKDFExpandLabel(ssl, secret, ssl->specs.hash_size, - ssl->session->masterSecret, ssl->specs.hash_size, - protocol, protocolLen, resumptionLabel, - RESUMPTION_LABEL_SZ, nonce, nonceLen, digestAlg); -#endif /* !defined(HAVE_FIPS) || FIPS_VERSION_GE(5,3) */ + /* Only a nonce too long for the fixed HKDF label buffer needs the heap */ + if (OPAQUE16_LEN + OPAQUE8_LEN + protocolLen + RESUMPTION_LABEL_SZ + + OPAQUE8_LEN + nonceLen > MAX_TLS13_HKDF_LABEL_SZ) { + PRIVATE_KEY_UNLOCK(); + ret = wc_Tls13_HKDF_Expand_Label_Alloc(secret, ssl->specs.hash_size, + ssl->session->masterSecret, ssl->specs.hash_size, protocol, + protocolLen, resumptionLabel, RESUMPTION_LABEL_SZ, nonce, nonceLen, + digestAlg, ssl->heap); + PRIVATE_KEY_LOCK(); + } + else +#endif /* WOLFSSL_TICKET_NONCE_MALLOC && FIPS_VERSION_GE(5,3) */ + { + ret = Tls13HKDFExpandLabel(ssl, secret, ssl->specs.hash_size, + ssl->session->masterSecret, + ssl->specs.hash_size, protocol, protocolLen, + resumptionLabel, RESUMPTION_LABEL_SZ, nonce, + nonceLen, digestAlg); + } return ret; } #endif /* HAVE_SESSION_TICKET */ @@ -14730,6 +14740,10 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input, #endif const byte* nonce; byte nonceLength; + int storeTicket = 1; +#ifdef WOLFSSL_EARLY_DATA + word32 maxEarlyDataSz; +#endif WOLFSSL_START(WC_FUNC_NEW_SESSION_TICKET_DO); WOLFSSL_ENTER("DoTls13NewSessionTicket"); @@ -14762,14 +14776,13 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input, if ((*inOutIdx - begin) + 1 > size) return BUFFER_ERROR; nonceLength = input[*inOutIdx]; -#if !defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ - (!defined(HAVE_FIPS) || FIPS_VERSION_GE(5,3)) +#if !defined(WOLFSSL_TICKET_NONCE_MALLOC) || (defined(HAVE_FIPS) && \ + !(defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) if (nonceLength > MAX_TICKET_NONCE_STATIC_SZ) { - WOLFSSL_MSG("Nonce length not supported"); - WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); - return INVALID_PARAMETER; + WOLFSSL_MSG("Ticket nonce too long to store, ignoring ticket"); + storeTicket = 0; } -#endif /* WOLFSSL_TICKET_NONCE_MALLOC && FIPS_VERSION_GE(5,3) */ +#endif /* !WOLFSSL_TICKET_NONCE_MALLOC || !FIPS_VERSION_GE(5,3) */ *inOutIdx += 1; if ((*inOutIdx - begin) + nonceLength > size) return BUFFER_ERROR; @@ -14788,40 +14801,38 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input, if (length == 0) return BUFFER_ERROR; - if ((ret = SetTicket(ssl, input + *inOutIdx, length)) != 0) - return ret; - *inOutIdx += length; + if (storeTicket) { + if ((ret = SetTicket(ssl, input + *inOutIdx, length)) != 0) + return ret; - now = TimeNowInMilliseconds(); - if (now == 0) - return GETTIME_ERROR; - /* Copy in ticket data (server identity). */ - ssl->timeout = lifetime; - ssl->session->timeout = lifetime; - ssl->session->cipherSuite0 = ssl->options.cipherSuite0; - ssl->session->cipherSuite = ssl->options.cipherSuite; - ssl->session->ticketSeen = now; - ssl->session->ticketAdd = ageAdd; - #ifdef WOLFSSL_EARLY_DATA - ssl->session->maxEarlyDataSz = ssl->options.maxEarlyDataSz; - #endif + now = TimeNowInMilliseconds(); + if (now == 0) + return GETTIME_ERROR; + /* Copy in ticket data (server identity). */ + ssl->timeout = lifetime; + ssl->session->timeout = lifetime; + ssl->session->cipherSuite0 = ssl->options.cipherSuite0; + ssl->session->cipherSuite = ssl->options.cipherSuite; + ssl->session->ticketSeen = now; + ssl->session->ticketAdd = ageAdd; + #ifdef WOLFSSL_EARLY_DATA + ssl->session->maxEarlyDataSz = ssl->options.maxEarlyDataSz; + #endif #if defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) - ret = SessionTicketNoncePopulate(ssl->session, nonce, nonceLength); - if (ret != 0) - return ret; + ret = SessionTicketNoncePopulate(ssl->session, nonce, nonceLength); + if (ret != 0) + return ret; #else - ssl->session->ticketNonce.len = nonceLength; - if (nonceLength > MAX_TICKET_NONCE_STATIC_SZ) { - ret = BUFFER_ERROR; - return ret; - } - if (nonceLength > 0) - XMEMCPY(ssl->session->ticketNonce.data, nonce, nonceLength); + ssl->session->ticketNonce.len = nonceLength; + if (nonceLength > 0) + XMEMCPY(ssl->session->ticketNonce.data, nonce, nonceLength); #endif /* defined(WOLFSSL_TICKET_NONCE_MALLOC) && FIPS_VERSION_GE(5,3) */ - ssl->session->namedGroup = ssl->namedGroup; + ssl->session->namedGroup = ssl->namedGroup; + } + *inOutIdx += length; if ((*inOutIdx - begin) + EXTS_SZ > size) return BUFFER_ERROR; @@ -14832,15 +14843,27 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input, /* RFC 9846 Section 4.7.1: the extensions are Section 4.3 Extension TLVs. * Malformed framing is a syntax error even when no extension in the list * is one we act on. */ +#ifdef WOLFSSL_EARLY_DATA + if (!storeTicket && !HaveUniqueSessionObj(ssl)) + return MEMORY_ERROR; + maxEarlyDataSz = ssl->session->maxEarlyDataSz; +#endif ret = TLSX_Parse(ssl, input + *inOutIdx, length, session_ticket, NULL); +#ifdef WOLFSSL_EARLY_DATA + /* Undo the early_data update to the session of the kept ticket. */ + if (!storeTicket) + ssl->session->maxEarlyDataSz = maxEarlyDataSz; +#endif if (ret != 0) return ret; *inOutIdx += length; - SetupSession(ssl); + if (storeTicket) { + SetupSession(ssl); #ifndef NO_SESSION_CACHE AddSession(ssl); #endif + } ssl->expect_session_ticket = 0; #else diff --git a/tests/api.c b/tests/api.c index 1a1f9426ebe..04d3d4fc7d5 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39801,16 +39801,16 @@ static int test_wolfSSL_FIPS_mode(void) -#if defined(WOLFSSL_TICKET_NONCE_MALLOC) && defined(HAVE_SESSION_TICKET) \ - && defined(WOLFSSL_TLS13) && \ - (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)))\ - && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) -static int send_new_session_ticket(WOLFSSL *ssl, byte nonceLength, byte filler) +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) +static int send_new_session_ticket(WOLFSSL *ssl, byte nonceLength, byte filler, + word32 maxEarlyData) { struct test_memio_ctx *test_ctx; byte buf[2048]; int idx, sz; word32 tmp; + word32 extLen = (maxEarlyData != 0) ? 8 : 0; int ret; idx = 5; /* space for record header */ @@ -39821,13 +39821,13 @@ static int send_new_session_ticket(WOLFSSL *ssl, byte nonceLength, byte filler) tmp = OPAQUE32_LEN + OPAQUE32_LEN + OPAQUE8_LEN + nonceLength + - OPAQUE16_LEN + OPAQUE8_LEN + OPAQUE16_LEN; + OPAQUE16_LEN + OPAQUE8_LEN + OPAQUE16_LEN + extLen; c32to24(tmp, buf + idx); idx += OPAQUE24_LEN; c32toa((word32)12345, buf+idx); /* lifetime */ idx += OPAQUE32_LEN; - c32toa((word32)12345, buf+idx); /* add */ + c32toa((word32)filler, buf+idx); /* add */ idx += OPAQUE32_LEN; buf[idx] = nonceLength; /* nonce length */ idx++; @@ -39836,11 +39836,18 @@ static int send_new_session_ticket(WOLFSSL *ssl, byte nonceLength, byte filler) tmp = 1; /* ticket len */ c16toa((word16)tmp, buf+idx); idx += 2; - buf[idx] = 0xFF; /* ticket */ + buf[idx] = filler; /* ticket */ idx++; - tmp = 0; /* ext len */ - c16toa((word16)tmp, buf+idx); + c16toa((word16)extLen, buf+idx); idx += 2; + if (maxEarlyData != 0) { + c16toa(TLSXT_EARLY_DATA, buf+idx); + idx += 2; + c16toa(OPAQUE32_LEN, buf+idx); + idx += 2; + c32toa(maxEarlyData, buf+idx); + idx += OPAQUE32_LEN; + } sz = BuildTls13Message(ssl, buf, 2048, buf+5, idx - 5, handshake, 0, 0, 0); @@ -39850,7 +39857,12 @@ static int send_new_session_ticket(WOLFSSL *ssl, byte nonceLength, byte filler) ret = test_memio_write_cb(ssl, (char*)buf, sz, test_ctx); return !(ret == sz); } +#endif +#if defined(WOLFSSL_TICKET_NONCE_MALLOC) && defined(HAVE_SESSION_TICKET) \ + && defined(WOLFSSL_TLS13) && \ + (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)))\ + && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) static int test_ticket_nonce_check(WOLFSSL_SESSION *sess, byte len) { int ret = 0; @@ -39876,7 +39888,7 @@ static int test_ticket_nonce_malloc_do(WOLFSSL *ssl_s, WOLFSSL *ssl_c, byte len) EXPECT_DECLS; char *buf[1024]; - ExpectIntEQ(send_new_session_ticket(ssl_s, len, len), 0); + ExpectIntEQ(send_new_session_ticket(ssl_s, len, len, 0), 0); ExpectTrue((wolfSSL_recv(ssl_c, buf, 1024, 0) == WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)) && (ssl_c->error == WC_NO_ERR_TRACE(WANT_READ))); @@ -39910,6 +39922,45 @@ static int test_ticket_nonce_cache(WOLFSSL *ssl_s, WOLFSSL *ssl_c, byte len) return EXPECT_RESULT(); } +#if defined(PERSIST_SESSION_CACHE) && !defined(SESSION_CACHE_DYNAMIC_MEM) +static int test_ticket_nonce_cache_restore(WOLFSSL *ssl_s, WOLFSSL *ssl_c, + byte len) +{ + EXPECT_DECLS; + WOLFSSL_SESSION *cached = NULL; + byte *mem = NULL; + int memSz = 0; + + ExpectIntEQ(test_ticket_nonce_cache(ssl_s, ssl_c, len), TEST_SUCCESS); + ExpectIntGT(memSz = wolfSSL_get_session_cache_memsize(), 0); + ExpectNotNull(mem = (byte*)XMALLOC((size_t)memSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(wolfSSL_memsave_session_cache(mem, memSz), WOLFSSL_SUCCESS); + /* Frees the heap nonces that the saved image still points to */ + wolfSSL_CTX_flush_sessions(ssl_c->ctx, 0x7FFFFFFFL); + ExpectIntEQ(wolfSSL_memrestore_session_cache(mem, memSz), WOLFSSL_SUCCESS); + + ExpectNotNull(cached = wolfSSL_SESSION_new()); + ExpectIntEQ(wolfSSL_GetSessionFromCache(ssl_c, cached), WOLFSSL_SUCCESS); +#if TLS13_TICKET_NONCE_STATIC_SZ < TLS13_TICKET_NONCE_MAX_SZ + if (len > MAX_TICKET_NONCE_STATIC_SZ) { + ExpectIntEQ(cached->ticketNonce.len, 0); + ExpectIntEQ(cached->ticketLen, 0); + } + else +#endif + { + ExpectIntEQ(test_ticket_nonce_check(cached, len), 0); + ExpectIntGT(cached->ticketLen, 0); + } + + wolfSSL_SESSION_free(cached); + XFREE(mem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + return EXPECT_RESULT(); +} +#endif + static int test_ticket_nonce_malloc(void) { EXPECT_DECLS; @@ -39949,7 +40000,7 @@ static int test_ticket_nonce_malloc(void) medium = 255; #endif #if TLS13_TICKET_NONCE_STATIC_SZ + 20 + 20 <= 255 - big = small + 20; + big = small + 40; #else big = 255; #endif @@ -39966,6 +40017,12 @@ static int test_ticket_nonce_malloc(void) ExpectIntEQ(test_ticket_nonce_cache(ssl_s, ssl_c, small), TEST_SUCCESS); ExpectIntEQ(test_ticket_nonce_cache(ssl_s, ssl_c, medium), TEST_SUCCESS); ExpectIntEQ(test_ticket_nonce_cache(ssl_s, ssl_c, big), TEST_SUCCESS); +#if defined(PERSIST_SESSION_CACHE) && !defined(SESSION_CACHE_DYNAMIC_MEM) + ExpectIntEQ(test_ticket_nonce_cache_restore(ssl_s, ssl_c, small), + TEST_SUCCESS); + ExpectIntEQ(test_ticket_nonce_cache_restore(ssl_s, ssl_c, big), + TEST_SUCCESS); +#endif ExpectIntEQ(test_ticket_nonce_cache(ssl_s, ssl_c, medium), TEST_SUCCESS); ExpectIntEQ(test_ticket_nonce_cache(ssl_s, ssl_c, small), TEST_SUCCESS); @@ -39977,6 +40034,74 @@ static int test_ticket_nonce_malloc(void) return EXPECT_RESULT(); } +#elif defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) +static int test_ticket_nonce_too_long(void) +{ + EXPECT_DECLS; +#if TLS13_TICKET_NONCE_STATIC_SZ < TLS13_TICKET_NONCE_MAX_SZ + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + char buf[64]; + byte small = TLS13_TICKET_NONCE_STATIC_SZ; + byte big = TLS13_TICKET_NONCE_MAX_SZ; +#ifdef WOLFSSL_EARLY_DATA + WOLFSSL_SESSION *held = NULL; + word32 earlyData = 0x100; +#else + word32 earlyData = 0; +#endif + + 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); + ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectIntEQ(send_new_session_ticket(ssl_s, small, small, earlyData), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_c->session->ticketNonce.len, small); + +#ifdef WOLFSSL_EARLY_DATA + ExpectNotNull(held = wolfSSL_get1_session(ssl_c)); +#endif + /* A nonce that does not fit drops the ticket, not the connection. */ + ExpectIntEQ(send_new_session_ticket(ssl_s, big, big, earlyData * 2), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_c->session->ticketNonce.len, small); + ExpectIntEQ(ssl_c->session->ticketNonce.data[0], small); + ExpectIntEQ(ssl_c->session->ticketLen, 1); + ExpectIntEQ(ssl_c->session->ticket[0], small); + ExpectIntEQ(ssl_c->session->ticketAdd, small); +#ifdef WOLFSSL_EARLY_DATA + ExpectIntEQ(ssl_c->session->maxEarlyDataSz, earlyData); + ExpectPtrNE(ssl_c->session, held); + ExpectIntEQ(held->maxEarlyDataSz, earlyData); + wolfSSL_SESSION_free(held); +#endif + + ExpectIntEQ(wolfSSL_write(ssl_s, "ping", 4), 4); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), 4); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#else + (void)send_new_session_ticket; +#endif + return EXPECT_RESULT(); +} #endif /* WOLFSSL_TICKET_NONCE_MALLOC */ #if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_TLS12) && \ @@ -47331,6 +47456,9 @@ TEST_CASE testCases[] = { (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)))\ && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) TEST_DECL(test_ticket_nonce_malloc), +#elif defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + TEST_DECL(test_ticket_nonce_too_long), #endif TEST_DECL(test_ticket_ret_create), TEST_DECL(test_ticket_expired_full_handshake), diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 4101dfa9b3d..e912a805210 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -10863,6 +10863,63 @@ int test_tls13_derive_keys_no_key(void) return EXPECT_RESULT(); } +/* Resumption PSKs from nonces too long for the fixed HKDF label buffer */ +int test_tls13_resumption_psk_long_nonce(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(WOLFSSL_TICKET_NONCE_MALLOC) && !defined(NO_SHA256) && \ + (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)))\ + && defined(HAVE_AESGCM) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + byte nonce[TLS13_TICKET_NONCE_MAX_SZ]; + byte psk[WC_SHA256_DIGEST_SIZE]; + byte expected[WC_SHA256_DIGEST_SIZE]; + int lens[3]; + int i; + + /* largest nonce whose HkdfLabel still fits MAX_TLS13_HKDF_LABEL_SZ */ + lens[0] = MAX_TLS13_HKDF_LABEL_SZ - + (OPAQUE16_LEN + OPAQUE8_LEN + 6 + 10 + OPAQUE8_LEN); + lens[1] = lens[0] + 1; + lens[2] = TLS13_TICKET_NONCE_MAX_SZ; + XMEMSET(nonce, 0x5a, sizeof(nonce)); + 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); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, "TLS13-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + for (i = 0; i < 3 && EXPECT_SUCCESS(); i++) { + if (lens[i] > TLS13_TICKET_NONCE_MAX_SZ) + continue; + ExpectIntEQ(DeriveResumptionPSK(ssl_c, nonce, (byte)lens[i], psk), 0); + PRIVATE_KEY_UNLOCK(); + ExpectIntEQ(wc_Tls13_HKDF_Expand_Label_Alloc(expected, + WC_SHA256_DIGEST_SIZE, ssl_c->session->masterSecret, + WC_SHA256_DIGEST_SIZE, (const byte*)"tls13 ", 6, + (const byte*)"resumption", 10, nonce, (word32)lens[i], WC_SHA256, + NULL), 0); + PRIVATE_KEY_LOCK(); + ExpectBufEQ(psk, expected, sizeof(psk)); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + + return EXPECT_RESULT(); +} + /* Test that a truncated PQC hybrid KeyShare in a ServerHello does not cause a * heap use-after-free during cleanup. A malicious server sends * SECP256R1MLKEM768 with only 10 bytes of key exchange data (expected: 1120+). diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 3b1059dcb25..2972beb30bf 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -81,6 +81,7 @@ int test_tls13_hrr_recognized_ext_downgrade(void); int test_tls13_cert_req_sigalgs(void); int test_tls13_sha1_cert_chain(void); int test_tls13_derive_keys_no_key(void); +int test_tls13_resumption_psk_long_nonce(void); int test_tls13_pqc_hybrid_truncated_keyshare(void); int test_tls13_pqc_hybrid_malformed_ecdh(void); int test_tls13_empty_record_limit(void); @@ -267,6 +268,7 @@ int test_tls13_AEAD_limit_KeyUpdate_want_write(void); TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \ TEST_DECL_GROUP("tls13", test_tls13_sha1_cert_chain), \ TEST_DECL_GROUP("tls13", test_tls13_derive_keys_no_key), \ + TEST_DECL_GROUP("tls13", test_tls13_resumption_psk_long_nonce), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_truncated_keyshare), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_malformed_ecdh), \ TEST_DECL_GROUP("tls13", test_tls13_empty_record_limit), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 3974ba87cbc..8c58641c24d 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1503,7 +1503,15 @@ enum { #endif #ifndef TLS13_TICKET_NONCE_STATIC_SZ +#if !defined(WOLFSSL_TLS13) || !defined(HAVE_SESSION_TICKET) || \ + defined(NO_WOLFSSL_CLIENT) || \ + (defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ + (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)))) #define TLS13_TICKET_NONCE_STATIC_SZ 8 +#else +/* Fits the 32 byte nonces Mbed TLS servers send */ +#define TLS13_TICKET_NONCE_STATIC_SZ 32 +#endif #endif #if TLS13_TICKET_NONCE_STATIC_SZ > TLS13_TICKET_NONCE_MAX_SZ @@ -1612,6 +1620,11 @@ enum Misc { #if defined(WOLFSSL_TLS13) || !defined(NO_PSK) MAX_TICKET_NONCE_STATIC_SZ = TLS13_TICKET_NONCE_STATIC_SZ, /* maximum ticket nonce static size */ +#if TLS13_TICKET_NONCE_STATIC_SZ < 8 + INTERNAL_TICKET_NONCE_SZ = TLS13_TICKET_NONCE_STATIC_SZ, +#else + INTERNAL_TICKET_NONCE_SZ = 8, /* nonce field in tickets we issue */ +#endif #endif /* WOLFSSL_TLS13 || !NO_PSK */ MAX_LIFETIME = 604800, /* maximum ticket lifetime */ @@ -2236,7 +2249,7 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #endif #ifndef PREALLOC_SESSION_TICKET_NONCE_LEN - #define PREALLOC_SESSION_TICKET_NONCE_LEN 32 + #define PREALLOC_SESSION_TICKET_NONCE_LEN TLS13_TICKET_NONCE_MAX_SZ #endif #ifndef SESSION_TICKET_HINT_DEFAULT @@ -4065,7 +4078,7 @@ typedef struct InternalTicket { byte ageAdd[AGEADD_LEN]; /* Obfuscation of age */ byte namedGroup[NAMEDGROUP_LEN]; /* Named group used */ byte ticketNonceLen; - byte ticketNonce[MAX_TICKET_NONCE_STATIC_SZ]; + byte ticketNonce[INTERNAL_TICKET_NONCE_SZ]; #ifdef WOLFSSL_EARLY_DATA byte maxEarlyDataSz[MAXEARLYDATASZ_LEN]; /* Max size of * early data */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index bd38b9d1aeb..d7f0a9ca70d 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -5395,6 +5395,16 @@ blinding by defining WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS." #undef HAVE_SESSION_TICKET #endif +/* Accept TLS 1.3 ticket nonces longer than TLS13_TICKET_NONCE_STATIC_SZ */ +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(HAVE_FIPS) && \ + !defined(HAVE_SELFTEST) && !defined(WOLFSSL_MAXQ1065) && \ + (!defined(WOLFSSL_NO_MALLOC) || defined(WOLFSSL_STATIC_MEMORY)) && \ + !defined(WOLFSSL_MAXQ108X) && !defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ + !defined(WOLFSSL_NO_TICKET_NONCE_MALLOC) + #define WOLFSSL_TICKET_NONCE_MALLOC +#endif + /* Use static ECC structs for Position Independent Code (PIC) */ #if defined(__IAR_SYSTEMS_ICC__) && defined(__ROPI__) #define WOLFSSL_ECC_CURVE_STATIC