From 2cc5d9d469769de2bd89f5d202ea45b0693be4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 13 Oct 2026 19:05:47 +0200 Subject: [PATCH 1/4] Ignore TLS 1.3 tickets whose nonce cannot be stored RFC 8446 allows a NewSessionTicket nonce of up to 255 bytes. Without WOLFSSL_TICKET_NONCE_MALLOC the client stores at most TLS13_TICKET_NONCE_STATIC_SZ bytes and failed the whole connection with INVALID_PARAMETER when a server sent more. The ticket only enables a later resumption, so losing it should not take down a working connection. Mbed TLS servers send 32 byte nonces by default, which broke the first wolfSSL_read() after the handshake. DoTls13NewSessionTicket now parses and validates the full message but leaves the session untouched when the nonce does not fit. The early_data extension still goes through TLSX_Parse for its checks, and its update to the session's maxEarlyDataSz is undone so the kept ticket stays consistent. The session is made unique first, as SetTicket() does, so that temporary update never lands on a session the application holds through wolfSSL_get1_session(). A failed allocation is still fatal, as it is for the ticket itself. --- src/tls13.c | 85 +++++++++++++++++++++++++----------------- tests/api.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 145 insertions(+), 45 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index 1a4e34e73c6..e4590ca243f 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -14730,6 +14730,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 +14766,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 +14791,42 @@ 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); + if (nonceLength > MAX_TICKET_NONCE_STATIC_SZ) { + ret = BUFFER_ERROR; + return ret; + } + 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 +14837,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..cda073000f3 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))); @@ -39977,6 +39989,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 +47411,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), From 0f9fe16e674522ec7e7e747cd9c1a7e8e691e1b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 13 Oct 2026 19:05:52 +0200 Subject: [PATCH 2/4] Raise the static ticket nonce size to 32 bytes without nonce malloc Without WOLFSSL_TICKET_NONCE_MALLOC the static buffer is the only place a client can keep a NewSessionTicket nonce. 8 bytes covers OpenSSL and wolfSSL but not the 32 byte nonces Mbed TLS servers send, so those builds now default to 32. Builds with WOLFSSL_TICKET_NONCE_MALLOC keep 8 and allocate longer nonces, so the extra 24 bytes per WOLFSSL_SESSION and session cache entry only lands where it is needed. Builds that never receive a nonce (no TLS 1.3, no session tickets or no client) keep 8 as well. InternalTicket sized its nonce field by the same constant, although a wolfSSL server only ever puts its own one byte nonce there. It now uses INTERNAL_TICKET_NONCE_SZ, which stays at 8 (or the static size if that is smaller), so tickets issued by a wolfSSL server keep their size and layout in every configuration. wolfSSL_GetSessionFromCache copies a nonce that does not fit the static buffer into a PREALLOC_SESSION_TICKET_NONCE_LEN buffer while the cache row is locked, and failed for anything longer than 32 bytes. The preallocation now covers the full 255 byte range. It is only made for client sessions, since a server session only holds its own one byte nonce, and a failed preallocation no longer fails the lookup; only a session whose nonce really needs it is then skipped. Restoring a persisted session cache cannot bring back a nonce that was on the heap. The sanitizer used to clamp its length to the static buffer, leaving a valid ticket paired with stale nonce bytes, so the next resumption sent a wrong binder and the server aborted the handshake. It now drops the ticket, which gives a full handshake. test_ticket_nonce_malloc never exercised a nonce above 28 bytes because "big" was computed as small + 20 instead of small + 40; fixed so the cache path is tested above the old prealloc size. It also checks a save and restore of the session cache with a heap held nonce. --- src/internal.c | 8 ++++---- src/ssl_sess.c | 20 ++++++++++---------- tests/api.c | 36 +++++++++++++++++++++++++++++++++++- wolfssl/internal.h | 17 +++++++++++++++-- 4 files changed, 64 insertions(+), 17 deletions(-) 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/tests/api.c b/tests/api.c index cda073000f3..067c7608649 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39922,6 +39922,36 @@ 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); + ExpectIntEQ(cached->ticketNonce.len, 0); + ExpectIntEQ(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; @@ -39961,7 +39991,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 @@ -39978,6 +40008,10 @@ 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, 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); 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 */ From 71ae1d8b44fc4ee127fd1aedd0c4844fbd27c688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 13 Oct 2026 19:05:58 +0200 Subject: [PATCH 3/4] Enable ticket nonce allocation by default for TLS 1.3 clients WOLFSSL_TICKET_NONCE_MALLOC was only set by --enable-curl (and through it --enable-all) and by CMake's WOLFSSL_ALL, so default, minimal and user_settings.h builds could not store a ticket nonce longer than the static buffer. settings.h now defines it for TLS 1.3 clients with session tickets. Nonces that fit the static buffer still use it, so the heap is only touched for longer ones. With the macro on, the static buffer goes back to 8 bytes. DeriveResumptionPSK used wc_Tls13_HKDF_Expand_Label_Alloc() whenever the macro was defined, for every nonce length. That function runs HKDF in software, so it skipped the HKDF PK callback, the crypto callback device and async support that Tls13HKDFExpandLabel() provides. It is now used only when the label does not fit MAX_TLS13_HKDF_LABEL_SZ, which means nonces of roughly 60 bytes or more. Longer nonces still bypass those hooks; the result is the same since the resumption master secret is in memory. test_tls13_resumption_psk_long_nonce checks both sides of the threshold against wc_Tls13_HKDF_Expand_Label_Alloc(). FIPS and self-test builds are left out of the default because the macro also compiles wc_Tls13_HKDF_Expand_Label_Alloc() into kdf.c. They keep the soft failure and the larger static buffer, and can still opt in with --enable-ticket-nonce-malloc. MAXQ10xx builds are left out too, since they reject the macro with an #error, and so are WOLFSSL_NO_MALLOC builds without WOLFSSL_STATIC_MEMORY, which keep a fixed footprint. WOLFSSL_NO_TICKET_NONCE_MALLOC opts out. configure maps --disable-ticket-nonce-malloc to it and leaves the default to settings.h. CMake does the same: WOLFSSL_TICKET_NONCE_MALLOC defaults to undefined, which emits nothing so the settings.h exclusions apply, and yes or no emit the macro or the opt-out. WOLFSSL_ALL no longer forces it on. CI: the Mbed TLS interop job builds with session tickets and checks TLS 1.3 resumption against ssl_server2, which sends 32 byte nonces. A new os-check entry builds with --disable-ticket-nonce-malloc and early data so test_ticket_nonce_too_long keeps running, including its checks on a shared session. --- .github/configs/os-check-linux.json | 4 ++ .github/workflows/mbedtls.sh | 15 ++++++++ .github/workflows/mbedtls.yml | 2 +- CMakeLists.txt | 15 ++++---- cmake/options.h.in | 2 + configure.ac | 10 +++-- src/tls13.c | 25 ++++++++----- tests/api/test_tls13.c | 57 +++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 + wolfssl/wolfcrypt/settings.h | 10 +++++ 10 files changed, 122 insertions(+), 20 deletions(-) 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/tls13.c b/src/tls13.c index e4590ca243f..008d26c8be7 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,22 @@ 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 + /* 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(); + return ret; + } +#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); -#endif /* !defined(HAVE_FIPS) || FIPS_VERSION_GE(5,3) */ return ret; } #endif /* HAVE_SESSION_TICKET */ 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/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 From 73a44ab033eab4695ff33b3ab86fbb098209fc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 3 Nov 2026 09:24:48 +0100 Subject: [PATCH 4/4] Address review comments DeriveResumptionPSK now has a single return. Drop the BUFFER_ERROR check in the non-malloc nonce store path of DoTls13NewSessionTicket. It cannot be reached: the nonce length check above sets storeTicket to 0 under the same condition. test_ticket_nonce_cache_restore expects the ticket to be dropped only when the nonce exceeds the static buffer. With TLS13_TICKET_NONCE_STATIC_SZ=255 the nonce stays static and survives the restore, which the old assertions failed on. The restore is now run with the small nonce as well, so default builds check both outcomes. --- src/tls13.c | 17 ++++++++--------- tests/api.c | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index 008d26c8be7..c5813af87c1 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -1672,13 +1672,16 @@ int DeriveResumptionPSK(WOLFSSL* ssl, byte* nonce, byte nonceLen, byte* secret) protocolLen, resumptionLabel, RESUMPTION_LABEL_SZ, nonce, nonceLen, digestAlg, ssl->heap); PRIVATE_KEY_LOCK(); - return ret; } + 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); + { + 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 */ @@ -14822,10 +14825,6 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input, if (ret != 0) return ret; #else - if (nonceLength > MAX_TICKET_NONCE_STATIC_SZ) { - ret = BUFFER_ERROR; - return ret; - } ssl->session->ticketNonce.len = nonceLength; if (nonceLength > 0) XMEMCPY(ssl->session->ticketNonce.data, nonce, nonceLength); diff --git a/tests/api.c b/tests/api.c index 067c7608649..04d3d4fc7d5 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39942,8 +39942,17 @@ static int test_ticket_nonce_cache_restore(WOLFSSL *ssl_s, WOLFSSL *ssl_c, ExpectNotNull(cached = wolfSSL_SESSION_new()); ExpectIntEQ(wolfSSL_GetSessionFromCache(ssl_c, cached), WOLFSSL_SUCCESS); - ExpectIntEQ(cached->ticketNonce.len, 0); - ExpectIntEQ(cached->ticketLen, 0); +#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); @@ -40009,6 +40018,8 @@ static int test_ticket_nonce_malloc(void) 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