TLS 1.3: gate 0-RTT on a cache-backed resumption ticket

RFC 8446 section 8 requires any server instance to accept 0-RTT for a
given ClientHello at most once. Prior to this change wolfSSL's behaviour
diverged from that requirement in several ways:

  * ctx->maxEarlyDataSz defaulted to MAX_EARLY_DATA_SZ whenever the
    library was built with WOLFSSL_EARLY_DATA, so servers auto-
    advertised 0-RTT in NewSessionTicket without the application
    asking. RFC 8446 E.5 says 0-RTT MUST NOT be enabled unless
    specifically requested.
  * The post-accept eviction is compiled out under NO_SESSION_CACHE,
    so builds without the cache accepted 0-RTT with no replay defence.
  * Stateless self-encrypted tickets do not carry a session ID on the
    stateless DoClientTicket decrypt path, so wolfSSL_SSL_CTX_remove_
    session could not locate them to evict.
  * wolfSSL_SSL_CTX_remove_session always returned 0 on success
    regardless of whether the session was actually in the cache,
    diverging from OpenSSL's SSL_CTX_remove_session (1 on success,
    0 on not-found).

Changes:
  * src/internal.c: ctx->maxEarlyDataSz defaults to 0; applications
    must opt in with wolfSSL_CTX_set_max_early_data.
  * src/tls13.c: #error when WOLFSSL_EARLY_DATA is built with
    HAVE_SESSION_TICKET and NO_SESSION_CACHE. Escape hatch
    WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY for deployments that take
    application-layer responsibility.
  * wolfssl/internal.h: imply WOLFSSL_TICKET_HAVE_ID from
    WOLFSSL_EARLY_DATA so stateless-ticket issuance populates the
    cache under an ID that eviction can find.
  * src/ssl_sess.c: wolfSSL_SSL_CTX_remove_session returns 1 when the
    session was found (internal-cache hit, or ctx->rem_sess_cb fired
    for an external cache), 0 otherwise. Matches OpenSSL semantics.
  * src/tls13.c: the 0-RTT acceptance condition in CheckPreSharedKeys
    now calls wolfSSL_SSL_CTX_remove_session and checks its return:
    the eviction is the check. If the session was in the cache, 0-RTT
    is accepted and the single-use requirement is satisfied. If not,
    the early_data extension is rejected through the normal path so
    the record layer correctly skips in-flight 0-RTT records.
    WOLFSSL_MSG at each rejection site.
  * doc/dox_comments/header_files/ssl.h: document runtime opt-in.
  * tests: four new tests —
    test_tls13_0rtt_default_off (fails without default-to-0 fix),
    test_tls13_0rtt_stateless_replay (fails without TICKET_HAVE_ID
    implication and remove_session gate),
    test_tls13_remove_session_return (fails without return-value fix),
    test_tls13_0rtt_ext_cache_eviction (fails without ext-cache
    counts-as-found fix).
    test_tls13_early_data explicitly opts in via
    wolfSSL_CTX_set_max_early_data.
    tests/api.c: two SSL_CTX_remove_session == 0 assertions updated
    to == 1.
This commit is contained in:
Juliusz Sosinowicz
2026-04-22 15:29:26 +02:00
parent 1c9555c121
commit b0fdaa2a6d
11 changed files with 354 additions and 30 deletions
+1
View File
@@ -750,6 +750,7 @@ WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS
WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT
WOLFSSL_DUMP_MEMIO_STREAM
WOLFSSL_DUP_CERTPOL
WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY
WOLFSSL_ECC_BLIND_K
WOLFSSL_ECC_GEN_REJECT_SAMPLING
WOLFSSL_ECC_NO_SMALL_STACK
+4
View File
@@ -14407,6 +14407,10 @@ wolfSSL_accept_TLSv13(WOLFSSL* ssl);
A server value of zero indicates no early data is to be sent by client using
session tickets. A client value of zero indicates that the client will
not send any early data.
The default value is zero: per RFC 8446 Appendix E.5, TLS implementations
"MUST NOT enable 0-RTT (either sending or accepting) unless specifically
requested by the application." Servers must call this function (or the
per-SSL equivalent) with a non-zero value to opt in.
It is recommended that the number of early data bytes be kept as low as
practically possible in the application.
+8 -6
View File
@@ -602,21 +602,23 @@ static void SetKeyShare(WOLFSSL* ssl, int onlyKeyShare, int useX25519,
#endif /* WOLFSSL_TLS13 && HAVE_SUPPORTED_CURVES */
#ifdef WOLFSSL_EARLY_DATA
static void EarlyData(WOLFSSL_CTX* ctx, WOLFSSL* ssl, const char* msg,
int msgSz, char* buffer)
static int EarlyData(WOLFSSL_CTX* ctx, WOLFSSL* ssl, const char* msg,
int msgSz, char* buffer)
{
int err;
int ret;
(void)ctx;
(void)buffer;
WOLFSSL_ASYNC_WHILE_PENDING(ret = wolfSSL_write_early_data(ssl, msg, msgSz, &msgSz),
ret <= 0);
if (ret != msgSz) {
err = wolfSSL_get_error(ssl, ret);
LOG_ERROR("SSL_write_early_data msg error %d, %s\n", err,
wolfSSL_ERR_error_string((unsigned long)err, buffer));
wolfSSL_free(ssl); ssl = NULL;
wolfSSL_CTX_free(ctx); ctx = NULL;
err_sys("SSL_write_early_data failed");
wolfSSL_ERR_error_string((unsigned long)err, buffer));
return -1;
}
return 0;
}
#endif
+4
View File
@@ -2848,6 +2848,10 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args)
err_sys_ex(catastrophic, "can't set minimum downgrade version");
}
#ifdef WOLFSSL_EARLY_DATA
if (earlyData)
wolfSSL_CTX_set_max_early_data(ctx, 4096);
#endif
#ifdef OPENSSL_COMPATIBLE_DEFAULTS
/* Restore wolfSSL verify defaults */
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_DEFAULT, NULL);
+3 -1
View File
@@ -2840,7 +2840,9 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap)
#endif
#ifdef WOLFSSL_EARLY_DATA
ctx->maxEarlyDataSz = MAX_EARLY_DATA_SZ;
/* RFC 8446 section E.5: 0-RTT off by default; opt in via
* wolfSSL_CTX_set_max_early_data(). */
ctx->maxEarlyDataSz = 0;
#endif
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
+9 -5
View File
@@ -3255,8 +3255,12 @@ static void SESSION_ex_data_cache_update(WOLFSSL_SESSION* session, int idx,
#endif
#ifndef NO_SESSION_CACHE
/* OpenSSL-compatible return: 1 if the session was found and removed from the
* internal cache, or if the external remove callback (rem_sess_cb) was
* invoked. 0 if neither applied (not present, or null arguments). */
int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
{
int found = 0;
#if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)
int rem_called = FALSE;
#endif
@@ -3265,7 +3269,7 @@ int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
s = ClientSessionToSession(s);
if (ctx == NULL || s == NULL)
return BAD_FUNC_ARG;
return 0;
#ifdef HAVE_EXT_CACHE
if (!ctx->internalCacheOff)
@@ -3282,6 +3286,7 @@ int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
ret = TlsSessionCacheGetAndWrLock(id, &sess, &row, ctx->method->side);
if (ret == 0 && sess != NULL) {
found = 1;
#if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)
if (sess->rem_sess_cb != NULL) {
rem_called = TRUE;
@@ -3320,13 +3325,12 @@ int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
#if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)
if (ctx->rem_sess_cb != NULL && !rem_called) {
ctx->rem_sess_cb(ctx, s);
/* Assume the external cache had the session. */
found = 1;
}
#endif
/* s cannot be resumed at this point */
s->timeout = 0;
return 0;
return found;
}
#if defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) \
+30 -14
View File
@@ -61,6 +61,7 @@
*
* 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
*
* TLS 1.3 Key Exchange:
@@ -81,6 +82,14 @@
#if !defined(NO_TLS) && defined(WOLFSSL_TLS13)
/* 0-RTT anti-replay eviction needs the session cache. */
#if defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET) && \
defined(NO_SESSION_CACHE) && !defined(NO_WOLFSSL_SERVER) && \
!defined(WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY)
#error "WOLFSSL_EARLY_DATA with tickets requires !NO_SESSION_CACHE, or " \
"define WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY to opt out."
#endif
#ifndef WOLFCRYPT_ONLY
#ifdef HAVE_ERRNO_H
@@ -5901,8 +5910,11 @@ static int DoTls13EncryptedExtensions(WOLFSSL* ssl, const byte* input,
#ifdef WOLFSSL_EARLY_DATA
if (ssl->earlyData != no_early_data) {
TLSX* ext = TLSX_Find(ssl->extensions, TLSX_EARLY_DATA);
if (ext == NULL || !ext->val)
if (ext == NULL || !ext->val) {
WOLFSSL_MSG("Early data rejected by server (no early_data "
"EncryptedExtensions response)");
ssl->earlyData = no_early_data;
}
}
if (ssl->earlyData == no_early_data) {
@@ -6377,18 +6389,6 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz,
/* This PSK works, no need to try any more. */
current->chosen = 1;
ext->resp = 1;
#if defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET) && \
!defined(NO_SESSION_CACHE)
/* RFC 8446 section 8: accept 0-RTT for a given handshake at most
* once. Evict the session from both the internal cache (under a
* write lock) and any external cache (via ctx->rem_sess_cb) so
* the same ClientHello cannot replay early data. Only when the
* client offered 0-RTT on a session that permits it. */
if (ssl->earlyData != no_early_data &&
ssl->session->maxEarlyDataSz != 0) {
(void)wolfSSL_SSL_CTX_remove_session(ssl->ctx, ssl->session);
}
#endif
break;
}
@@ -6549,8 +6549,16 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
* RFC 8773bis: early_data is not compatible with
* cert_with_extern_psk, so skip key derivation in that case. */
if (ssl->earlyData != no_early_data && first
&& ssl->options.maxEarlyDataSz > 0
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
&& !hasCertWithExternPsk
#endif
#if defined(HAVE_SESSION_TICKET) && !defined(NO_SESSION_CACHE)
/* RFC 8446 section 8: evict the session from the cache.
* Accept 0-RTT only when the eviction found the entry
* (single-use). */
&& wolfSSL_SSL_CTX_remove_session(ssl->ctx, ssl->session)
== 1
#endif
) {
extEarlyData->resp = 1;
@@ -6613,6 +6621,8 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
* combination in the ClientHello, but clear the response flag
* here as a defense-in-depth measure. */
if (extEarlyData != NULL) {
WOLFSSL_MSG("Rejecting early data: "
"cert_with_extern_psk is not 0-RTT compatible");
extEarlyData->resp = 0;
ssl->earlyData = no_early_data;
}
@@ -15388,7 +15398,8 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl)
#ifdef HAVE_SESSION_TICKET
#ifdef WOLFSSL_TLS13_TICKET_BEFORE_FINISHED
if (!ssl->options.verifyPeer && !ssl->options.noTicketTls13 &&
ssl->ctx->ticketEncCb != NULL) {
ssl->ctx->ticketEncCb != NULL &&
ssl->options.maxTicketTls13 > 0) {
if ((ssl->error = SendTls13NewSessionTicket(ssl)) != 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
@@ -15529,6 +15540,11 @@ int wolfSSL_send_SessionTicket(WOLFSSL* ssl)
* A value of zero indicates no early data is to be sent by client using session
* tickets.
*
* The default value is zero: per RFC 8446 Appendix E.5, TLS implementations
* "MUST NOT enable 0-RTT (either sending or accepting) unless specifically
* requested by the application." Servers must explicitly opt in by calling
* this function (or the per-SSL equivalent) with a non-zero value.
*
* ctx The SSL/TLS CTX object.
* sz Maximum size of the early data.
* returns BAD_FUNC_ARG when ctx is NULL, SIDE_ERROR when not a server and
+6 -4
View File
@@ -18689,8 +18689,9 @@ static int test_wolfSSL_CTX_sess_set_remove_cb(void)
/* Force a cache update */
ExpectNotNull(SSL_SESSION_set_ex_data(clientSess, serverSessRemIdx - 1, 0));
/* This should set the timeout to 0 and call the remove callback from within
* the session cache. */
ExpectIntEQ(SSL_CTX_remove_session(clientSessCtx, clientSess), 0);
* the session cache. Returns 1 per OpenSSL semantics (session was
* present in the cache and removed). */
ExpectIntEQ(SSL_CTX_remove_session(clientSessCtx, clientSess), 1);
ExpectNull(SSL_SESSION_get_ex_data(clientSess, serverSessRemIdx));
ExpectIntEQ(clientSessRemCountFree, 1);
#endif
@@ -18702,8 +18703,9 @@ static int test_wolfSSL_CTX_sess_set_remove_cb(void)
/* Force a cache update */
ExpectNotNull(SSL_SESSION_set_ex_data(serverSess, serverSessRemIdx - 1, 0));
/* This should set the timeout to 0 and call the remove callback from within
* the session cache. */
ExpectIntEQ(SSL_CTX_remove_session(serverSessCtx, serverSess), 0);
* the session cache. Returns 1 per OpenSSL semantics (session was
* present in the cache and removed). */
ExpectIntEQ(SSL_CTX_remove_session(serverSessCtx, serverSess), 1);
ExpectNull(SSL_SESSION_get_ex_data(serverSess, serverSessRemIdx));
ExpectIntEQ(serverSessRemCountFree, 1);
/* Need to free the references that we kept */
+275
View File
@@ -2831,6 +2831,11 @@ int test_tls13_early_data(void)
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c,
&ssl_s, params[i].client_meth, params[i].server_meth), 0);
/* Opt the server into 0-RTT (off by default per RFC 8446 E.5). */
ExpectIntGE(wolfSSL_CTX_set_max_early_data(ctx_s, MAX_EARLY_DATA_SZ),
0);
ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0);
if (params[i].isUdp) {
/* Early data is incompatible with HRR usage. Hence, we have to make
* sure a group is negotiated that does not cause a fragemented CH.
@@ -3203,6 +3208,276 @@ int test_tls13_early_data_0rtt_replay(void)
}
#endif
/* Verify that maxEarlyDataSz defaults to 0 (RFC 8446 E.5): a server that
* has not called wolfSSL_set_max_early_data must not advertise 0-RTT in its
* NewSessionTicket. Fails without the ctx->maxEarlyDataSz=0 default fix
* because the old default was MAX_EARLY_DATA_SZ (4096). */
int test_tls13_0rtt_default_off(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \
defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB)
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION *sess = NULL;
char buf[64];
int written = 0;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
/* Step 1: handshake WITHOUT opting into 0-RTT on the server. */
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method),
0);
/* Deliberately do NOT call wolfSSL_set_max_early_data. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
/* Consume NewSessionTicket. */
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));
wolfSSL_free(ssl_c); ssl_c = NULL;
wolfSSL_free(ssl_s); ssl_s = NULL;
/* Step 2: resume - early data write must fail because the ticket
* was issued without max_early_data_size. Without the default-to-0
* fix the old default (4096) would let this succeed. */
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_session(ssl_c, sess), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_write_early_data(ssl_c, "test", 4, &written),
WOLFSSL_FATAL_ERROR);
wolfSSL_SESSION_free(sess);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* Verify that a stateless self-encrypted ticket can carry 0-RTT exactly
* once: the first resumption succeeds with early data, the second (replay)
* refuses it because wolfSSL_SSL_CTX_remove_session evicted the cache entry.
* Fails without the WOLFSSL_TICKET_HAVE_ID implication + the
* remove_session-based gate because the old code either never populated
* the cache for stateless tickets or never checked the return value. */
int test_tls13_0rtt_stateless_replay(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \
defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TICKET_HAVE_ID) && \
!defined(NO_SESSION_CACHE) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB)
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION *sess = NULL;
char buf[64];
int round;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
/* Step 1: full handshake to get a stateless ticket with 0-RTT. */
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method),
0);
/* Do NOT set WOLFSSL_OP_NO_TICKET - keep stateless tickets. */
ExpectIntGE(wolfSSL_CTX_set_max_early_data(ctx_s, MAX_EARLY_DATA_SZ), 0);
ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));
ExpectIntEQ(wolfSSL_SessionIsSetup(sess), 1);
wolfSSL_free(ssl_c); ssl_c = NULL;
wolfSSL_free(ssl_s); ssl_s = NULL;
/* Suppress ticket reissuance on resume so the eviction from round 0
* is not undone by AddSession from a new NewSessionTicket. */
ExpectIntEQ(wolfSSL_CTX_set_num_tickets(ctx_s, 0), WOLFSSL_SUCCESS);
/* Step 2: resume twice. Round 0 = first use, round 1 = replay. */
for (round = 0; round < 2 && !EXPECT_FAIL(); round++) {
const char earlyMsg[] = "stateless-0rtt";
int written = 0;
int earlyRead = 0;
char earlyBuf[sizeof(earlyMsg)];
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
XMEMSET(earlyBuf, 0, sizeof(earlyBuf));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c,
&ssl_s, wolfTLSv1_3_client_method,
wolfTLSv1_3_server_method), 0);
ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0);
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);
ExpectIntEQ(test_tls13_early_data_write_until_write_ok(ssl_c,
earlyMsg, (int)sizeof(earlyMsg), &written),
sizeof(earlyMsg));
ExpectIntEQ(written, sizeof(earlyMsg));
(void)test_tls13_early_data_read_until_write_ok(ssl_s, earlyBuf,
sizeof(earlyBuf), &earlyRead);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
if (round == 0) {
/* First use: 0-RTT accepted. */
ExpectIntEQ(earlyRead, sizeof(earlyMsg));
ExpectStrEQ(earlyMsg, earlyBuf);
}
else {
/* Replay: 0-RTT refused, handshake still completes (1-RTT). */
ExpectIntEQ(earlyRead, 0);
}
wolfSSL_free(ssl_c); ssl_c = NULL;
wolfSSL_free(ssl_s); ssl_s = NULL;
}
wolfSSL_SESSION_free(sess);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* Verify wolfSSL_SSL_CTX_remove_session returns OpenSSL-compatible values:
* 1 when the session was in the cache and removed, 0 otherwise.
* Fails without the return-value fix because the old code returned 0/
* BAD_FUNC_ARG. */
int test_tls13_remove_session_return(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \
defined(WOLFSSL_TICKET_HAVE_ID) && !defined(NO_SESSION_CACHE) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB)
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION *sess = NULL;
char buf[64];
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(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
/* Consume NewSessionTicket so the cache is populated (AddSession fires
* because WOLFSSL_TICKET_HAVE_ID is defined). */
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
ExpectNotNull(sess = wolfSSL_get1_session(ssl_s));
/* Session is in the cache - first remove returns 1. */
ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_s, sess), 1);
/* Already removed - second remove returns 0. */
ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_s, sess), 0);
/* NULL args - returns 0 (not BAD_FUNC_ARG). */
ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(NULL, sess), 0);
ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_s, NULL), 0);
wolfSSL_SESSION_free(sess);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \
defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TICKET_HAVE_ID) && \
!defined(NO_SESSION_CACHE) && defined(HAVE_EXT_CACHE)
/* Thin external cache: only tracks rem_calls to verify that
* wolfSSL_SSL_CTX_remove_session counts the callback as "found". */
static int test_0rtt_ext_only_rem_calls;
static int test_0rtt_ext_only_new_cb(WOLFSSL* ssl, WOLFSSL_SESSION* s)
{
(void)ssl; (void)s;
return 0; /* don't retain */
}
static WOLFSSL_SESSION* test_0rtt_ext_only_get_cb(WOLFSSL* ssl,
const byte* id, int idLen, int* ref)
{
(void)ssl; (void)id; (void)idLen;
*ref = 0;
return NULL;
}
static void test_0rtt_ext_only_rem_cb(WOLFSSL_CTX* ctx, WOLFSSL_SESSION* s)
{
(void)ctx; (void)s;
test_0rtt_ext_only_rem_calls++;
}
#endif
/* Verify that when the internal cache is off but an external cache callback
* is registered, wolfSSL_SSL_CTX_remove_session returns 1 (the ext callback
* fired, so we assume the session was present). Fails without the fix
* because the old code only set found=1 on an internal-cache hit. */
int test_tls13_0rtt_ext_cache_eviction(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \
defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TICKET_HAVE_ID) && \
!defined(NO_SESSION_CACHE) && defined(HAVE_EXT_CACHE) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB)
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION *sess = NULL;
char buf[64];
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_0rtt_ext_only_rem_calls = 0;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method),
0);
/* Turn off internal cache; rely on external callbacks only. */
ExpectIntEQ(wolfSSL_CTX_set_session_cache_mode(ctx_s,
WOLFSSL_SESS_CACHE_NO_INTERNAL), WOLFSSL_SUCCESS);
wolfSSL_CTX_sess_set_new_cb(ctx_s, test_0rtt_ext_only_new_cb);
wolfSSL_CTX_sess_set_get_cb(ctx_s, test_0rtt_ext_only_get_cb);
wolfSSL_CTX_sess_set_remove_cb(ctx_s, test_0rtt_ext_only_rem_cb);
ExpectTrue(wolfSSL_set_options(ssl_s, WOLFSSL_OP_NO_TICKET) != 0);
ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
/* remove_session on an ext-cache-only server: rem_cb should fire and
* the function should return 1 (assumes the ext cache had it). */
ExpectNotNull(sess = wolfSSL_get1_session(ssl_s));
ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_s, sess), 1);
ExpectIntGT(test_0rtt_ext_only_rem_calls, 0);
wolfSSL_SESSION_free(sess);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* Check that the client won't send the same CH after a HRR. An HRR without
* a KeyShare or a Cookie extension will trigger the error. */
+8
View File
@@ -48,6 +48,10 @@ int test_tls13_pqc_hybrid_truncated_keyshare(void);
int test_tls13_empty_record_limit(void);
int test_tls13_short_session_ticket(void);
int test_tls13_early_data_0rtt_replay(void);
int test_tls13_0rtt_default_off(void);
int test_tls13_0rtt_stateless_replay(void);
int test_tls13_remove_session_return(void);
int test_tls13_0rtt_ext_cache_eviction(void);
int test_tls13_corrupted_finished(void);
int test_tls13_peerauth_failsafe(void);
int test_tls13_hrr_bad_cookie(void);
@@ -85,6 +89,10 @@ int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
TEST_DECL_GROUP("tls13", test_tls13_empty_record_limit), \
TEST_DECL_GROUP("tls13", test_tls13_short_session_ticket), \
TEST_DECL_GROUP("tls13", test_tls13_early_data_0rtt_replay), \
TEST_DECL_GROUP("tls13", test_tls13_0rtt_default_off), \
TEST_DECL_GROUP("tls13", test_tls13_0rtt_stateless_replay), \
TEST_DECL_GROUP("tls13", test_tls13_remove_session_return), \
TEST_DECL_GROUP("tls13", test_tls13_0rtt_ext_cache_eviction), \
TEST_DECL_GROUP("tls13", test_tls13_unknown_ext_rejected), \
TEST_DECL_GROUP("tls13", test_tls13_corrupted_finished), \
TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \
+6
View File
@@ -1288,6 +1288,12 @@ enum {
#define MAX_EARLY_DATA_SZ 4096
#endif
/* Anti-replay eviction keys off the ticket's session ID. */
#if defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET) && \
!defined(WOLFSSL_TICKET_HAVE_ID)
#define WOLFSSL_TICKET_HAVE_ID
#endif
#if !defined(NO_RSA) || !defined(NO_DH) || defined(HAVE_ECC)
/* MySQL wants to be able to use 8192-bit numbers. */