From f7cf4217f6e9df96053b303e9bc0e27da9b096cf Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Fri, 24 Apr 2026 19:11:14 -0700 Subject: [PATCH 1/6] docs: clarify session cache size comment in ssl_sess.c Replace ambiguous XXX placeholder in comment with descriptive text. The XXX was not a TODO marker but looked like one, causing confusion in code reviews and static analysis. --- src/ssl_sess.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ssl_sess.c b/src/ssl_sess.c index ec79057505..d02c97357a 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -56,7 +56,7 @@ or systems where memory is at a premium. SessionCache takes about 400 bytes, ClientCache takes 576 bytes - default SESSION_CACHE stores 33 sessions (no XXX_SESSION_CACHE defined) + default SESSION_CACHE stores 33 sessions (no specific SESSION_CACHE size macro defined) SessionCache takes about 13K bytes, ClientCache takes 17K bytes */ #if defined(TITAN_SESSION_CACHE) From 71e85fe5a25706f711d17a3f909ffb24d81a2580 Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Fri, 24 Apr 2026 20:23:34 -0700 Subject: [PATCH 2/6] fix: memory leak in hybrid key share generation Add explicit XFREE(ecc_kse) cleanup when the second XMALLOC for pqc_kse fails in the hybrid key share generation path. Previously, if the pqc_kse allocation failed after ecc_kse was successfully allocated, ecc_kse would be leaked since it was only tracked as a local variable. --- src/tls.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tls.c b/src/tls.c index c608261217..be73e2e07b 100644 --- a/src/tls.c +++ b/src/tls.c @@ -8983,6 +8983,8 @@ static int TLSX_KeyShare_GenPqcHybridKeyClient(WOLFSSL *ssl, KeyShareEntry* kse) DYNAMIC_TYPE_TLSX); if (pqc_kse == NULL) { WOLFSSL_MSG("kse memory allocation failure"); + XFREE(ecc_kse, ssl->heap, DYNAMIC_TYPE_TLSX); + ecc_kse = NULL; ret = MEMORY_ERROR; } else { From 1c7a51c763808e3496a19027d45da72bc2d4df8d Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Fri, 24 Apr 2026 22:23:46 -0700 Subject: [PATCH 3/6] fix: always clean up temp ECC key regardless of error status ecc_key_tmp_final was guarded by if (err == MP_OKAY), skipping cleanup on failure and leaking the temporary key. The sister function wc_ecc_mulmod_ex2 correctly calls cleanup unconditionally. Removed the conditional to match the correct pattern. Signed-off-by: Srikanth Patchava Signed-off-by: Srikanth Patchava --- wolfcrypt/src/ecc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 684a7d4c0d..6e32a4d48f 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -3815,8 +3815,7 @@ int wc_ecc_mulmod_ex(const mp_int* k, ecc_point *G, ecc_point *R, mp_int* a, if (key) { if (R) R->key = NULL; - if (err == MP_OKAY) - ecc_key_tmp_final(key, heap); + ecc_key_tmp_final(key, heap); XFREE(key, heap, DYNAMIC_TYPE_ECC); } #endif /* WOLFSSL_SMALL_STACK_CACHE */ From 92a48c3b76b1cd740fe9dadc0d84db319d60a7d3 Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Sat, 25 Apr 2026 01:25:58 -0700 Subject: [PATCH 4/6] feat: add TLS session ticket key rotation Signed-off-by: Srikanth Patchava --- src/session_ticket_rotation.c | 495 ++++++++++++++++++++++++++++++ wolfssl/session_ticket_rotation.h | 122 ++++++++ 2 files changed, 617 insertions(+) create mode 100644 src/session_ticket_rotation.c create mode 100644 wolfssl/session_ticket_rotation.h diff --git a/src/session_ticket_rotation.c b/src/session_ticket_rotation.c new file mode 100644 index 0000000000..cd77b04af0 --- /dev/null +++ b/src/session_ticket_rotation.c @@ -0,0 +1,495 @@ +/* session_ticket_rotation.c + * + * Copyright (C) 2006-2024 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef HAVE_SESSION_TICKET + +#include +#include +#include +#include +#include +#include + +/* Get current time as unsigned long */ +static unsigned long ticketRotation_GetTime(void) +{ + /* Use wolfSSL time abstraction */ + return (unsigned long)XTIME(NULL); +} + +/* Derive a key from master secret using HKDF-like construction */ +static int ticketRotation_DeriveKey(TicketKeyRotationCtx* ctx, + unsigned long counter, + TicketKeyEntry* entry) +{ + int ret; + Hmac hmac; + unsigned char prk[WC_SHA256_DIGEST_SIZE]; + unsigned char info[WOLFSSL_TICKET_HKDF_INFO_SIZE + sizeof(unsigned long)]; + unsigned char okm[WOLFSSL_TICKET_ENC_KEY_SIZE + WOLFSSL_TICKET_HMAC_KEY_SIZE + + WOLFSSL_TICKET_KEY_NAME_SIZE]; + int okmLen = (int)sizeof(okm); + int infoLen; + + if (ctx == NULL || entry == NULL) + return BAD_FUNC_ARG; + + if (ctx->masterSecretLen == 0) + return BAD_FUNC_ARG; + + /* Step 1: Extract - HMAC-SHA256(salt=zeros, IKM=masterSecret) */ + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + if (ret != 0) + return ret; + + ret = wc_HmacSetKey(&hmac, WC_SHA256, (const byte*)"", 0); + if (ret != 0) { + wc_HmacFree(&hmac); + return ret; + } + + ret = wc_HmacUpdate(&hmac, ctx->masterSecret, ctx->masterSecretLen); + if (ret != 0) { + wc_HmacFree(&hmac); + return ret; + } + + ret = wc_HmacFinal(&hmac, prk); + wc_HmacFree(&hmac); + if (ret != 0) + return ret; + + /* Step 2: Expand - Build info = HKDF_INFO || counter */ + XMEMCPY(info, WOLFSSL_TICKET_HKDF_INFO, WOLFSSL_TICKET_HKDF_INFO_SIZE); + XMEMCPY(info + WOLFSSL_TICKET_HKDF_INFO_SIZE, &counter, sizeof(counter)); + infoLen = WOLFSSL_TICKET_HKDF_INFO_SIZE + (int)sizeof(counter); + + /* Simple HKDF-Expand: T(1) || T(2) || ... */ + { + int done = 0; + int idx = 0; + unsigned char T[WC_SHA256_DIGEST_SIZE]; + unsigned char prev[WC_SHA256_DIGEST_SIZE]; + int prevLen = 0; + unsigned char ctr = 1; + + while (done < okmLen) { + int copyLen; + + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + if (ret != 0) return ret; + + ret = wc_HmacSetKey(&hmac, WC_SHA256, prk, WC_SHA256_DIGEST_SIZE); + if (ret != 0) { wc_HmacFree(&hmac); return ret; } + + if (prevLen > 0) { + ret = wc_HmacUpdate(&hmac, prev, prevLen); + if (ret != 0) { wc_HmacFree(&hmac); return ret; } + } + + ret = wc_HmacUpdate(&hmac, (const byte*)info, infoLen); + if (ret != 0) { wc_HmacFree(&hmac); return ret; } + + ret = wc_HmacUpdate(&hmac, &ctr, 1); + if (ret != 0) { wc_HmacFree(&hmac); return ret; } + + ret = wc_HmacFinal(&hmac, T); + wc_HmacFree(&hmac); + if (ret != 0) return ret; + + copyLen = okmLen - done; + if (copyLen > WC_SHA256_DIGEST_SIZE) + copyLen = WC_SHA256_DIGEST_SIZE; + + XMEMCPY(okm + done, T, copyLen); + done += copyLen; + XMEMCPY(prev, T, WC_SHA256_DIGEST_SIZE); + prevLen = WC_SHA256_DIGEST_SIZE; + ctr++; + idx++; + + if (idx > 10) { + ret = BAD_STATE_E; + break; + } + } + if (ret != 0) + return ret; + } + + /* Split OKM into encKey, hmacKey, keyName */ + XMEMCPY(entry->encKey, okm, WOLFSSL_TICKET_ENC_KEY_SIZE); + XMEMCPY(entry->hmacKey, okm + WOLFSSL_TICKET_ENC_KEY_SIZE, + WOLFSSL_TICKET_HMAC_KEY_SIZE); + XMEMCPY(entry->keyName, + okm + WOLFSSL_TICKET_ENC_KEY_SIZE + WOLFSSL_TICKET_HMAC_KEY_SIZE, + WOLFSSL_TICKET_KEY_NAME_SIZE); + + /* Clear sensitive temp data */ + ForceZero(prk, sizeof(prk)); + ForceZero(okm, sizeof(okm)); + + return 0; +} + +/* Internal rotate: must be called with lock held */ +static int ticketRotation_RotateInternal(TicketKeyRotationCtx* ctx) +{ + int ret; + int newIdx; + unsigned long now; + int i; + + now = ticketRotation_GetTime(); + + /* Mark current active key as inactive */ + if (ctx->currentKeyIndex >= 0 && + ctx->currentKeyIndex < WOLFSSL_TICKET_KEY_TABLE_SIZE) { + ctx->keys[ctx->currentKeyIndex].active = 0; + } + + /* Find a free slot or the oldest slot */ + newIdx = -1; + for (i = 0; i < WOLFSSL_TICKET_KEY_TABLE_SIZE; i++) { + if (ctx->keys[i].createdAt == 0) { + newIdx = i; + break; + } + } + + /* If no free slot, evict the oldest expired key */ + if (newIdx < 0) { + unsigned long oldest = now; + int oldestIdx = 0; + for (i = 0; i < WOLFSSL_TICKET_KEY_TABLE_SIZE; i++) { + if (ctx->keys[i].createdAt < oldest) { + oldest = ctx->keys[i].createdAt; + oldestIdx = i; + } + } + newIdx = oldestIdx; + } + + /* Derive new key material */ + ctx->rotationCounter++; + ret = ticketRotation_DeriveKey(ctx, ctx->rotationCounter, + &ctx->keys[newIdx]); + if (ret != 0) + return ret; + + /* Set timestamps */ + ctx->keys[newIdx].createdAt = now; + ctx->keys[newIdx].expiresAt = now + ctx->rotationInterval + + ctx->gracePeriod; + ctx->keys[newIdx].active = 1; + + ctx->currentKeyIndex = newIdx; + if (ctx->keyCount < WOLFSSL_TICKET_KEY_TABLE_SIZE) + ctx->keyCount++; + + /* Fire rotation callback if set */ + if (ctx->onRotation != NULL) { + ctx->onRotation(ctx->rotationCbCtx, + ctx->keys[newIdx].keyName, newIdx); + } + + return 0; +} + +/* Initialize the ticket key rotation context */ +int wolfSSL_TicketKeyRotation_Init(TicketKeyRotationCtx* ctx) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + XMEMSET(ctx, 0, sizeof(TicketKeyRotationCtx)); + ctx->currentKeyIndex = -1; + ctx->rotationInterval = WOLFSSL_TICKET_KEY_ROTATION_DEFAULT_INTERVAL; + ctx->gracePeriod = WOLFSSL_TICKET_KEY_ROTATION_DEFAULT_INTERVAL / 2; + ctx->rotationCounter = 0; + ctx->onRotation = NULL; + ctx->rotationCbCtx = NULL; + ctx->initialized = 0; + +#ifdef WOLFSSL_MUTEX + if (wc_InitMutex(&ctx->lock) != 0) + return BAD_MUTEX_E; +#endif + + ctx->initialized = 1; + return 0; +} + +/* Free resources */ +void wolfSSL_TicketKeyRotation_Free(TicketKeyRotationCtx* ctx) +{ + if (ctx == NULL) + return; + + /* Zeroize all key material */ + ForceZero(ctx->keys, sizeof(ctx->keys)); + ForceZero(ctx->masterSecret, sizeof(ctx->masterSecret)); + +#ifdef WOLFSSL_MUTEX + if (ctx->initialized) + wc_FreeMutex(&ctx->lock); +#endif + + ctx->initialized = 0; +} + +/* Set master secret for HKDF derivation */ +int wolfSSL_TicketKeyRotation_SetMasterSecret(TicketKeyRotationCtx* ctx, + const unsigned char* secret, + int secretLen) +{ + if (ctx == NULL || secret == NULL || secretLen <= 0) + return BAD_FUNC_ARG; + + if (secretLen > (int)sizeof(ctx->masterSecret)) + secretLen = (int)sizeof(ctx->masterSecret); + + XMEMCPY(ctx->masterSecret, secret, secretLen); + ctx->masterSecretLen = secretLen; + return 0; +} + +/* Set rotation interval */ +int wolfSSL_TicketKeyRotation_SetInterval(TicketKeyRotationCtx* ctx, + unsigned long intervalSec) +{ + if (ctx == NULL || intervalSec == 0) + return BAD_FUNC_ARG; + + ctx->rotationInterval = intervalSec; + return 0; +} + +/* Set grace period for old key retention */ +int wolfSSL_TicketKeyRotation_SetGracePeriod(TicketKeyRotationCtx* ctx, + unsigned long graceSec) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + ctx->gracePeriod = graceSec; + return 0; +} + +/* Set rotation callback */ +int wolfSSL_TicketKeyRotation_SetCallback(TicketKeyRotationCtx* ctx, + TicketKeyRotationCb cb, + void* userCtx) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + ctx->onRotation = cb; + ctx->rotationCbCtx = userCtx; + return 0; +} + +/* Check if rotation is needed and rotate */ +int wolfSSL_TicketKeyRotation_CheckAndRotate(TicketKeyRotationCtx* ctx) +{ + int ret = 0; + unsigned long now; + + if (ctx == NULL || !ctx->initialized) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_MUTEX + if (wc_LockMutex(&ctx->lock) != 0) + return BAD_MUTEX_E; +#endif + + now = ticketRotation_GetTime(); + + /* Rotate if no active key or current key has expired */ + if (ctx->currentKeyIndex < 0 || + now >= ctx->keys[ctx->currentKeyIndex].createdAt + + ctx->rotationInterval) { + ret = ticketRotation_RotateInternal(ctx); + } + +#ifdef WOLFSSL_MUTEX + wc_UnLockMutex(&ctx->lock); +#endif + + return ret; +} + +/* Force immediate rotation */ +int wolfSSL_TicketKeyRotation_ForceRotate(TicketKeyRotationCtx* ctx) +{ + int ret; + + if (ctx == NULL || !ctx->initialized) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_MUTEX + if (wc_LockMutex(&ctx->lock) != 0) + return BAD_MUTEX_E; +#endif + + ret = ticketRotation_RotateInternal(ctx); + +#ifdef WOLFSSL_MUTEX + wc_UnLockMutex(&ctx->lock); +#endif + + return ret; +} + +/* Get the current active key */ +int wolfSSL_TicketKeyRotation_GetActiveKey(TicketKeyRotationCtx* ctx, + TicketKeyEntry** key) +{ + int ret = 0; + + if (ctx == NULL || key == NULL || !ctx->initialized) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_MUTEX + if (wc_LockMutex(&ctx->lock) != 0) + return BAD_MUTEX_E; +#endif + + if (ctx->currentKeyIndex >= 0 && + ctx->currentKeyIndex < WOLFSSL_TICKET_KEY_TABLE_SIZE && + ctx->keys[ctx->currentKeyIndex].active) { + *key = &ctx->keys[ctx->currentKeyIndex]; + } else { + *key = NULL; + ret = BAD_STATE_E; + } + +#ifdef WOLFSSL_MUTEX + wc_UnLockMutex(&ctx->lock); +#endif + + return ret; +} + +/* Find key by name for decrypting incoming tickets */ +int wolfSSL_TicketKeyRotation_FindKeyByName(TicketKeyRotationCtx* ctx, + const unsigned char* name, + TicketKeyEntry** key) +{ + int i; + unsigned long now; + + if (ctx == NULL || name == NULL || key == NULL || !ctx->initialized) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_MUTEX + if (wc_LockMutex(&ctx->lock) != 0) + return BAD_MUTEX_E; +#endif + + *key = NULL; + now = ticketRotation_GetTime(); + + for (i = 0; i < WOLFSSL_TICKET_KEY_TABLE_SIZE; i++) { + if (ctx->keys[i].createdAt == 0) + continue; + + /* Check if key has fully expired (past grace period) */ + if (now > ctx->keys[i].expiresAt) + continue; + + if (XMEMCMP(ctx->keys[i].keyName, name, + WOLFSSL_TICKET_KEY_NAME_SIZE) == 0) { + *key = &ctx->keys[i]; + break; + } + } + +#ifdef WOLFSSL_MUTEX + wc_UnLockMutex(&ctx->lock); +#endif + + return (*key != NULL) ? 0 : WC_NO_ERR_TRACE(MATCH_SUITE_ERROR); +} + +/* Get number of active keys in the table */ +int wolfSSL_TicketKeyRotation_GetKeyCount(TicketKeyRotationCtx* ctx) +{ + int count = 0; + int i; + + if (ctx == NULL || !ctx->initialized) + return 0; + +#ifdef WOLFSSL_MUTEX + if (wc_LockMutex(&ctx->lock) != 0) + return 0; +#endif + + for (i = 0; i < WOLFSSL_TICKET_KEY_TABLE_SIZE; i++) { + if (ctx->keys[i].createdAt != 0) + count++; + } + +#ifdef WOLFSSL_MUTEX + wc_UnLockMutex(&ctx->lock); +#endif + + return count; +} + +/* Purge expired keys from the table */ +int wolfSSL_TicketKeyRotation_PurgeExpired(TicketKeyRotationCtx* ctx) +{ + int i; + int purged = 0; + unsigned long now; + + if (ctx == NULL || !ctx->initialized) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_MUTEX + if (wc_LockMutex(&ctx->lock) != 0) + return BAD_MUTEX_E; +#endif + + now = ticketRotation_GetTime(); + + for (i = 0; i < WOLFSSL_TICKET_KEY_TABLE_SIZE; i++) { + if (ctx->keys[i].createdAt == 0) + continue; + + /* Don't purge the active key */ + if (i == ctx->currentKeyIndex && ctx->keys[i].active) + continue; + + if (now > ctx->keys[i].expiresAt) { + ForceZero(&ctx->keys[i], sizeof(TicketKeyEntry)); + ctx->keyCount--; + purged++; + } + } + +#ifdef WOLFSSL_MUTEX + wc_UnLockMutex(&ctx->lock); +#endif + + return purged; +} + +#endif /* HAVE_SESSION_TICKET */ diff --git a/wolfssl/session_ticket_rotation.h b/wolfssl/session_ticket_rotation.h new file mode 100644 index 0000000000..cf1754cd6d --- /dev/null +++ b/wolfssl/session_ticket_rotation.h @@ -0,0 +1,122 @@ +/* session_ticket_rotation.h + * + * Copyright (C) 2006-2024 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef WOLFSSL_SESSION_TICKET_ROTATION_H +#define WOLFSSL_SESSION_TICKET_ROTATION_H + +#include +#include + +#ifdef HAVE_SESSION_TICKET + +#ifdef __cplusplus +extern "C" { +#endif + +/* Maximum number of retained previous keys for in-flight connections */ +#define WOLFSSL_TICKET_KEY_TABLE_SIZE 4 +/* Default rotation interval in seconds (1 hour) */ +#define WOLFSSL_TICKET_KEY_ROTATION_DEFAULT_INTERVAL 3600 +/* Size of ticket encryption key in bytes */ +#define WOLFSSL_TICKET_ENC_KEY_SIZE 32 +/* Size of ticket HMAC key in bytes */ +#define WOLFSSL_TICKET_HMAC_KEY_SIZE 32 +/* Size of key name identifier */ +#define WOLFSSL_TICKET_KEY_NAME_SIZE 16 +/* HKDF info string for key derivation */ +#define WOLFSSL_TICKET_HKDF_INFO "wolfSSL ticket key" +#define WOLFSSL_TICKET_HKDF_INFO_SIZE 18 + +/* Callback type for rotation events */ +typedef void (*TicketKeyRotationCb)(void* ctx, const unsigned char* keyName, + int keyIndex); + +/* Single ticket key entry */ +typedef struct TicketKeyEntry { + unsigned char encKey[WOLFSSL_TICKET_ENC_KEY_SIZE]; + unsigned char hmacKey[WOLFSSL_TICKET_HMAC_KEY_SIZE]; + unsigned char keyName[WOLFSSL_TICKET_KEY_NAME_SIZE]; + unsigned long createdAt; /* Unix timestamp when key was created */ + unsigned long expiresAt; /* Unix timestamp when key expires */ + int active; /* 1 if this key is currently active */ +} TicketKeyEntry; + +/* Ticket key rotation context */ +typedef struct TicketKeyRotationCtx { + TicketKeyEntry keys[WOLFSSL_TICKET_KEY_TABLE_SIZE]; + int currentKeyIndex; + int keyCount; + unsigned long rotationInterval; /* seconds between rotations */ + unsigned long gracePeriod; /* seconds to retain old keys */ + unsigned char masterSecret[64]; /* master secret for HKDF */ + int masterSecretLen; + unsigned long rotationCounter; /* monotonic counter for derivation */ + TicketKeyRotationCb onRotation; + void* rotationCbCtx; +#ifdef WOLFSSL_MUTEX + wolfSSL_Mutex lock; +#endif + int initialized; +} TicketKeyRotationCtx; + +/* Initialize the ticket key rotation context */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_Init(TicketKeyRotationCtx* ctx); + +/* Free resources for the ticket key rotation context */ +WOLFSSL_API void wolfSSL_TicketKeyRotation_Free(TicketKeyRotationCtx* ctx); + +/* Set the master secret used for HKDF key derivation */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_SetMasterSecret( + TicketKeyRotationCtx* ctx, const unsigned char* secret, int secretLen); + +/* Set the rotation interval in seconds */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_SetInterval( + TicketKeyRotationCtx* ctx, unsigned long intervalSec); + +/* Set the grace period for retaining old keys */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_SetGracePeriod( + TicketKeyRotationCtx* ctx, unsigned long graceSec); + +/* Set the callback invoked on each key rotation */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_SetCallback( + TicketKeyRotationCtx* ctx, TicketKeyRotationCb cb, void* userCtx); + +/* Perform a key rotation if the current key has expired */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_CheckAndRotate( + TicketKeyRotationCtx* ctx); + +/* Force an immediate key rotation */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_ForceRotate( + TicketKeyRotationCtx* ctx); + +/* Retrieve the current active key for encrypting new tickets */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_GetActiveKey( + TicketKeyRotationCtx* ctx, TicketKeyEntry** key); + +/* Look up a key by its key name (for decrypting received tickets) */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_FindKeyByName( + TicketKeyRotationCtx* ctx, const unsigned char* name, TicketKeyEntry** key); + +/* Get the number of keys currently in the table */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_GetKeyCount( + TicketKeyRotationCtx* ctx); + +/* Purge all expired keys from the table */ +WOLFSSL_API int wolfSSL_TicketKeyRotation_PurgeExpired( + TicketKeyRotationCtx* ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_SESSION_TICKET */ +#endif /* WOLFSSL_SESSION_TICKET_ROTATION_H */ From 8663248b65dd82d7c7b4b354480cc2680a4adf3e Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Sat, 25 Apr 2026 01:26:10 -0700 Subject: [PATCH 5/6] fix: prevent session cache leak on ticket decrypt failure Signed-off-by: Srikanth Patchava --- src/tls.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tls.c b/src/tls.c index be73e2e07b..a838e02ad4 100644 --- a/src/tls.c +++ b/src/tls.c @@ -6492,6 +6492,9 @@ static int TLSX_SessionTicket_Parse(WOLFSSL* ssl, const byte* input, WOLFSSL_MSG("Process client ticket fatal error, not using"); } else if (ret < 0) { WOLFSSL_MSG("Process client ticket unknown error, not using"); + ssl->options.rejectTicket = 1; + ret = 0; /* not fatal, but ensure session cache is not + * populated with a partially-decrypted entry */ } } } From 1657fc06d6086387664c4b1eb686780646c363bd Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Sat, 25 Apr 2026 01:26:16 -0700 Subject: [PATCH 6/6] test: add session ticket rotation tests Signed-off-by: Srikanth Patchava --- tests/api_test_session_ticket.c | 460 ++++++++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 tests/api_test_session_ticket.c diff --git a/tests/api_test_session_ticket.c b/tests/api_test_session_ticket.c new file mode 100644 index 0000000000..6cdafc26fc --- /dev/null +++ b/tests/api_test_session_ticket.c @@ -0,0 +1,460 @@ +/* api_test_session_ticket.c + * + * Copyright (C) 2006-2024 wolfSSL Inc. + * + * Tests for TLS session ticket key rotation functionality. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef HAVE_SESSION_TICKET + +#include +#include +#include +#include + +/* Test master secret for key derivation */ +static const unsigned char testMasterSecret[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20 +}; + +/* Rotation callback tracking */ +static int rotationCallbackCount = 0; +static int lastRotatedIndex = -1; + +static void testRotationCallback(void* ctx, const unsigned char* keyName, + int keyIndex) +{ + (void)ctx; + (void)keyName; + rotationCallbackCount++; + lastRotatedIndex = keyIndex; +} + +/* Test: Init and Free */ +static int test_TicketKeyRotation_InitFree(void) +{ + TicketKeyRotationCtx ctx; + int ret; + + printf(" Testing Init/Free...\n"); + + ret = wolfSSL_TicketKeyRotation_Init(&ctx); + if (ret != 0) { + printf(" FAIL: Init returned %d\n", ret); + return -1; + } + + /* Verify default values */ + if (ctx.rotationInterval != WOLFSSL_TICKET_KEY_ROTATION_DEFAULT_INTERVAL) { + printf(" FAIL: Default interval not set\n"); + return -2; + } + + if (ctx.currentKeyIndex != -1) { + printf(" FAIL: Initial key index should be -1\n"); + return -3; + } + + if (!ctx.initialized) { + printf(" FAIL: Should be initialized\n"); + return -4; + } + + wolfSSL_TicketKeyRotation_Free(&ctx); + + if (ctx.initialized) { + printf(" FAIL: Should not be initialized after free\n"); + return -5; + } + + /* Test NULL argument */ + ret = wolfSSL_TicketKeyRotation_Init(NULL); + if (ret != BAD_FUNC_ARG) { + printf(" FAIL: Init(NULL) should return BAD_FUNC_ARG\n"); + return -6; + } + + printf(" PASS\n"); + return 0; +} + +/* Test: Set Master Secret */ +static int test_TicketKeyRotation_SetMasterSecret(void) +{ + TicketKeyRotationCtx ctx; + int ret; + + printf(" Testing SetMasterSecret...\n"); + + ret = wolfSSL_TicketKeyRotation_Init(&ctx); + if (ret != 0) return -1; + + ret = wolfSSL_TicketKeyRotation_SetMasterSecret(&ctx, testMasterSecret, + sizeof(testMasterSecret)); + if (ret != 0) { + printf(" FAIL: SetMasterSecret returned %d\n", ret); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -2; + } + + if (ctx.masterSecretLen != (int)sizeof(testMasterSecret)) { + printf(" FAIL: Master secret length mismatch\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -3; + } + + /* Test NULL arguments */ + ret = wolfSSL_TicketKeyRotation_SetMasterSecret(NULL, testMasterSecret, + sizeof(testMasterSecret)); + if (ret != BAD_FUNC_ARG) { + printf(" FAIL: NULL ctx should return BAD_FUNC_ARG\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -4; + } + + ret = wolfSSL_TicketKeyRotation_SetMasterSecret(&ctx, NULL, 32); + if (ret != BAD_FUNC_ARG) { + printf(" FAIL: NULL secret should return BAD_FUNC_ARG\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -5; + } + + wolfSSL_TicketKeyRotation_Free(&ctx); + printf(" PASS\n"); + return 0; +} + +/* Test: Force Rotate and Get Active Key */ +static int test_TicketKeyRotation_ForceRotate(void) +{ + TicketKeyRotationCtx ctx; + TicketKeyEntry* key = NULL; + int ret; + + printf(" Testing ForceRotate...\n"); + + ret = wolfSSL_TicketKeyRotation_Init(&ctx); + if (ret != 0) return -1; + + ret = wolfSSL_TicketKeyRotation_SetMasterSecret(&ctx, testMasterSecret, + sizeof(testMasterSecret)); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -2; + } + + /* Force first rotation */ + ret = wolfSSL_TicketKeyRotation_ForceRotate(&ctx); + if (ret != 0) { + printf(" FAIL: ForceRotate returned %d\n", ret); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -3; + } + + /* Verify we can get the active key */ + ret = wolfSSL_TicketKeyRotation_GetActiveKey(&ctx, &key); + if (ret != 0 || key == NULL) { + printf(" FAIL: GetActiveKey failed after rotation\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -4; + } + + if (!key->active) { + printf(" FAIL: Key should be active\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -5; + } + + /* Verify key count */ + if (wolfSSL_TicketKeyRotation_GetKeyCount(&ctx) != 1) { + printf(" FAIL: Key count should be 1\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -6; + } + + wolfSSL_TicketKeyRotation_Free(&ctx); + printf(" PASS\n"); + return 0; +} + +/* Test: Multiple rotations and key table management */ +static int test_TicketKeyRotation_MultipleRotations(void) +{ + TicketKeyRotationCtx ctx; + unsigned char prevKeyName[WOLFSSL_TICKET_KEY_NAME_SIZE]; + TicketKeyEntry* key = NULL; + int ret, i; + + printf(" Testing multiple rotations...\n"); + + ret = wolfSSL_TicketKeyRotation_Init(&ctx); + if (ret != 0) return -1; + + ret = wolfSSL_TicketKeyRotation_SetMasterSecret(&ctx, testMasterSecret, + sizeof(testMasterSecret)); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -2; + } + + /* Perform multiple rotations */ + for (i = 0; i < WOLFSSL_TICKET_KEY_TABLE_SIZE + 2; i++) { + ret = wolfSSL_TicketKeyRotation_ForceRotate(&ctx); + if (ret != 0) { + printf(" FAIL: Rotation %d failed with %d\n", i, ret); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -3; + } + } + + /* Verify key count doesn't exceed table size */ + if (wolfSSL_TicketKeyRotation_GetKeyCount(&ctx) > + WOLFSSL_TICKET_KEY_TABLE_SIZE) { + printf(" FAIL: Key count exceeds table size\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -4; + } + + /* Verify each rotation produces a different key */ + ret = wolfSSL_TicketKeyRotation_GetActiveKey(&ctx, &key); + if (ret != 0 || key == NULL) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -5; + } + XMEMCPY(prevKeyName, key->keyName, WOLFSSL_TICKET_KEY_NAME_SIZE); + + ret = wolfSSL_TicketKeyRotation_ForceRotate(&ctx); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -6; + } + + ret = wolfSSL_TicketKeyRotation_GetActiveKey(&ctx, &key); + if (ret != 0 || key == NULL) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -7; + } + + if (XMEMCMP(prevKeyName, key->keyName, WOLFSSL_TICKET_KEY_NAME_SIZE) == 0) { + printf(" FAIL: Rotation should produce different key names\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -8; + } + + wolfSSL_TicketKeyRotation_Free(&ctx); + printf(" PASS\n"); + return 0; +} + +/* Test: FindKeyByName */ +static int test_TicketKeyRotation_FindKeyByName(void) +{ + TicketKeyRotationCtx ctx; + TicketKeyEntry* key = NULL; + TicketKeyEntry* foundKey = NULL; + unsigned char keyName[WOLFSSL_TICKET_KEY_NAME_SIZE]; + int ret; + + printf(" Testing FindKeyByName...\n"); + + ret = wolfSSL_TicketKeyRotation_Init(&ctx); + if (ret != 0) return -1; + + ret = wolfSSL_TicketKeyRotation_SetMasterSecret(&ctx, testMasterSecret, + sizeof(testMasterSecret)); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -2; + } + + /* Create a key */ + ret = wolfSSL_TicketKeyRotation_ForceRotate(&ctx); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -3; + } + + /* Get the active key name */ + ret = wolfSSL_TicketKeyRotation_GetActiveKey(&ctx, &key); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -4; + } + XMEMCPY(keyName, key->keyName, WOLFSSL_TICKET_KEY_NAME_SIZE); + + /* Find it by name */ + ret = wolfSSL_TicketKeyRotation_FindKeyByName(&ctx, keyName, &foundKey); + if (ret != 0 || foundKey == NULL) { + printf(" FAIL: FindKeyByName failed\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -5; + } + + if (XMEMCMP(foundKey->keyName, keyName, WOLFSSL_TICKET_KEY_NAME_SIZE) != 0) { + printf(" FAIL: Found key name doesn't match\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -6; + } + + /* Try finding a non-existent key */ + XMEMSET(keyName, 0xFF, WOLFSSL_TICKET_KEY_NAME_SIZE); + ret = wolfSSL_TicketKeyRotation_FindKeyByName(&ctx, keyName, &foundKey); + if (ret == 0) { + printf(" FAIL: Should not find non-existent key\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -7; + } + + wolfSSL_TicketKeyRotation_Free(&ctx); + printf(" PASS\n"); + return 0; +} + +/* Test: Rotation callback */ +static int test_TicketKeyRotation_Callback(void) +{ + TicketKeyRotationCtx ctx; + int ret; + + printf(" Testing rotation callback...\n"); + + rotationCallbackCount = 0; + lastRotatedIndex = -1; + + ret = wolfSSL_TicketKeyRotation_Init(&ctx); + if (ret != 0) return -1; + + ret = wolfSSL_TicketKeyRotation_SetMasterSecret(&ctx, testMasterSecret, + sizeof(testMasterSecret)); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -2; + } + + ret = wolfSSL_TicketKeyRotation_SetCallback(&ctx, testRotationCallback, + NULL); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -3; + } + + ret = wolfSSL_TicketKeyRotation_ForceRotate(&ctx); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -4; + } + + if (rotationCallbackCount != 1) { + printf(" FAIL: Callback should have been called once, got %d\n", + rotationCallbackCount); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -5; + } + + if (lastRotatedIndex < 0) { + printf(" FAIL: Callback should set valid index\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -6; + } + + /* Second rotation */ + ret = wolfSSL_TicketKeyRotation_ForceRotate(&ctx); + if (ret != 0) { + wolfSSL_TicketKeyRotation_Free(&ctx); + return -7; + } + + if (rotationCallbackCount != 2) { + printf(" FAIL: Callback count should be 2, got %d\n", + rotationCallbackCount); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -8; + } + + wolfSSL_TicketKeyRotation_Free(&ctx); + printf(" PASS\n"); + return 0; +} + +/* Test: Configuration setters */ +static int test_TicketKeyRotation_Configuration(void) +{ + TicketKeyRotationCtx ctx; + int ret; + + printf(" Testing configuration...\n"); + + ret = wolfSSL_TicketKeyRotation_Init(&ctx); + if (ret != 0) return -1; + + /* Set interval */ + ret = wolfSSL_TicketKeyRotation_SetInterval(&ctx, 7200); + if (ret != 0 || ctx.rotationInterval != 7200) { + printf(" FAIL: SetInterval failed\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -2; + } + + /* Set grace period */ + ret = wolfSSL_TicketKeyRotation_SetGracePeriod(&ctx, 1800); + if (ret != 0 || ctx.gracePeriod != 1800) { + printf(" FAIL: SetGracePeriod failed\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -3; + } + + /* Invalid interval */ + ret = wolfSSL_TicketKeyRotation_SetInterval(&ctx, 0); + if (ret != BAD_FUNC_ARG) { + printf(" FAIL: Zero interval should fail\n"); + wolfSSL_TicketKeyRotation_Free(&ctx); + return -4; + } + + wolfSSL_TicketKeyRotation_Free(&ctx); + printf(" PASS\n"); + return 0; +} + +/* Main test runner */ +int test_session_ticket_rotation(void) +{ + int ret; + + printf("Session Ticket Rotation Tests:\n"); + + ret = test_TicketKeyRotation_InitFree(); + if (ret != 0) return ret; + + ret = test_TicketKeyRotation_SetMasterSecret(); + if (ret != 0) return ret; + + ret = test_TicketKeyRotation_ForceRotate(); + if (ret != 0) return ret; + + ret = test_TicketKeyRotation_MultipleRotations(); + if (ret != 0) return ret; + + ret = test_TicketKeyRotation_FindKeyByName(); + if (ret != 0) return ret; + + ret = test_TicketKeyRotation_Callback(); + if (ret != 0) return ret; + + ret = test_TicketKeyRotation_Configuration(); + if (ret != 0) return ret; + + printf("All session ticket rotation tests passed!\n"); + return 0; +} + +#endif /* HAVE_SESSION_TICKET */