Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 89 additions & 22 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -38086,6 +38086,30 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
ssl->options.resuming = 0;
return ret;
}
#if defined(HAVE_SESSION_TICKET) && \
(defined(HAVE_SNI) || defined(HAVE_ALPN))
/* Do not resume session if sniHash/alpnHash do not match. */
if (!ssl->options.useTicket) {
Comment thread
julek-wolfssl marked this conversation as resolved.
byte curHash[TICKET_BINDING_HASH_SZ];
#ifdef HAVE_SNI
if (TicketSniHash(ssl, curHash) != 0 ||
XMEMCMP(curHash, session->sniHash,
TICKET_BINDING_HASH_SZ) != 0) {
WOLFSSL_MSG("Resumed session SNI mismatch, full handshake");
ssl->options.resuming = 0;
}
#endif
#ifdef HAVE_ALPN
if (ssl->options.resuming &&
(TicketAlpnHash(ssl, curHash) != 0 ||
XMEMCMP(curHash, session->alpnHash,
TICKET_BINDING_HASH_SZ) != 0)) {
WOLFSSL_MSG("Resumed session ALPN mismatch, full handshake");
ssl->options.resuming = 0;
}
#endif
}
#endif /* HAVE_SESSION_TICKET && (HAVE_SNI || HAVE_ALPN) */
#if !defined(WOLFSSL_NO_TICKET_EXPIRE) && !defined(NO_ASN_TIME)
/* check if the ticket is valid */
if (LowResTimer() > session->bornOn + ssl->timeout) {
Expand Down Expand Up @@ -38667,8 +38691,22 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
#endif
#if defined(HAVE_SESSION_TICKET) && \
(defined(HAVE_SNI) || defined(HAVE_ALPN))
if((ret=VerifyTicketBinding(ssl)))
goto out;
/* Only verify here for TLS 1.2 ticket-based resumption.
* For stateful (session-ID) resumption ssl->session is
* not loaded until HandleTlsResumption runs below, which
* performs its own binding check against the cached
* session. On mismatch decline the resumption (RFC 6066
* Section 3) but proceed with a full handshake; leave
* useTicket set so the server still issues a fresh
* ticket to the client. */
if (ssl->options.useTicket &&
VerifyTicketBinding(ssl) != 0) {
WOLFSSL_MSG("Ticket binding mismatch, "
"declining resumption and falling back "
"to full handshake");
ssl->options.resuming = 0;
ssl->options.peerAuthGood = 0;
}
#endif

i += totalExtSz;
Expand Down Expand Up @@ -39450,7 +39488,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)

#ifdef HAVE_SNI
/* Hash server-selected SNI; zeros dst when none. */
static int TicketSniHash(WOLFSSL* ssl, byte* dst)
int TicketSniHash(WOLFSSL* ssl, byte* dst)
{
char* name = NULL;
word16 nameLen;
Expand All @@ -39470,16 +39508,23 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)

#ifdef HAVE_ALPN
/* Hash negotiated ALPN; zeros dst when none. */
static int TicketAlpnHash(WOLFSSL* ssl, byte* dst)
int TicketAlpnHash(WOLFSSL* ssl, byte* dst)
{
char* proto = NULL;
word16 protoLen = 0;
TLSX* extension;
ALPN* alpn;

if (TLSX_ALPN_GetRequest(ssl->extensions, (void**)&proto,
&protoLen) == WOLFSSL_SUCCESS &&
proto != NULL && protoLen > 0) {
return wc_Hash(TICKET_BINDING_HASH_TYPE, (const byte*)proto,
protoLen, dst, TICKET_BINDING_HASH_SZ);
extension = TLSX_Find(ssl->extensions, TLSX_APPLICATION_LAYER_PROTOCOL);
if (extension != NULL) {
alpn = (ALPN*)extension->data;
if (alpn != NULL && alpn->negotiated == 1 &&
alpn->protocol_name != NULL) {
word32 protoLen = (word32)XSTRLEN(alpn->protocol_name);
if (protoLen > 0) {
return wc_Hash(TICKET_BINDING_HASH_TYPE,
(const byte*)alpn->protocol_name,
protoLen, dst, TICKET_BINDING_HASH_SZ);
}
}
}

XMEMSET(dst, 0, TICKET_BINDING_HASH_SZ);
Expand All @@ -39488,15 +39533,30 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
#endif

#if defined(HAVE_SNI) || defined(HAVE_ALPN)
/* Server-side: verify the SNI/ALPN bindings carried on a resumed
* session match what was negotiated for the current connection.
* Must be called after extension parsing and ALPN_Select.
* Returns 0 on match, WOLFSSL_FATAL_ERROR on mismatch. */
/* Server-side TLS 1.2 ticket-resumption binding check. Confirms the
* SNI/ALPN bound to the resumed session matches what was negotiated
* for the current connection. Must be called after extension
* parsing and ALPN_Select so the negotiated values are available,
* and only once DoClientTicketFinalize has populated
* ssl->session->sniHash/alpnHash from the decrypted ticket.
*
* Other resumption paths handle the same check themselves and do
* not use this function:
* - TLS 1.2 session-ID (stateful): HandleTlsResumption compares
* against the cached session at lookup time.
* - TLS 1.3 PSK: DoPreSharedKeys compares against each candidate
* ticket's bound hashes before committing, allowing the server
* to skip mismatching PSKs and pick the next one.
*
* Returns 0 on match, WOLFSSL_FATAL_ERROR on mismatch. The caller
* is responsible for the policy on mismatch -- RFC 6066 Section 3
* mandates declining the resumption and proceeding with a full
* handshake rather than aborting. */
int VerifyTicketBinding(WOLFSSL* ssl)
{
byte curHash[TICKET_BINDING_HASH_SZ];

if (!ssl->options.resuming || !ssl->options.useTicket)
if (!ssl->options.resuming)
return 0;

#ifdef HAVE_SNI
Expand Down Expand Up @@ -40005,8 +40065,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
ssl->sessionCtxSz) != 0))
return WOLFSSL_FATAL_ERROR;
#endif
/* SNI/ALPN binding is verified after ALPN_Select via
* VerifyTicketBinding(). */
/* SNI/ALPN binding is checked by the per-PSK loop in
* DoPreSharedKeys, not here, so that mismatching PSKs can be
* skipped in favor of the next candidate. */
return 0;
}
#endif /* WOLFSSL_SLT13 */
Expand Down Expand Up @@ -40102,8 +40163,13 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
}
}
#endif
/* Carry the ticket bindings on the session for the deferred
* VerifyTicketBinding() check. */
/* Carry the ticket bindings on the session. TLS 1.2 uses these
* for the deferred VerifyTicketBinding() check in DoClientHello
* (SNI/ALPN aren't known when DoClientTicket runs during
* extension parsing). TLS 1.3 checks bindings per-PSK before
* reaching this point, but still copies them so a subsequent
* SetupSession on a resumed session preserves them in the cache
* for future resumptions. */
#ifdef HAVE_SNI
XMEMCPY(ssl->session->sniHash, it->sniHash, TICKET_BINDING_HASH_SZ);
#endif
Expand Down Expand Up @@ -40469,8 +40535,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
goto cleanup;
}

/* SNI/ALPN binding is verified after ALPN_Select via
* VerifyTicketBinding(). */
/* SNI/ALPN binding is verified later in DoClientHello via
* VerifyTicketBinding(), once extension parsing and ALPN_Select
* have run and the negotiated values are available. */
DoClientTicketFinalize(ssl, it, NULL);

cleanup:
Expand Down
50 changes: 50 additions & 0 deletions src/ssl_sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -2715,6 +2715,16 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p)
#ifdef HAVE_SESSION_TICKET
/* ticket len | ticket */
size += OPAQUE16_LEN + sess->ticketLen;
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
#ifdef HAVE_SNI
/* sniHash */
size += TICKET_BINDING_HASH_SZ;
#endif
#ifdef HAVE_ALPN
/* alpnHash */
size += TICKET_BINDING_HASH_SZ;
#endif
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
#endif

if (p != NULL) {
Expand Down Expand Up @@ -2800,6 +2810,16 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p)
c16toa(sess->ticketLen, data + idx); idx += OPAQUE16_LEN;
XMEMCPY(data + idx, sess->ticket, sess->ticketLen);
idx += sess->ticketLen;
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
#ifdef HAVE_SNI
XMEMCPY(data + idx, sess->sniHash, TICKET_BINDING_HASH_SZ);
idx += TICKET_BINDING_HASH_SZ;
#endif
#ifdef HAVE_ALPN
XMEMCPY(data + idx, sess->alpnHash, TICKET_BINDING_HASH_SZ);
idx += TICKET_BINDING_HASH_SZ;
#endif
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
#endif
}
#endif
Expand Down Expand Up @@ -3086,6 +3106,26 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess,
goto end;
}
XMEMCPY(s->ticket, data + idx, s->ticketLen); idx += s->ticketLen;
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
#ifdef HAVE_SNI
/* sniHash - SNI binding for stateful resumption (RFC 6066 section 3) */
if (i - idx < TICKET_BINDING_HASH_SZ) {
ret = BUFFER_ERROR;
goto end;
}
XMEMCPY(s->sniHash, data + idx, TICKET_BINDING_HASH_SZ);
idx += TICKET_BINDING_HASH_SZ;
#endif
#ifdef HAVE_ALPN
/* alpnHash - ALPN binding for stateful resumption */
if (i - idx < TICKET_BINDING_HASH_SZ) {
ret = BUFFER_ERROR;
goto end;
}
XMEMCPY(s->alpnHash, data + idx, TICKET_BINDING_HASH_SZ);
idx += TICKET_BINDING_HASH_SZ;
#endif
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
#endif
(void)idx;

Expand Down Expand Up @@ -3664,6 +3704,16 @@ void SetupSession(WOLFSSL* ssl)
session->sessionCtxSz = ssl->sessionCtxSz;
}
#endif
#if defined(HAVE_SESSION_TICKET) && \
!defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
/* Bind the current SNI/ALPN to the session to verify on later resumption */
#ifdef HAVE_SNI
(void)TicketSniHash(ssl, session->sniHash);
#endif
#ifdef HAVE_ALPN
(void)TicketAlpnHash(ssl, session->alpnHash);
#endif
#endif /* HAVE_SESSION_TICKET && !NO_WOLFSSL_SERVER && !NO_TLS */
session->timeout = ssl->timeout;
#ifndef NO_ASN_TIME
session->bornOn = LowResTimer();
Expand Down
79 changes: 64 additions & 15 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -6429,6 +6429,37 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz,
}
#endif
ret = DoClientTicketCheck(ssl, current, ssl->timeout, suite);
#if defined(HAVE_SNI) || defined(HAVE_ALPN)
if (ret == 0) {
/* Decline this PSK if the SNI/ALPN bound to the ticket
* does not match the current connection. RFC 6066 Sect.
* 3 mandates this for SNI; wolfSSL applies the same
* policy to ALPN as defense in depth. Skipping the PSK
* (rather than aborting) lets the server try the next
* candidate or fall back to a full handshake naturally
* without unwinding committed PSK state. ALPN_Select
* has already run earlier in DoTls13ClientHello so the
* negotiated ALPN is available to TicketAlpnHash. */
byte curHash[TICKET_BINDING_HASH_SZ];
#ifdef HAVE_SNI
if (TicketSniHash(ssl, curHash) != 0 ||
XMEMCMP(curHash, current->it->sniHash,
TICKET_BINDING_HASH_SZ) != 0) {
WOLFSSL_MSG("Ticket SNI mismatch, skipping PSK");
ret = WOLFSSL_FATAL_ERROR;
}
#endif
#ifdef HAVE_ALPN
if (ret == 0 &&
(TicketAlpnHash(ssl, curHash) != 0 ||
XMEMCMP(curHash, current->it->alpnHash,
TICKET_BINDING_HASH_SZ) != 0)) {
WOLFSSL_MSG("Ticket ALPN mismatch, skipping PSK");
ret = WOLFSSL_FATAL_ERROR;
}
#endif
}
#endif
if (ret == 0)
DoClientTicketFinalize(ssl, current->it, current->sess);
if (current->sess_free_cb != NULL) {
Expand Down Expand Up @@ -6592,11 +6623,11 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
return ret;
}

/* Extensions pushed on stack/list and PSK must be last. */
if (ssl->extensions != ext) {
WOLFSSL_ERROR_VERBOSE(PSK_KEY_ERROR);
return PSK_KEY_ERROR;
}
/* Wire-order check that PSK was the last extension in ClientHello is
* performed in DoTls13ClientHello immediately after TLSX_Parse, since
* post-parse code (e.g. ALPN_Select via TLSX_SetALPN) may legitimately
* prepend new entries to ssl->extensions before this point and would
* otherwise trip a head-of-list check here. */

/* Assume we are going to resume with a pre-shared key. */
ssl->options.resuming = 1;
Expand Down Expand Up @@ -7562,6 +7593,25 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
goto exit_dch;
}

#if (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \
defined(HAVE_TLS_EXTENSIONS)
/* RFC 8446 Section 4.2.11: the pre_shared_key extension MUST be the
* last extension in the ClientHello. wolfSSL stores extensions in
* reverse wire order (TLSX_Push prepends), so a well-formed
* ClientHello with PSK leaves PSK at the head of ssl->extensions
* here, before any post-parse code (e.g. ALPN_Select) modifies the
* list. */
{
TLSX* pskExt = TLSX_Find(ssl->extensions, TLSX_PRE_SHARED_KEY);
if (pskExt != NULL && ssl->extensions != pskExt) {
WOLFSSL_MSG("pre_shared_key extension was not last in "
"ClientHello");
WOLFSSL_ERROR_VERBOSE(PSK_KEY_ERROR);
ERROR_OUT(PSK_KEY_ERROR, exit_dch);
}
}
#endif

#if defined(HAVE_ECH)
if (!ssl->options.echProcessingInner && echX != NULL &&
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
Expand Down Expand Up @@ -7671,6 +7721,15 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
}
#endif

#ifdef HAVE_ALPN
/* Select the ALPN protocol before PSK selection so that the
* selected value is available to the per-PSK SNI/ALPN binding check
* inside CheckPreSharedKeys/DoPreSharedKeys. ALPN_Select itself
* only inspects ssl->extensions and the app callback; it does not
* depend on any state set during PSK validation. */
if ((ret = ALPN_Select(ssl)) != 0)
goto exit_dch;
#endif
#if (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \
defined(HAVE_TLS_EXTENSIONS)
ret = CheckPreSharedKeys(ssl, input + args->begin, helloSz, ssl->clSuites,
Expand Down Expand Up @@ -7712,16 +7771,6 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
#endif
}

#ifdef HAVE_ALPN
/* With PSK and all other things validated, it's time to
* select the ALPN protocol, if so requested */
if ((ret = ALPN_Select(ssl)) != 0)
goto exit_dch;
#endif
#if defined(HAVE_SESSION_TICKET) && (defined(HAVE_SNI) || defined(HAVE_ALPN))
if ((ret = VerifyTicketBinding(ssl)) != 0)
goto exit_dch;
#endif
} /* case TLS_ASYNC_BEGIN */
FALL_THROUGH;

Expand Down
Loading
Loading