From 11e553ae8010b61b1620f3d07993daa76e153d93 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 3 Jun 2026 17:22:39 -0700 Subject: [PATCH 1/5] Also send decode_error for BUFFER_E as this error code is used throughout tls.c/tls13.c. Thanks to Ben Smyth for the report. --- src/internal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/internal.c b/src/internal.c index a3be7ee448..ce927b8695 100644 --- a/src/internal.c +++ b/src/internal.c @@ -35735,6 +35735,7 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, { switch (err) { case WC_NO_ERR_TRACE(BUFFER_ERROR): + case WC_NO_ERR_TRACE(BUFFER_E): return decode_error; case WC_NO_ERR_TRACE(EXT_NOT_ALLOWED): case WC_NO_ERR_TRACE(PEER_KEY_ERROR): From fdcef8ba616f40f0678c6bc3fbfe6ce0894054f1 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 3 Jun 2026 17:27:54 -0700 Subject: [PATCH 2/5] Prevent exporting keying material until the handshake is complete. Thanks to Ben Smyth for the report. --- src/ssl.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ssl.c b/src/ssl.c index c215101175..9300eda845 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -6023,6 +6023,17 @@ int wolfSSL_export_keying_material(WOLFSSL *ssl, return WOLFSSL_FAILURE; } + /* RFC 8446 Section 7.5 / RFC 5705: keying-material exporters derive from + * exporter_master_secret, which exists only after the handshake is + * complete. Refuse the export until the handshake has completed so that + * a premature call cannot derive material from an uninitialised + * exporterSecret buffer. */ + if (ssl->options.handShakeDone == 0 || + ssl->options.handShakeState != HANDSHAKE_DONE) { + WOLFSSL_MSG("Handshake not complete; refusing keying-material export"); + return WOLFSSL_FAILURE; + } + /* Sanity check contextLen to prevent integer overflow when cast to word32 * and to ensure it fits in the 2-byte length encoding (max 65535). */ if (use_context && contextLen > WOLFSSL_MAX_16BIT) { From 1db5c1caf6ebada9eb0244a872fc8790a62d045e Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 3 Jun 2026 17:29:30 -0700 Subject: [PATCH 3/5] Check for ticket expiration before using a ticket for resumption. Thanks to Ben Smyth for the report. --- src/internal.c | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/internal.c b/src/internal.c index ce927b8695..a209694b49 100644 --- a/src/internal.c +++ b/src/internal.c @@ -31518,17 +31518,38 @@ static void MakePSKPreMasterSecret(Arrays* arrays, byte use_psk_key) if (ssl->options.resuming && ssl->session->ticketLen > 0) { SessionTicket* ticket; - ticket = TLSX_SessionTicket_Create(0, ssl->session->ticket, +#if !defined(WOLFSSL_NO_TICKET_EXPIRE) && !defined(NO_ASN_TIME) + /* RFC 5077 Section 3.3 / RFC 8446 Section 4.6.1: a client SHOULD + * NOT use a ticket whose lifetime has expired. If the stored + * session has aged past its timeout the server would just reject + * the resumption, so suppress the ticket here and fall back to a + * full handshake (avoids leaking a stale ticket and saves a + * round-trip). Expiry is measured against ssl->session->timeout + * (the session's own lifetime) so this stays consistent with + * wolfSSL_SetSession(), which gates resumption on the same field; + * keying off ssl->timeout instead could contradict a decision + * SetSession() already made when the two values differ. */ + if (LowResTimer() >= + (ssl->session->bornOn + ssl->session->timeout)) { + WOLFSSL_MSG("Stored session ticket expired; full handshake"); + ssl->options.resuming = 0; + } + else +#endif + { + ticket = TLSX_SessionTicket_Create(0, ssl->session->ticket, ssl->session->ticketLen, ssl->heap); - if (ticket == NULL) return MEMORY_E; + if (ticket == NULL) return MEMORY_E; - ret = TLSX_UseSessionTicket(&ssl->extensions, ticket, ssl->heap); - if (ret != WOLFSSL_SUCCESS) { - TLSX_SessionTicket_Free(ticket, ssl->heap); - return ret; - } + ret = TLSX_UseSessionTicket(&ssl->extensions, ticket, + ssl->heap); + if (ret != WOLFSSL_SUCCESS) { + TLSX_SessionTicket_Free(ticket, ssl->heap); + return ret; + } - idSz = 0; + idSz = 0; + } } #endif /* HAVE_SESSION_TICKET */ length = VERSION_SZ + RAN_LEN From c275b97477237fde5d7b9a687dcf420a408d3434 Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 4 Jun 2026 15:58:58 -0700 Subject: [PATCH 4/5] Code review feedback: set idSz = 0 for both cases. --- src/internal.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index a209694b49..12a9a02052 100644 --- a/src/internal.c +++ b/src/internal.c @@ -31547,9 +31547,8 @@ static void MakePSKPreMasterSecret(Arrays* arrays, byte use_psk_key) TLSX_SessionTicket_Free(ticket, ssl->heap); return ret; } - - idSz = 0; } + idSz = 0; } #endif /* HAVE_SESSION_TICKET */ length = VERSION_SZ + RAN_LEN From 133b75455bf11928b12e6c3b82774ec0e84341e1 Mon Sep 17 00:00:00 2001 From: Kareem Date: Mon, 8 Jun 2026 10:38:20 -0700 Subject: [PATCH 5/5] Skip session timeout check if bornOn is 0 or if a secret callback is set. This should fix hostap EAP-FAST failures. --- src/internal.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 12a9a02052..4302afbba8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -31528,8 +31528,15 @@ static void MakePSKPreMasterSecret(Arrays* arrays, byte use_psk_key) * (the session's own lifetime) so this stays consistent with * wolfSSL_SetSession(), which gates resumption on the same field; * keying off ssl->timeout instead could contradict a decision - * SetSession() already made when the two values differ. */ - if (LowResTimer() >= + * SetSession() already made when the two values differ. + * If bornOn is 0 or the secret callback is set, it is assumed that + * the session is being externally managed and this check is + * skipped. This is needed for hostap. */ + if (ssl->session->bornOn != 0 && + #ifdef HAVE_SECRET_CALLBACK + ssl->sessionSecretCb == NULL && + #endif + LowResTimer() >= (ssl->session->bornOn + ssl->session->timeout)) { WOLFSSL_MSG("Stored session ticket expired; full handshake"); ssl->options.resuming = 0;