Skip to content

Commit e328585

Browse files
authored
Merge pull request #10289 from julek-wolfssl/zd/21652
TLS 1.3: gate 0-RTT on a cache-backed resumption ticket
2 parents 5f1e0d0 + b0fdaa2 commit e328585

11 files changed

Lines changed: 354 additions & 30 deletions

File tree

.wolfssl_known_macro_extras

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS
754754
WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT
755755
WOLFSSL_DUMP_MEMIO_STREAM
756756
WOLFSSL_DUP_CERTPOL
757+
WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY
757758
WOLFSSL_ECC_BLIND_K
758759
WOLFSSL_ECC_GEN_REJECT_SAMPLING
759760
WOLFSSL_ECC_NO_SMALL_STACK

doc/dox_comments/header_files/ssl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14454,6 +14454,10 @@ wolfSSL_accept_TLSv13(WOLFSSL* ssl);
1445414454
A server value of zero indicates no early data is to be sent by client using
1445514455
session tickets. A client value of zero indicates that the client will
1445614456
not send any early data.
14457+
The default value is zero: per RFC 8446 Appendix E.5, TLS implementations
14458+
"MUST NOT enable 0-RTT (either sending or accepting) unless specifically
14459+
requested by the application." Servers must call this function (or the
14460+
per-SSL equivalent) with a non-zero value to opt in.
1445714461
It is recommended that the number of early data bytes be kept as low as
1445814462
practically possible in the application.
1445914463

examples/client/client.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -602,21 +602,23 @@ static void SetKeyShare(WOLFSSL* ssl, int onlyKeyShare, int useX25519,
602602
#endif /* WOLFSSL_TLS13 && HAVE_SUPPORTED_CURVES */
603603

604604
#ifdef WOLFSSL_EARLY_DATA
605-
static void EarlyData(WOLFSSL_CTX* ctx, WOLFSSL* ssl, const char* msg,
606-
int msgSz, char* buffer)
605+
static int EarlyData(WOLFSSL_CTX* ctx, WOLFSSL* ssl, const char* msg,
606+
int msgSz, char* buffer)
607607
{
608608
int err;
609609
int ret;
610610

611+
(void)ctx;
612+
(void)buffer;
611613
WOLFSSL_ASYNC_WHILE_PENDING(ret = wolfSSL_write_early_data(ssl, msg, msgSz, &msgSz),
612614
ret <= 0);
613615
if (ret != msgSz) {
616+
err = wolfSSL_get_error(ssl, ret);
614617
LOG_ERROR("SSL_write_early_data msg error %d, %s\n", err,
615-
wolfSSL_ERR_error_string((unsigned long)err, buffer));
616-
wolfSSL_free(ssl); ssl = NULL;
617-
wolfSSL_CTX_free(ctx); ctx = NULL;
618-
err_sys("SSL_write_early_data failed");
618+
wolfSSL_ERR_error_string((unsigned long)err, buffer));
619+
return -1;
619620
}
621+
return 0;
620622
}
621623
#endif
622624

examples/server/server.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,6 +2848,10 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args)
28482848
err_sys_ex(catastrophic, "can't set minimum downgrade version");
28492849
}
28502850

2851+
#ifdef WOLFSSL_EARLY_DATA
2852+
if (earlyData)
2853+
wolfSSL_CTX_set_max_early_data(ctx, 4096);
2854+
#endif
28512855
#ifdef OPENSSL_COMPATIBLE_DEFAULTS
28522856
/* Restore wolfSSL verify defaults */
28532857
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_DEFAULT, NULL);

src/internal.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2842,7 +2842,9 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap)
28422842
#endif
28432843

28442844
#ifdef WOLFSSL_EARLY_DATA
2845-
ctx->maxEarlyDataSz = MAX_EARLY_DATA_SZ;
2845+
/* RFC 8446 section E.5: 0-RTT off by default; opt in via
2846+
* wolfSSL_CTX_set_max_early_data(). */
2847+
ctx->maxEarlyDataSz = 0;
28462848
#endif
28472849

28482850
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)

src/ssl_sess.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3255,8 +3255,12 @@ static void SESSION_ex_data_cache_update(WOLFSSL_SESSION* session, int idx,
32553255
#endif
32563256

32573257
#ifndef NO_SESSION_CACHE
3258+
/* OpenSSL-compatible return: 1 if the session was found and removed from the
3259+
* internal cache, or if the external remove callback (rem_sess_cb) was
3260+
* invoked. 0 if neither applied (not present, or null arguments). */
32583261
int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
32593262
{
3263+
int found = 0;
32603264
#if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)
32613265
int rem_called = FALSE;
32623266
#endif
@@ -3265,7 +3269,7 @@ int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
32653269

32663270
s = ClientSessionToSession(s);
32673271
if (ctx == NULL || s == NULL)
3268-
return BAD_FUNC_ARG;
3272+
return 0;
32693273

32703274
#ifdef HAVE_EXT_CACHE
32713275
if (!ctx->internalCacheOff)
@@ -3282,6 +3286,7 @@ int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
32823286

32833287
ret = TlsSessionCacheGetAndWrLock(id, &sess, &row, ctx->method->side);
32843288
if (ret == 0 && sess != NULL) {
3289+
found = 1;
32853290
#if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)
32863291
if (sess->rem_sess_cb != NULL) {
32873292
rem_called = TRUE;
@@ -3320,13 +3325,12 @@ int wolfSSL_SSL_CTX_remove_session(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *s)
33203325
#if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA)
33213326
if (ctx->rem_sess_cb != NULL && !rem_called) {
33223327
ctx->rem_sess_cb(ctx, s);
3328+
/* Assume the external cache had the session. */
3329+
found = 1;
33233330
}
33243331
#endif
33253332

3326-
/* s cannot be resumed at this point */
3327-
s->timeout = 0;
3328-
3329-
return 0;
3333+
return found;
33303334
}
33313335

33323336
#if defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) \

src/tls13.c

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
*
6262
* TLS 1.3 Session Tickets:
6363
* WOLFSSL_TICKET_HAVE_ID: Session tickets include ID default: off
64+
* Forced on when WOLFSSL_EARLY_DATA is set.
6465
* WOLFSSL_TICKET_NONCE_MALLOC: Dynamically allocate ticket nonce default: off
6566
*
6667
* TLS 1.3 Key Exchange:
@@ -81,6 +82,14 @@
8182

8283
#if !defined(NO_TLS) && defined(WOLFSSL_TLS13)
8384

85+
/* 0-RTT anti-replay eviction needs the session cache. */
86+
#if defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET) && \
87+
defined(NO_SESSION_CACHE) && !defined(NO_WOLFSSL_SERVER) && \
88+
!defined(WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY)
89+
#error "WOLFSSL_EARLY_DATA with tickets requires !NO_SESSION_CACHE, or " \
90+
"define WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY to opt out."
91+
#endif
92+
8493
#ifndef WOLFCRYPT_ONLY
8594

8695
#ifdef HAVE_ERRNO_H
@@ -5942,8 +5951,11 @@ static int DoTls13EncryptedExtensions(WOLFSSL* ssl, const byte* input,
59425951
#ifdef WOLFSSL_EARLY_DATA
59435952
if (ssl->earlyData != no_early_data) {
59445953
TLSX* ext = TLSX_Find(ssl->extensions, TLSX_EARLY_DATA);
5945-
if (ext == NULL || !ext->val)
5954+
if (ext == NULL || !ext->val) {
5955+
WOLFSSL_MSG("Early data rejected by server (no early_data "
5956+
"EncryptedExtensions response)");
59465957
ssl->earlyData = no_early_data;
5958+
}
59475959
}
59485960

59495961
if (ssl->earlyData == no_early_data) {
@@ -6427,18 +6439,6 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz,
64276439
/* This PSK works, no need to try any more. */
64286440
current->chosen = 1;
64296441
ext->resp = 1;
6430-
#if defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET) && \
6431-
!defined(NO_SESSION_CACHE)
6432-
/* RFC 8446 section 8: accept 0-RTT for a given handshake at most
6433-
* once. Evict the session from both the internal cache (under a
6434-
* write lock) and any external cache (via ctx->rem_sess_cb) so
6435-
* the same ClientHello cannot replay early data. Only when the
6436-
* client offered 0-RTT on a session that permits it. */
6437-
if (ssl->earlyData != no_early_data &&
6438-
ssl->session->maxEarlyDataSz != 0) {
6439-
(void)wolfSSL_SSL_CTX_remove_session(ssl->ctx, ssl->session);
6440-
}
6441-
#endif
64426442
break;
64436443
}
64446444

@@ -6599,8 +6599,16 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
65996599
* RFC 8773bis: early_data is not compatible with
66006600
* cert_with_extern_psk, so skip key derivation in that case. */
66016601
if (ssl->earlyData != no_early_data && first
6602+
&& ssl->options.maxEarlyDataSz > 0
66026603
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
66036604
&& !hasCertWithExternPsk
6605+
#endif
6606+
#if defined(HAVE_SESSION_TICKET) && !defined(NO_SESSION_CACHE)
6607+
/* RFC 8446 section 8: evict the session from the cache.
6608+
* Accept 0-RTT only when the eviction found the entry
6609+
* (single-use). */
6610+
&& wolfSSL_SSL_CTX_remove_session(ssl->ctx, ssl->session)
6611+
== 1
66046612
#endif
66056613
) {
66066614
extEarlyData->resp = 1;
@@ -6663,6 +6671,8 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
66636671
* combination in the ClientHello, but clear the response flag
66646672
* here as a defense-in-depth measure. */
66656673
if (extEarlyData != NULL) {
6674+
WOLFSSL_MSG("Rejecting early data: "
6675+
"cert_with_extern_psk is not 0-RTT compatible");
66666676
extEarlyData->resp = 0;
66676677
ssl->earlyData = no_early_data;
66686678
}
@@ -15493,7 +15503,8 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl)
1549315503
#ifdef HAVE_SESSION_TICKET
1549415504
#ifdef WOLFSSL_TLS13_TICKET_BEFORE_FINISHED
1549515505
if (!ssl->options.verifyPeer && !ssl->options.noTicketTls13 &&
15496-
ssl->ctx->ticketEncCb != NULL) {
15506+
ssl->ctx->ticketEncCb != NULL &&
15507+
ssl->options.maxTicketTls13 > 0) {
1549715508
if ((ssl->error = SendTls13NewSessionTicket(ssl)) != 0) {
1549815509
WOLFSSL_ERROR(ssl->error);
1549915510
return WOLFSSL_FATAL_ERROR;
@@ -15634,6 +15645,11 @@ int wolfSSL_send_SessionTicket(WOLFSSL* ssl)
1563415645
* A value of zero indicates no early data is to be sent by client using session
1563515646
* tickets.
1563615647
*
15648+
* The default value is zero: per RFC 8446 Appendix E.5, TLS implementations
15649+
* "MUST NOT enable 0-RTT (either sending or accepting) unless specifically
15650+
* requested by the application." Servers must explicitly opt in by calling
15651+
* this function (or the per-SSL equivalent) with a non-zero value.
15652+
*
1563715653
* ctx The SSL/TLS CTX object.
1563815654
* sz Maximum size of the early data.
1563915655
* returns BAD_FUNC_ARG when ctx is NULL, SIDE_ERROR when not a server and

tests/api.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19613,8 +19613,9 @@ static int test_wolfSSL_CTX_sess_set_remove_cb(void)
1961319613
/* Force a cache update */
1961419614
ExpectNotNull(SSL_SESSION_set_ex_data(clientSess, serverSessRemIdx - 1, 0));
1961519615
/* This should set the timeout to 0 and call the remove callback from within
19616-
* the session cache. */
19617-
ExpectIntEQ(SSL_CTX_remove_session(clientSessCtx, clientSess), 0);
19616+
* the session cache. Returns 1 per OpenSSL semantics (session was
19617+
* present in the cache and removed). */
19618+
ExpectIntEQ(SSL_CTX_remove_session(clientSessCtx, clientSess), 1);
1961819619
ExpectNull(SSL_SESSION_get_ex_data(clientSess, serverSessRemIdx));
1961919620
ExpectIntEQ(clientSessRemCountFree, 1);
1962019621
#endif
@@ -19626,8 +19627,9 @@ static int test_wolfSSL_CTX_sess_set_remove_cb(void)
1962619627
/* Force a cache update */
1962719628
ExpectNotNull(SSL_SESSION_set_ex_data(serverSess, serverSessRemIdx - 1, 0));
1962819629
/* This should set the timeout to 0 and call the remove callback from within
19629-
* the session cache. */
19630-
ExpectIntEQ(SSL_CTX_remove_session(serverSessCtx, serverSess), 0);
19630+
* the session cache. Returns 1 per OpenSSL semantics (session was
19631+
* present in the cache and removed). */
19632+
ExpectIntEQ(SSL_CTX_remove_session(serverSessCtx, serverSess), 1);
1963119633
ExpectNull(SSL_SESSION_get_ex_data(serverSess, serverSessRemIdx));
1963219634
ExpectIntEQ(serverSessRemCountFree, 1);
1963319635
/* Need to free the references that we kept */

0 commit comments

Comments
 (0)