From 599bef555c21e9243564e1a4f41b4121fbdc8770 Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 6 Aug 2026 00:03:51 -0500 Subject: [PATCH] ssl_sess: sanity test session cache save and restore. --- CMakeLists.txt | 1 + src/ssl_sess.c | 221 +++++----------- tests/api/test_session.c | 530 +++++++++++++++++++++++++++++++++++++++ tests/api/test_session.h | 4 +- wolfssl/include.am | 3 +- wolfssl/internal.h | 48 ++-- wolfssl/ssl_sess.h | 192 ++++++++++++++ 7 files changed, 809 insertions(+), 190 deletions(-) create mode 100644 wolfssl/ssl_sess.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e44e5d81be..0a466d1d5c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4569,6 +4569,7 @@ include(GNUInstallDirs) set(HEADER_EXCLUDE "internal.h" + "ssl_sess.h" "wolfssl/wolfcrypt/port/nrf51.h" "wolfssl/wolfcrypt/port/arm" "wolfssl/wolfcrypt/port/cypress" diff --git a/src/ssl_sess.c b/src/ssl_sess.c index cfc5eb48133..e685b1e0fb4 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -28,85 +28,7 @@ #else #ifndef NO_SESSION_CACHE - - /* basic config gives a cache with 33 sessions, adequate for clients and - embedded servers - - TITAN_SESSION_CACHE allows just over 2 million sessions, for servers - with titanic amounts of memory with long session ID timeouts and high - levels of traffic. - - ENABLE_SESSION_CACHE_ROW_LOCK: Allows row level locking for increased - performance with large session caches - - HUGE_SESSION_CACHE yields 65,791 sessions, for servers under heavy load, - allows over 13,000 new sessions per minute or over 200 new sessions per - second - - BIG_SESSION_CACHE yields 20,027 sessions - - MEDIUM_SESSION_CACHE allows 1055 sessions, adequate for servers that - aren't under heavy load, basically allows 200 new sessions per minute - - SMALL_SESSION_CACHE only stores 6 sessions, good for embedded clients - or systems where the default of is too much RAM. - SessionCache takes about 2K, ClientCache takes about 3Kbytes - - MICRO_SESSION_CACHE only stores 1 session, good for embedded clients - 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) - SessionCache takes about 13K bytes, ClientCache takes 17K bytes - */ - #if defined(TITAN_SESSION_CACHE) - #define SESSIONS_PER_ROW 31 - #define SESSION_ROWS 64937 - #ifndef ENABLE_SESSION_CACHE_ROW_LOCK - #define ENABLE_SESSION_CACHE_ROW_LOCK - #endif - #elif defined(HUGE_SESSION_CACHE) - #define SESSIONS_PER_ROW 11 - #define SESSION_ROWS 5981 - #elif defined(BIG_SESSION_CACHE) - #define SESSIONS_PER_ROW 7 - #define SESSION_ROWS 2861 - #elif defined(MEDIUM_SESSION_CACHE) - #define SESSIONS_PER_ROW 5 - #define SESSION_ROWS 211 - #elif defined(SMALL_SESSION_CACHE) - #define SESSIONS_PER_ROW 2 - #define SESSION_ROWS 3 - #elif defined(MICRO_SESSION_CACHE) - #define SESSIONS_PER_ROW 1 - #define SESSION_ROWS 1 - #else - #define SESSIONS_PER_ROW 3 - #define SESSION_ROWS 11 - #endif - #define INVALID_SESSION_ROW (-1) - - #ifdef NO_SESSION_CACHE_ROW_LOCK - #undef ENABLE_SESSION_CACHE_ROW_LOCK - #endif - - typedef struct SessionRow { - int nextIdx; /* where to place next one */ - int totalCount; /* sessions ever on this row */ -#ifdef SESSION_CACHE_DYNAMIC_MEM - WOLFSSL_SESSION* Sessions[SESSIONS_PER_ROW]; - void* heap; -#else - WOLFSSL_SESSION Sessions[SESSIONS_PER_ROW]; -#endif - - #ifdef ENABLE_SESSION_CACHE_ROW_LOCK - /* not included in import/export */ - wolfSSL_RwLock row_lock; - int lock_valid; - #endif - } SessionRow; - #define SIZEOF_SESSION_ROW (sizeof(WOLFSSL_SESSION) + (sizeof(int) * 2)) + #include static WC_THREADSHARED SessionRow SessionCache[SESSION_ROWS]; @@ -127,54 +49,13 @@ #endif #if !defined(NO_SESSION_CACHE_REF) && defined(NO_CLIENT_CACHE) - #error ClientCache is required when not using NO_SESSION_CACHE_REF + #error ClientCache is required when not using NO_SESSION_CACHE_REF #endif #ifndef NO_CLIENT_CACHE - - #ifndef CLIENT_SESSIONS_MULTIPLIER - #ifdef NO_SESSION_CACHE_REF - #define CLIENT_SESSIONS_MULTIPLIER 1 - #else - /* ClientSession objects are lightweight (compared to - * WOLFSSL_SESSION) so to decrease chance that user will reuse - * the wrong session, increase the ClientCache size. This will - * make the entire ClientCache about the size of one - * WOLFSSL_SESSION object. */ - #define CLIENT_SESSIONS_MULTIPLIER 8 - #endif - #endif - #define CLIENT_SESSIONS_PER_ROW \ - (SESSIONS_PER_ROW * CLIENT_SESSIONS_MULTIPLIER) - #define CLIENT_SESSION_ROWS (SESSION_ROWS * CLIENT_SESSIONS_MULTIPLIER) - - #if CLIENT_SESSIONS_PER_ROW > 65535 - #error CLIENT_SESSIONS_PER_ROW too big - #endif - #if CLIENT_SESSION_ROWS > 65535 - #error CLIENT_SESSION_ROWS too big - #endif - - struct ClientSession { - word16 serverRow; /* SessionCache Row id */ - word16 serverIdx; /* SessionCache Idx (column) */ - word32 sessionIDHash; - }; - #ifndef WOLFSSL_CLIENT_SESSION_DEFINED - typedef struct ClientSession ClientSession; - #define WOLFSSL_CLIENT_SESSION_DEFINED - #endif - - typedef struct ClientRow { - int nextIdx; /* where to place next one */ - int totalCount; /* sessions ever on this row */ - ClientSession Clients[CLIENT_SESSIONS_PER_ROW]; - } ClientRow; - static WC_THREADSHARED ClientRow ClientCache[CLIENT_SESSION_ROWS]; /* Client Cache */ /* uses session mutex */ - /* ClientCache mutex */ static WC_THREADSHARED wolfSSL_Mutex clisession_mutex WOLFSSL_MUTEX_INITIALIZER_CLAUSE(clisession_mutex); @@ -392,28 +273,6 @@ int wolfSSL_SetServerID(WOLFSSL* ssl, const byte* id, int len, int newSession) */ #if defined(PERSIST_SESSION_CACHE) && !defined(SESSION_CACHE_DYNAMIC_MEM) -/* for persistence, if changes to layout need to increment and modify - save_session_cache() and restore_session_cache and memory versions too */ -#define WOLFSSL_CACHE_VERSION 2 - -/* Session Cache Header information */ -typedef struct { - int version; /* cache layout version id */ - int rows; /* session rows */ - int columns; /* session columns */ - int sessionSz; /* sizeof WOLFSSL_SESSION */ -} cache_header_t; - -/* current persistence layout is: - - 1) cache_header_t - 2) SessionCache - 3) ClientCache - - update WOLFSSL_CACHE_VERSION if change layout for the following - PERSISTENT_SESSION_CACHE functions -*/ - /* get how big the the session cache save buffer needs to be */ int wolfSSL_get_session_cache_memsize(void) { @@ -424,7 +283,6 @@ int wolfSSL_get_session_cache_memsize(void) return sz; } - /* Persist session cache to memory */ int wolfSSL_memsave_session_cache(void* mem, int sz) { @@ -465,7 +323,7 @@ int wolfSSL_memsave_session_cache(void* mem, int sz) } #endif - XMEMCPY(row++, &SessionCache[i], SIZEOF_SESSION_ROW); + XMEMCPY(row++, &SessionCache[i], SIZEOF_SESSION_ROW_SAVE); #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif @@ -490,8 +348,10 @@ int wolfSSL_memsave_session_cache(void* mem, int sz) #if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ - (defined(HAVE_SESSION_TICKET) || \ - (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) + (defined(PERSIST_SESSION_CACHE) || \ + defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) || \ + defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) static void SessionSanityPointerSet(SessionRow* row) { int j; @@ -499,27 +359,43 @@ static void SessionSanityPointerSet(SessionRow* row) /* Reset pointers to safe values after raw copy */ for (j = 0; j < SESSIONS_PER_ROW; j++) { WOLFSSL_SESSION* s = &row->Sessions[j]; -#ifdef HAVE_SESSION_TICKET + s->type = WOLFSSL_SESSION_TYPE_CACHE; + #ifdef HAVE_SESSION_TICKET s->ticket = s->staticTicket; s->ticketLenAlloc = 0; if (s->ticketLen > SESSION_TICKET_LEN) { s->ticketLen = SESSION_TICKET_LEN; } -#endif -#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ - defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ - (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) + #endif /* HAVE_SESSION_TICKET */ + #if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ + (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && \ + FIPS_VERSION_GE(5,3))) s->ticketNonce.data = s->ticketNonce.dataStatic; if (s->ticketNonce.len > MAX_TICKET_NONCE_STATIC_SZ) { s->ticketNonce.len = MAX_TICKET_NONCE_STATIC_SZ; } -#endif -#if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) + #endif /* WOLFSSL_TLS13 && HAVE_SESSION_TICKET && etc. */ + /* Sanitize the restored session: none of the following fields are + * meaningful after session restoration. + * + * We could null these instead in wolfSSL_[mem]save_session_cache(), + * but that would require an extra scratch copy (because we save from + * the live session). */ + #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) s->peer = NULL; -#endif + #endif /* SESSION_CERTS || OPENSSL_EXTRA */ + #if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA) + s->rem_sess_cb = NULL; + #endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + s->heap = NULL; + #ifdef HAVE_EX_DATA + XMEMSET(&s->ex_data, 0, sizeof(WOLFSSL_CRYPTO_EX_DATA)); + s->ownExData = 0; + #endif /* HAVE_EX_DATA */ } } -#endif +#endif /* !SESSION_CACHE_DYNAMIC_MEM && (HAVE_SESSION_TICKET || etc. ) */ /* Restore the persistent session cache from memory */ int wolfSSL_memrestore_session_cache(const void* mem, int sz) @@ -565,10 +441,12 @@ int wolfSSL_memrestore_session_cache(const void* mem, int sz) } #endif - XMEMCPY(&SessionCache[i], row++, SIZEOF_SESSION_ROW); + XMEMCPY(&SessionCache[i], row++, SIZEOF_SESSION_ROW_RESTORE); #if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ - (defined(HAVE_SESSION_TICKET) || \ - (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) + (defined(PERSIST_SESSION_CACHE) || \ + defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) || \ + defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) SessionSanityPointerSet(&SessionCache[i]); #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK @@ -642,7 +520,7 @@ int wolfSSL_save_session_cache(const char *fname) } #endif - ret = (int)XFWRITE(&SessionCache[i], SIZEOF_SESSION_ROW, 1, file); + ret = (int)XFWRITE(&SessionCache[i], SIZEOF_SESSION_ROW_SAVE, 1, file); #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif @@ -729,10 +607,12 @@ int wolfSSL_restore_session_cache(const char *fname) } #endif - ret = (int)XFREAD(&SessionCache[i], SIZEOF_SESSION_ROW, 1, file); + ret = (int)XFREAD(&SessionCache[i], SIZEOF_SESSION_ROW_RESTORE, 1, file); #if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ - (defined(HAVE_SESSION_TICKET) || \ - (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) + (defined(PERSIST_SESSION_CACHE) || \ + defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) || \ + defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) SessionSanityPointerSet(&SessionCache[i]); #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK @@ -744,6 +624,20 @@ int wolfSSL_restore_session_cache(const char *fname) rc = FREAD_ERROR; break; } + #ifdef ENABLE_SESSION_CACHE_ROW_LOCK + /* the file read did not include the row lock members. seek to next + * read offset. */ + if (i < cache_header.rows) { + ret = (int)XFSEEK(file, sizeof(cache_header) + + (i + 1) * sizeof(SessionRow), XSEEK_SET); + if (ret) { + WOLFSSL_MSG_EX("Session cache file seek(%d * %zu) failed", + i + 1, sizeof(SessionRow)); + rc = FREAD_ERROR; + break; + } + } + #endif } #ifndef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[0]); @@ -774,7 +668,6 @@ int wolfSSL_restore_session_cache(const char *fname) #endif /* !NO_FILESYSTEM */ #endif /* PERSIST_SESSION_CACHE && !SESSION_CACHE_DYNAMIC_MEM */ - /* on by default if built in but allow user to turn off */ WOLFSSL_ABI long wolfSSL_CTX_set_session_cache_mode(WOLFSSL_CTX* ctx, long mode) diff --git a/tests/api/test_session.c b/tests/api/test_session.c index e7557ff0070..82f87cf9b14 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -1614,3 +1614,533 @@ int test_wolfSSL_SESSION_get_ex_new_index(void) return TEST_SKIPPED; } #endif + +#if defined(PERSIST_SESSION_CACHE) && !defined(NO_SESSION_CACHE) && \ + !defined(SESSION_CACHE_DYNAMIC_MEM) +/* Several tests to ensure persistent session cache is saved and restored + * with expected behavior. */ +#include + +#if (defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) +static int test_rem_sess_cb_count = 0; + +static void test_rem_sess_cb(WOLFSSL_CTX* ctx, WOLFSSL_SESSION* sess) +{ + (void)ctx; (void)sess; + WOLFSSL_MSG_EX("error: test test_rem_sess_cb called: %d", + test_rem_sess_cb_count); + test_rem_sess_cb_count++; +} +#endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + +struct sess_cache_t { + cache_header_t hdr; + SessionRow s_rows[SESSION_ROWS]; + #ifndef NO_CLIENT_CACHE + ClientRow c_rows[CLIENT_SESSION_ROWS]; + #endif /* !NO_CLIENT_CACHE */ +}; + +typedef struct sess_cache_t sess_cache_t; + +/* Set the session cache to canary values for testing. + * Will be checked later in test_sanity_sessions(). + * */ +static void test_set_sessions(struct sess_cache_t * cache_mem) +{ + size_t i = 0; + size_t j = 0; + + for (i = 0; i < SESSION_ROWS; ++i) { + SessionRow * row = &cache_mem->s_rows[i]; + for (j = 0; j < SESSIONS_PER_ROW; ++j) { + WOLFSSL_SESSION * s = &row->Sessions[j]; + s->timeout = 1; + s->bornOn = 0; + /* label sessions with something predictable */ + s->sessionIDSz = 2; + s->sessionID[0] = (byte)i; + s->sessionID[1] = (byte)j; + /* set type, ticketLen, rem_ess_cb, heap, peer, etc to test + * canary values. These should be sanitized on session restore. */ + s->type = WOLFSSL_SESSION_TYPE_UNKNOWN; + #ifdef HAVE_SESSION_TICKET + s->ticketLen = SESSION_TICKET_LEN + 1; + s->ticketLenAlloc = 1; + #endif /* HAVE_SESSION_TICKET */ + #if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA) + s->rem_sess_cb = test_rem_sess_cb; + #endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + s->heap = (void *)0x77; + #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) + s->peer = (WOLFSSL_X509 *)0x77; + #endif /* SESSION_CERTS && OPENSSL_EXTRA */ + #ifdef HAVE_EX_DATA + XMEMSET(&s->ex_data, 0x77, sizeof(WOLFSSL_CRYPTO_EX_DATA)); + s->ownExData = 1; + #endif /* HAVE_EX_DATA */ + #ifdef WOLFSSL_TEST_SESSION_VERBOSE + WOLFSSL_MSG_EX("info: session(%zu, %zu): %p, id = 0x%02x%02x", + i, j, s, s->sessionID[0], s->sessionID[1]); + #endif /* WOLFSSL_TEST_SESSION_VERBOSE */ + } + } +} + +#define field_null_or_fail(s, what, fld) do { \ + if ((s)->fld != NULL) { \ + WOLFSSL_MSG_EX("error: s = %p, id = 0x%02x%02x, %s = %p", \ + (s), (s)->sessionID[0], (s)->sessionID[1], what, \ + (s)->fld); \ + ret = -1; \ + goto sanity_fail; \ + } \ +} while (0) + +/* sanity check the restored session cache. + * return 0 on success + * return -1 on error + * */ +static int test_sanity_sessions(const struct sess_cache_t * cache_mem) +{ + int ret = -1; + size_t i = 0; + size_t j = 0; + + /* walk sessions, check for expected values */ + for (i = 0; i < SESSION_ROWS; ++i) { + const SessionRow * row = &cache_mem->s_rows[i]; + for (j = 0; j < SESSIONS_PER_ROW; ++j) { + const WOLFSSL_SESSION * s = &row->Sessions[j]; + #ifdef HAVE_EX_DATA + const WOLFSSL_CRYPTO_EX_DATA * ex_data = &s->ex_data; + size_t k = 0; + #endif /* HAVE_EX_DATA */ + + #ifdef HAVE_SESSION_TICKET + if (s->ticketLenAlloc != 0 || + s->ticketLen > SESSION_TICKET_LEN) { + WOLFSSL_MSG_EX("error: s = %p, id = 0x%02x%02x: " + "ticketLenAlloc = %d, ticketLen = %d\n", s, + s->sessionID[0], s->sessionID[1], + s->ticketLenAlloc, s->ticketLen); + ret = -1; + goto sanity_fail; + } + #endif /* HAVE_SESSION_TICKET */ + + if (s->type != WOLFSSL_SESSION_TYPE_CACHE) { + WOLFSSL_MSG_EX("error: s = %p, id = 0x%02x%02x: " + "type not cache: 0x%02x", s, + s->sessionID[0], s->sessionID[1], + s->type); + ret = -1; + goto sanity_fail; + } + + if (s->sessionIDSz != 2) { + WOLFSSL_MSG_EX("error: s = %p: got id sz = %d" + ", expected id sz = 2", + s, s->sessionIDSz); + ret = -1; + goto sanity_fail; + } + + if (s->sessionID[0] != (byte)i || + s->sessionID[1] != (byte)j) { + WOLFSSL_MSG_EX("error: s = %p, got id = 0x%02x%02x:" + ", expected id = 0x%02x%02x", + s, s->sessionID[0], s->sessionID[1], (byte)i, (byte)j); + ret = -1; + goto sanity_fail; + } + + /* all the remaining fields should have been sanitized as null */ + field_null_or_fail(s, "heap", heap); + #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) + field_null_or_fail(s, "peer", peer); + #endif /* SESSION_CERTS && OPENSSL_EXTRA */ + #if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA) + field_null_or_fail(s, "rem_sess_cb", rem_sess_cb); + #endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + + #ifdef HAVE_EX_DATA + if (s->ownExData) { + WOLFSSL_MSG_EX("error: s = %p, id = 0x%02x%02x, ownExData = %d", + s, s->sessionID[0], s->sessionID[1], + s->ownExData); + ret = -1; + goto sanity_fail; + } + + for (k = 0; k < MAX_EX_DATA; ++k) { + if (ex_data->ex_data[k] + #ifdef HAVE_EX_DATA_CLEANUP_HOOKS + || ex_data->ex_data_cleanup_routines[k] + #endif /* HAVE_EX_DATA_CLEANUP_HOOKS */ + ) { + WOLFSSL_MSG_EX("error: s = %p, id = 0x%02x%02x:" + "ex_data[%d] not null", s, + s->sessionID[0], s->sessionID[1], k); + ret = -1; + goto sanity_fail; + } + } /* for k */ + #endif /* HAVE_EX_DATA */ + } /* for j */ + } /* for i */ + + ret = 0; +sanity_fail: + return ret; +} + +/* Tests mem[save, restore]_session_cache. + * + * returns -1 on err + * returns num times the session callback was called (0 is expected) + * */ +static int test_mem_session_cache(void) +{ + int ret = -1; + int mem_sz = 0; + sess_cache_t * cache_mem = NULL; + #ifndef NO_CLIENT_CACHE + ClientRow * c_rows = NULL; + #endif /* !NO_CLIENT_CACHE */ + + #if (defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) + /* reset callback count */ + test_rem_sess_cb_count = 0; + #endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + + /* get session cache size, and allocate scratch copy */ + mem_sz = wolfSSL_get_session_cache_memsize(); + if (mem_sz != sizeof(struct sess_cache_t)) { + WOLFSSL_MSG_EX("error: got mem_sz %d, expected %zu\n", mem_sz, + sizeof(struct sess_cache_t)); + return -1; + } + + cache_mem = (sess_cache_t *)XMALLOC((size_t)mem_sz, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (cache_mem == NULL) { + WOLFSSL_MSG_EX("error: xmalloc(%zu) failed", (size_t) mem_sz); + return -1; + } + + #ifndef NO_CLIENT_CACHE + /* allocate scratch ClientCache */ + c_rows = (ClientRow *)XMALLOC(sizeof(ClientRow) * CLIENT_SESSION_ROWS, + NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (c_rows == NULL) { + WOLFSSL_MSG_EX("error: xmalloc(%zu) failed", + sizeof(ClientRow) * CLIENT_SESSION_ROWS); + ret = -1; + goto cleanup; + } + #endif /* !NO_CLIENT_CACHE */ + + /* save cache to cache_mem struct */ + if (wolfSSL_memsave_session_cache(cache_mem, mem_sz) != WOLFSSL_SUCCESS) { + ret = -1; + goto cleanup; + } + + /* setup test sessions */ + test_set_sessions(cache_mem); + + #ifndef NO_CLIENT_CACHE + /* copy current client cache */ + XMEMCPY(c_rows, &cache_mem->c_rows, sizeof(cache_mem->c_rows)); + #endif /* !NO_CLIENT_CACHE */ + + /* restore from mem */ + if (wolfSSL_memrestore_session_cache(cache_mem, mem_sz) != WOLFSSL_SUCCESS) { + ret = -1; + goto cleanup; + } + + /* wipe our session struct in memory */ + XMEMSET(cache_mem, 0, sizeof(sess_cache_t)); + + /* save back to cache_mem struct */ + if (wolfSSL_memsave_session_cache(cache_mem, mem_sz) != WOLFSSL_SUCCESS) { + ret = -1; + goto cleanup; + } + + /* sanity check values */ + ret = test_sanity_sessions(cache_mem); + if (ret) { + goto cleanup; + } + + #ifndef NO_CLIENT_CACHE + /* verify we got back the exact client cache */ + ret = XMEMCMP(c_rows, &cache_mem->c_rows, sizeof(cache_mem->c_rows)); + if (ret) { + WOLFSSL_MSG_EX("error: mem restore c_rows diff: %d", ret); + ret = -1; + goto cleanup; + } + #endif /* !NO_CLIENT_CACHE */ + + /* eviction: flush sessions older than time 2 (0 + 1 < 2). */ + wolfSSL_CTX_flush_sessions(NULL, 2); + + #if (defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) + /* if all else succeeded, finally set ret to callback count. */ + ret = test_rem_sess_cb_count; + #endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + +cleanup: + if (cache_mem != NULL) { + XFREE(cache_mem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + cache_mem = NULL; + } + + #ifndef NO_CLIENT_CACHE + if (c_rows != NULL) { + XFREE(c_rows, NULL, DYNAMIC_TYPE_TMP_BUFFER); + c_rows = NULL; + } + #endif /* !NO_CLIENT_CACHE */ + + return ret; +} + +#if !defined(NO_FILESYSTEM) +/* write cache_mem to file. + * + * returns 0 on success + * returns < 0 on error + * */ +static int test_write_file(const char * fname, + const struct sess_cache_t * cache_mem) +{ + XFILE file = XBADFILE; + size_t n_write = 0; + int ret = -1; + + file = XFOPEN(fname, "w+b"); + if (file == XBADFILE) { + WOLFSSL_MSG_EX("error: open(%s, w+b)", fname); + goto write_file_cleanup; + } + + n_write = (int)XFWRITE(cache_mem, sizeof(struct sess_cache_t), 1, file); + if (n_write != 1) { + WOLFSSL_MSG_EX("error: write %s: %d", fname, n_write); + goto write_file_cleanup; + } + + ret = 0; +write_file_cleanup: + if (file != XBADFILE) { + XFCLOSE(file); + file = XBADFILE; + } + + return ret; +} + +/* read cache_mem from file. + * + * returns 0 on success + * returns < 0 on error + * */ +static int test_read_file(const char * fname, + struct sess_cache_t * cache_mem) +{ + XFILE file = XBADFILE; + size_t read = 0; + int ret = -1; + + file = XFOPEN(fname, "rb"); + if (file == XBADFILE) { + WOLFSSL_MSG_EX("error: open(%s, rb)", fname); + goto read_file_cleanup; + } + + read = XFREAD(cache_mem, sizeof(struct sess_cache_t), 1, file); + if (read != 1) { + WOLFSSL_MSG_EX("error: read %s: %d", fname, ret); + goto read_file_cleanup; + } + + ret = 0; + +read_file_cleanup: + if (file != XBADFILE) { + XFCLOSE(file); + file = XBADFILE; + } + + return ret; +} + +/* Tests file [save, restore]_session_cache. + * + * returns -1 on err + * returns num times the session callback was called (0 is expected) + * */ +static int test_file_session_cache(void) +{ + int ret = -1; + int mem_sz = 0; + sess_cache_t * cache_mem = NULL; + const char * fname = "tmp_test_session_cache.bin"; + #ifndef NO_CLIENT_CACHE + ClientRow * c_rows = NULL; + #endif /* !NO_CLIENT_CACHE */ + + #if (defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) + /* reset callback count */ + test_rem_sess_cb_count = 0; + #endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + + /* get session cache size, and allocate scratch copy */ + mem_sz = wolfSSL_get_session_cache_memsize(); + if (mem_sz != sizeof(struct sess_cache_t)) { + WOLFSSL_MSG_EX("error: got mem_sz %d, expected %zu\n", mem_sz, + sizeof(struct sess_cache_t)); + return -1; + } + + cache_mem = (sess_cache_t *)XMALLOC((size_t)mem_sz, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (cache_mem == NULL) { + WOLFSSL_MSG_EX("error: xmalloc(%zu) failed", (size_t) mem_sz); + return -1; + } + + #ifndef NO_CLIENT_CACHE + /* allocate scratch ClientCache */ + c_rows = (ClientRow *)XMALLOC(sizeof(ClientRow) * CLIENT_SESSION_ROWS, + NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (c_rows == NULL) { + WOLFSSL_MSG_EX("error: xmalloc(%zu) failed", + sizeof(ClientRow) * CLIENT_SESSION_ROWS); + goto file_cleanup; + } + #endif /* !NO_CLIENT_CACHE */ + + /* save cache to file fname */ + if (wolfSSL_save_session_cache(fname) != WOLFSSL_SUCCESS) { + ret = -1; + goto file_cleanup; + } + + /* read it into cache_mem struct */ + ret = test_read_file(fname, cache_mem); + if (ret) { + goto file_cleanup; + } + + /* setup test sessions */ + test_set_sessions(cache_mem); + + #ifndef NO_CLIENT_CACHE + /* copy current client cache */ + XMEMCPY(c_rows, &cache_mem->c_rows, sizeof(cache_mem->c_rows)); + #endif /* !NO_CLIENT_CACHE */ + + /* write it back to file */ + ret = test_write_file(fname, cache_mem); + if (ret) { + goto file_cleanup; + } + + /* restore from file */ + if (wolfSSL_restore_session_cache(fname) != WOLFSSL_SUCCESS) { + ret = -1; + goto file_cleanup; + } + + /* save back to file fname */ + if (wolfSSL_save_session_cache(fname) != WOLFSSL_SUCCESS) { + ret = -1; + goto file_cleanup; + } + + /* wipe our session struct in memory */ + XMEMSET(cache_mem, 0, sizeof(sess_cache_t)); + + /* read it into cache_mem struct */ + ret = test_read_file(fname, cache_mem); + if (ret) { + goto file_cleanup; + } + + /* sanity check values */ + ret = test_sanity_sessions(cache_mem); + if (ret) { + goto file_cleanup; + } + + #ifndef NO_CLIENT_CACHE + /* verify we got back the exact client cache */ + ret = XMEMCMP(c_rows, &cache_mem->c_rows, sizeof(cache_mem->c_rows)); + if (ret) { + WOLFSSL_MSG_EX("error: file restore c_rows diff: %d", ret); + ret = -1; + goto file_cleanup; + } + #endif /* !NO_CLIENT_CACHE */ + + /* eviction: flush sessions older than time 2 (0 + 1 < 2). */ + wolfSSL_CTX_flush_sessions(NULL, 2); + +file_cleanup: + /* remove session cache file. the file existing and being removed + * is part of the expected result. */ + { + int rc = remove(fname); + if (rc) { + fprintf(stderr, "remove(%s) failed: %d\n", fname, ret); + } + if (ret == 0 && rc) { + ret = rc; + } + } + + #if (defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)) + /* if all else succeeded, finally set ret to callback count. */ + if (ret == 0) { + ret = test_rem_sess_cb_count; + } + #endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ + + if (cache_mem != NULL) { + XFREE(cache_mem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + cache_mem = NULL; + } + + #ifndef NO_CLIENT_CACHE + if (c_rows != NULL) { + XFREE(c_rows, NULL, DYNAMIC_TYPE_TMP_BUFFER); + c_rows = NULL; + } + #endif /* !NO_CLIENT_CACHE */ + + return ret; +} +#endif /* NO_FILESYSTEM */ + +int test_wolfSSL_session_cache_restore(void) +{ + EXPECT_DECLS; + ExpectIntEQ(test_mem_session_cache(), 0); + #if !defined(NO_FILESYSTEM) + ExpectIntEQ(test_file_session_cache(), 0); + #endif /* NO_FILESYSTEM */ + return EXPECT_RESULT(); +} +#else +int test_wolfSSL_session_cache_restore(void) +{ + return TEST_SKIPPED; +} +#endif /* PERSIST_SESSION_CACHE && !NO_SESSION_CACHE && + * !SESSION_CACHE_DYNAMIC_MEM && etc... */ diff --git a/tests/api/test_session.h b/tests/api/test_session.h index 9dc2e258f14..e3e5505433c 100644 --- a/tests/api/test_session.h +++ b/tests/api/test_session.h @@ -36,6 +36,7 @@ int test_wolfSSL_SESSION_expire_downgrade(void); int test_wolfSSL_CTX_sess_set_remove_cb(void); int test_wolfSSL_ticket_keys(void); int test_wolfSSL_SESSION_get_ex_new_index(void); +int test_wolfSSL_session_cache_restore(void); #define TEST_SESSION_DECLS \ TEST_DECL_GROUP("session", test_wolfSSL_CTX_add_session), \ @@ -49,6 +50,7 @@ int test_wolfSSL_SESSION_get_ex_new_index(void); TEST_DECL_GROUP("session", test_wolfSSL_SESSION_expire_downgrade), \ TEST_DECL_GROUP("session", test_wolfSSL_CTX_sess_set_remove_cb), \ TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys), \ - TEST_DECL_GROUP("session", test_wolfSSL_SESSION_get_ex_new_index) + TEST_DECL_GROUP("session", test_wolfSSL_SESSION_get_ex_new_index), \ + TEST_DECL_GROUP("session", test_wolfSSL_session_cache_restore) #endif /* WOLFCRYPT_TEST_SESSION_H */ diff --git a/wolfssl/include.am b/wolfssl/include.am index 0da35486d33..d1f9abfe3ba 100644 --- a/wolfssl/include.am +++ b/wolfssl/include.am @@ -27,7 +27,8 @@ nobase_include_HEADERS+= \ wolfssl/wolfio.h noinst_HEADERS+= \ - wolfssl/internal.h + wolfssl/internal.h \ + wolfssl/ssl_sess.h # For distro build don't install options.h. # It depends on the architecture and conflicts with Multi-Arch. diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 573bab58c90..83a515bdf3e 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -4965,7 +4965,7 @@ typedef struct TicketNonce { byte data[MAX_TICKET_NONCE_STATIC_SZ]; #endif /* WOLFSSL_TICKET_NONCE_MALLOC && FIPS_VERSION_GE(5,3) */ } TicketNonce; -#endif +#endif /* WOLFSSL_TLS13 && HAVE_SESSION_TICKET || !NO_PSK*/ /* wolfSSL session type */ struct WOLFSSL_SESSION { @@ -4974,16 +4974,16 @@ struct WOLFSSL_SESSION { WOLFSSL_SESSION_TYPE type; #ifndef NO_SESSION_CACHE int cacheRow; /* row in session cache */ -#endif +#endif /* NO_SESSION_CACHE */ wolfSSL_Ref ref; byte altSessionID[ID_LEN]; byte haveAltSessionID:1; #ifdef HAVE_EX_DATA byte ownExData:1; -#endif +#endif /* HAVE_EX_DATA */ #if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA) Rem_Sess_Cb rem_sess_cb; -#endif +#endif /* HAVE_EXT_CACHE || HAVE_EX_DATA */ void* heap; /* WARNING The above fields (up to and including the heap) are not copied * in wolfSSL_DupSession. Place new fields after the heap @@ -5003,41 +5003,41 @@ struct WOLFSSL_SESSION { word16 haveEMS; /* ext master secret flag */ #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) WOLFSSL_X509* peer; /* peer cert */ -#endif +#endif /* SESSION_CERTS && OPENSSL_EXTRA */ ProtocolVersion version; /* which version was used */ #if defined(SESSION_CERTS) || !defined(NO_RESUME_SUITE_CHECK) || \ (defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET)) byte cipherSuite0; /* first byte, normally 0 */ byte cipherSuite; /* 2nd byte, actual suite */ -#endif +#endif /* SESSION_CERTS || !NO_RESUME_SUITE_CHECK || ... */ #ifndef NO_CLIENT_CACHE word16 idLen; /* serverID length */ byte serverID[SERVER_ID_LEN]; /* for easier client lookup */ -#endif +#endif /* !NO_CLIENT_CACHE */ #ifdef WOLFSSL_SESSION_ID_CTX byte sessionCtxSz; /* sessionCtx length */ byte sessionCtx[ID_LEN]; /* app specific context id */ #endif /* WOLFSSL_SESSION_ID_CTX */ #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) byte peerVerifyRet; /* cert verify error */ -#endif +#endif /* OPENSSL_EXTRA || OPENSSL_EXTRA_X509_SMALL */ #ifdef WOLFSSL_TLS13 word16 namedGroup; -#endif +#endif /* WOLFSSL_TLS13 */ #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) -#ifdef WOLFSSL_TLS13 -#ifdef WOLFSSL_32BIT_MILLI_TIME + #ifdef WOLFSSL_TLS13 + #ifdef WOLFSSL_32BIT_MILLI_TIME word32 ticketSeen; /* Time ticket seen (ms) */ -#else + #else sword64 ticketSeen; /* Time ticket seen (ms) */ -#endif + #endif /* WOLFSSL_32BIT_MILLI_TIME */ word32 ticketAdd; /* Added by client */ TicketNonce ticketNonce; /* Nonce used to derive PSK */ -#endif -#ifdef WOLFSSL_EARLY_DATA + #endif /* WOLFSSL_TLS13 */ + #ifdef WOLFSSL_EARLY_DATA word32 maxEarlyDataSz; -#endif -#endif + #endif /* WOLFSSL_EARLY_DATA */ +#endif /* HAVE_SESSION_TICKET || !NO_PSK */ #ifdef HAVE_SESSION_TICKET byte staticTicket[SESSION_TICKET_LEN]; byte* ticket; @@ -5045,25 +5045,25 @@ struct WOLFSSL_SESSION { word16 ticketLenAlloc; /* is dynamic */ #ifdef HAVE_SNI byte sniHash[TICKET_BINDING_HASH_SZ]; /* SNI at issue */ -#endif -#ifdef HAVE_ALPN +#endif /* HAVE_SNI */ + #ifdef HAVE_ALPN byte alpnHash[TICKET_BINDING_HASH_SZ]; /* ALPN at issue */ -#endif -#endif + #endif /* HAVE_ALPN */ +#endif /* HAVE_SESSION_TICKET */ #ifdef SESSION_CERTS WOLFSSL_X509_CHAIN chain; /* peer cert chain, static */ #ifdef WOLFSSL_ALT_CERT_CHAINS WOLFSSL_X509_CHAIN altChain; /* peer alt cert chain, static */ - #endif + #endif /* WOLFSSL_ALT_CERT_CHAINS */ #endif #ifdef HAVE_EX_DATA WOLFSSL_CRYPTO_EX_DATA ex_data; -#endif +#endif /* HAVE_EX_DATA */ #ifdef HAVE_MAX_FRAGMENT byte mfl; /* max fragment length negotiated i.e. * WOLFSSL_MFL_2_8 (6) */ -#endif +#endif /* HAVE_MAX_FRAGMENT */ byte isSetup:1; }; diff --git a/wolfssl/ssl_sess.h b/wolfssl/ssl_sess.h new file mode 100644 index 00000000000..c84d13a8d89 --- /dev/null +++ b/wolfssl/ssl_sess.h @@ -0,0 +1,192 @@ +/* ssl_sess.h + * + * Copyright (C) 2006-2026 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 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFSSL_SSL_SESS_H +#define WOLFSSL_SSL_SESS_H + +#ifdef __cplusplus + extern "C" { +#endif /* __cplusplus */ + + /* basic config gives a cache with 33 sessions, adequate for clients and + embedded servers + + TITAN_SESSION_CACHE allows just over 2 million sessions, for servers + with titanic amounts of memory with long session ID timeouts and high + levels of traffic. + + ENABLE_SESSION_CACHE_ROW_LOCK: Allows row level locking for increased + performance with large session caches + + HUGE_SESSION_CACHE yields 65,791 sessions, for servers under heavy load, + allows over 13,000 new sessions per minute or over 200 new sessions per + second + + BIG_SESSION_CACHE yields 20,027 sessions + + MEDIUM_SESSION_CACHE allows 1055 sessions, adequate for servers that + aren't under heavy load, basically allows 200 new sessions per minute + + SMALL_SESSION_CACHE only stores 6 sessions, good for embedded clients + or systems where the default of is too much RAM. + SessionCache takes about 2K, ClientCache takes about 3Kbytes + + MICRO_SESSION_CACHE only stores 1 session, good for embedded clients + 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) + SessionCache takes about 13K bytes, ClientCache takes 17K bytes + */ + #if defined(TITAN_SESSION_CACHE) + #define SESSIONS_PER_ROW 31 + #define SESSION_ROWS 64937 + #ifndef ENABLE_SESSION_CACHE_ROW_LOCK + #define ENABLE_SESSION_CACHE_ROW_LOCK + #endif + #elif defined(HUGE_SESSION_CACHE) + #define SESSIONS_PER_ROW 11 + #define SESSION_ROWS 5981 + #elif defined(BIG_SESSION_CACHE) + #define SESSIONS_PER_ROW 7 + #define SESSION_ROWS 2861 + #elif defined(MEDIUM_SESSION_CACHE) + #define SESSIONS_PER_ROW 5 + #define SESSION_ROWS 211 + #elif defined(SMALL_SESSION_CACHE) + #define SESSIONS_PER_ROW 2 + #define SESSION_ROWS 3 + #elif defined(MICRO_SESSION_CACHE) + #define SESSIONS_PER_ROW 1 + #define SESSION_ROWS 1 + #else + #define SESSIONS_PER_ROW 3 + #define SESSION_ROWS 11 + #endif + + #define INVALID_SESSION_ROW (-1) + + #ifdef NO_SESSION_CACHE_ROW_LOCK + #undef ENABLE_SESSION_CACHE_ROW_LOCK + #endif + + typedef struct SessionRow { + int nextIdx; /* where to place next one */ + int totalCount; /* sessions ever on this row */ + #ifdef SESSION_CACHE_DYNAMIC_MEM + WOLFSSL_SESSION* Sessions[SESSIONS_PER_ROW]; + void* heap; + #else + WOLFSSL_SESSION Sessions[SESSIONS_PER_ROW]; + #endif + + #ifdef ENABLE_SESSION_CACHE_ROW_LOCK + /* not included in import/export */ + wolfSSL_RwLock row_lock; + int lock_valid; + #endif + } SessionRow; + + #if defined(PERSIST_SESSION_CACHE) && !defined(SESSION_CACHE_DYNAMIC_MEM) + /* when writing session cache to storage, for simplicity just + * write the full SessionRow struct (including lock members). + * + * when restoring from storage however, do not read the full struct, + * as this would clobber active row locks. */ + #define SIZEOF_SESSION_ROW_SAVE (sizeof(SessionRow)) + + #ifdef ENABLE_SESSION_CACHE_ROW_LOCK + #define SIZEOF_SESSION_ROW_RESTORE WC_OFFSETOF(SessionRow, row_lock) + #else + #define SIZEOF_SESSION_ROW_RESTORE (sizeof(SessionRow)) + #endif /* ENABLE_SESSION_CACHE_ROW_LOCK */ + #endif /* PERSIST_SESSION_CACHE && !SESSION_CACHE_DYNAMIC_MEM */ + + #ifndef NO_CLIENT_CACHE + #ifndef CLIENT_SESSIONS_MULTIPLIER + #ifdef NO_SESSION_CACHE_REF + #define CLIENT_SESSIONS_MULTIPLIER 1 + #else + /* ClientSession objects are lightweight (compared to + * WOLFSSL_SESSION) so to decrease chance that user will reuse + * the wrong session, increase the ClientCache size. This will + * make the entire ClientCache about the size of one + * WOLFSSL_SESSION object. */ + #define CLIENT_SESSIONS_MULTIPLIER 8 + #endif + #endif + #define CLIENT_SESSIONS_PER_ROW \ + (SESSIONS_PER_ROW * CLIENT_SESSIONS_MULTIPLIER) + #define CLIENT_SESSION_ROWS (SESSION_ROWS * CLIENT_SESSIONS_MULTIPLIER) + + #if CLIENT_SESSIONS_PER_ROW > 65535 + #error CLIENT_SESSIONS_PER_ROW too big + #endif + #if CLIENT_SESSION_ROWS > 65535 + #error CLIENT_SESSION_ROWS too big + #endif + + struct ClientSession { + word16 serverRow; /* SessionCache Row id */ + word16 serverIdx; /* SessionCache Idx (column) */ + word32 sessionIDHash; + }; + #ifndef WOLFSSL_CLIENT_SESSION_DEFINED + typedef struct ClientSession ClientSession; + #define WOLFSSL_CLIENT_SESSION_DEFINED + #endif + + typedef struct ClientRow { + int nextIdx; /* where to place next one */ + int totalCount; /* sessions ever on this row */ + ClientSession Clients[CLIENT_SESSIONS_PER_ROW]; + } ClientRow; + #endif /* !NO_CLIENT_CACHE */ + + #if defined(PERSIST_SESSION_CACHE) && !defined(SESSION_CACHE_DYNAMIC_MEM) + /* for persistence, if changes to layout need to increment and modify + save_session_cache() and restore_session_cache and memory versions too */ + #define WOLFSSL_CACHE_VERSION 3 + + /* Session Cache Header information */ + typedef struct { + int version; /* cache layout version id */ + int rows; /* session rows */ + int columns; /* session columns */ + int sessionSz; /* sizeof WOLFSSL_SESSION */ + } cache_header_t; + + /* current persistence layout is: + + 1) cache_header_t + 2) SessionCache + 3) ClientCache + + update WOLFSSL_CACHE_VERSION if change layout for the following + PERSISTENT_SESSION_CACHE functions + */ + #endif /* PERSIST_SESSION_CACHE && !SESSION_CACHE_DYNAMIC_MEM */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* WOLFSSL_SSL_SESS_H */