mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 11:00:54 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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), \
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user