Skip to content

Commit 43fabc6

Browse files
Check SNI/ALPN in TLS 1.2 stateful session ID resumption
1 parent 7cf84dd commit 43fabc6

5 files changed

Lines changed: 187 additions & 10 deletions

File tree

src/internal.c

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38086,6 +38086,30 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3808638086
ssl->options.resuming = 0;
3808738087
return ret;
3808838088
}
38089+
#if defined(HAVE_SESSION_TICKET) && \
38090+
(defined(HAVE_SNI) || defined(HAVE_ALPN))
38091+
/* Do not resume session if sniHash/alpnHash do not match. */
38092+
if (!ssl->options.useTicket) {
38093+
byte curHash[TICKET_BINDING_HASH_SZ];
38094+
#ifdef HAVE_SNI
38095+
if (TicketSniHash(ssl, curHash) != 0 ||
38096+
XMEMCMP(curHash, session->sniHash,
38097+
TICKET_BINDING_HASH_SZ) != 0) {
38098+
WOLFSSL_MSG("Resumed session SNI mismatch, full handshake");
38099+
ssl->options.resuming = 0;
38100+
}
38101+
#endif
38102+
#ifdef HAVE_ALPN
38103+
if (ssl->options.resuming &&
38104+
(TicketAlpnHash(ssl, curHash) != 0 ||
38105+
XMEMCMP(curHash, session->alpnHash,
38106+
TICKET_BINDING_HASH_SZ) != 0)) {
38107+
WOLFSSL_MSG("Resumed session ALPN mismatch, full handshake");
38108+
ssl->options.resuming = 0;
38109+
}
38110+
#endif
38111+
}
38112+
#endif /* HAVE_SESSION_TICKET && (HAVE_SNI || HAVE_ALPN) */
3808938113
#if !defined(WOLFSSL_NO_TICKET_EXPIRE) && !defined(NO_ASN_TIME)
3809038114
/* check if the ticket is valid */
3809138115
if (LowResTimer() > session->bornOn + ssl->timeout) {
@@ -39450,7 +39474,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3945039474

3945139475
#ifdef HAVE_SNI
3945239476
/* Hash server-selected SNI; zeros dst when none. */
39453-
static int TicketSniHash(WOLFSSL* ssl, byte* dst)
39477+
int TicketSniHash(WOLFSSL* ssl, byte* dst)
3945439478
{
3945539479
char* name = NULL;
3945639480
word16 nameLen;
@@ -39470,16 +39494,23 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3947039494

3947139495
#ifdef HAVE_ALPN
3947239496
/* Hash negotiated ALPN; zeros dst when none. */
39473-
static int TicketAlpnHash(WOLFSSL* ssl, byte* dst)
39497+
int TicketAlpnHash(WOLFSSL* ssl, byte* dst)
3947439498
{
39475-
char* proto = NULL;
39476-
word16 protoLen = 0;
39477-
39478-
if (TLSX_ALPN_GetRequest(ssl->extensions, (void**)&proto,
39479-
&protoLen) == WOLFSSL_SUCCESS &&
39480-
proto != NULL && protoLen > 0) {
39481-
return wc_Hash(TICKET_BINDING_HASH_TYPE, (const byte*)proto,
39482-
protoLen, dst, TICKET_BINDING_HASH_SZ);
39499+
TLSX* extension;
39500+
ALPN* alpn;
39501+
39502+
extension = TLSX_Find(ssl->extensions, TLSX_APPLICATION_LAYER_PROTOCOL);
39503+
if (extension != NULL) {
39504+
alpn = (ALPN*)extension->data;
39505+
if (alpn != NULL && alpn->negotiated == 1 &&
39506+
alpn->protocol_name != NULL) {
39507+
word32 protoLen = (word32)XSTRLEN(alpn->protocol_name);
39508+
if (protoLen > 0) {
39509+
return wc_Hash(TICKET_BINDING_HASH_TYPE,
39510+
(const byte*)alpn->protocol_name,
39511+
protoLen, dst, TICKET_BINDING_HASH_SZ);
39512+
}
39513+
}
3948339514
}
3948439515

3948539516
XMEMSET(dst, 0, TICKET_BINDING_HASH_SZ);

src/ssl_sess.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,16 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p)
27152715
#ifdef HAVE_SESSION_TICKET
27162716
/* ticket len | ticket */
27172717
size += OPAQUE16_LEN + sess->ticketLen;
2718+
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
2719+
#ifdef HAVE_SNI
2720+
/* sniHash */
2721+
size += TICKET_BINDING_HASH_SZ;
2722+
#endif
2723+
#ifdef HAVE_ALPN
2724+
/* alpnHash */
2725+
size += TICKET_BINDING_HASH_SZ;
2726+
#endif
2727+
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
27182728
#endif
27192729

27202730
if (p != NULL) {
@@ -2800,6 +2810,16 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p)
28002810
c16toa(sess->ticketLen, data + idx); idx += OPAQUE16_LEN;
28012811
XMEMCPY(data + idx, sess->ticket, sess->ticketLen);
28022812
idx += sess->ticketLen;
2813+
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
2814+
#ifdef HAVE_SNI
2815+
XMEMCPY(data + idx, sess->sniHash, TICKET_BINDING_HASH_SZ);
2816+
idx += TICKET_BINDING_HASH_SZ;
2817+
#endif
2818+
#ifdef HAVE_ALPN
2819+
XMEMCPY(data + idx, sess->alpnHash, TICKET_BINDING_HASH_SZ);
2820+
idx += TICKET_BINDING_HASH_SZ;
2821+
#endif
2822+
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
28032823
#endif
28042824
}
28052825
#endif
@@ -3086,6 +3106,26 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess,
30863106
goto end;
30873107
}
30883108
XMEMCPY(s->ticket, data + idx, s->ticketLen); idx += s->ticketLen;
3109+
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
3110+
#ifdef HAVE_SNI
3111+
/* sniHash - SNI binding for stateful resumption (RFC 6066 section 3) */
3112+
if (i - idx < TICKET_BINDING_HASH_SZ) {
3113+
ret = BUFFER_ERROR;
3114+
goto end;
3115+
}
3116+
XMEMCPY(s->sniHash, data + idx, TICKET_BINDING_HASH_SZ);
3117+
idx += TICKET_BINDING_HASH_SZ;
3118+
#endif
3119+
#ifdef HAVE_ALPN
3120+
/* alpnHash - ALPN binding for stateful resumption */
3121+
if (i - idx < TICKET_BINDING_HASH_SZ) {
3122+
ret = BUFFER_ERROR;
3123+
goto end;
3124+
}
3125+
XMEMCPY(s->alpnHash, data + idx, TICKET_BINDING_HASH_SZ);
3126+
idx += TICKET_BINDING_HASH_SZ;
3127+
#endif
3128+
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
30893129
#endif
30903130
(void)idx;
30913131

@@ -3664,6 +3704,16 @@ void SetupSession(WOLFSSL* ssl)
36643704
session->sessionCtxSz = ssl->sessionCtxSz;
36653705
}
36663706
#endif
3707+
#if defined(HAVE_SESSION_TICKET) && \
3708+
!defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
3709+
/* Bind the current SNI/ALPN to the session to verify on later resumption */
3710+
#ifdef HAVE_SNI
3711+
(void)TicketSniHash(ssl, session->sniHash);
3712+
#endif
3713+
#ifdef HAVE_ALPN
3714+
(void)TicketAlpnHash(ssl, session->alpnHash);
3715+
#endif
3716+
#endif /* HAVE_SESSION_TICKET && !NO_WOLFSSL_SERVER && !NO_TLS */
36673717
session->timeout = ssl->timeout;
36683718
#ifndef NO_ASN_TIME
36693719
session->bornOn = LowResTimer();

tests/api/test_tls.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,88 @@ int test_tls_set_session_min_downgrade(void)
906906
return EXPECT_RESULT();
907907
}
908908

909+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
910+
!defined(WOLFSSL_NO_TLS12) && defined(HAVE_SNI) && \
911+
defined(HAVE_SESSION_TICKET) && !defined(NO_SESSION_CACHE)
912+
/* Accept-all SNI callback. */
913+
static int accept_any_sni_cb(WOLFSSL* ssl, int* ret, void* arg)
914+
{
915+
(void)ssl; (void)ret; (void)arg;
916+
return 0; /* accept */
917+
}
918+
#endif
919+
920+
/* TLS resumption must proceed with full handshake to establish new session if
921+
* SNI/ALPN does not match previously established session. */
922+
int test_tls12_session_id_resumption_sni_mismatch(void)
923+
{
924+
EXPECT_DECLS;
925+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
926+
!defined(WOLFSSL_NO_TLS12) && defined(HAVE_SNI) && \
927+
defined(HAVE_SESSION_TICKET) && !defined(NO_SESSION_CACHE)
928+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
929+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
930+
WOLFSSL_SESSION *sess = NULL;
931+
struct test_memio_ctx test_ctx;
932+
const char* sniA = "public.example";
933+
const char* sniB = "admin.example";
934+
935+
/* Step 1: full TLS 1.2 handshake under SNI=public.example, with the
936+
* session ticket path disabled so resumption can only happen via the
937+
* server's session-ID cache. The server-side SNI callback ensures
938+
* ssl->extensions retains the client's SNI in builds that don't
939+
* compile in WOLFSSL_ALWAYS_KEEP_SNI. */
940+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
941+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
942+
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
943+
wolfSSL_CTX_set_servername_callback(ctx_s, accept_any_sni_cb);
944+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_c), WOLFSSL_SUCCESS);
945+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_s), WOLFSSL_SUCCESS);
946+
ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME,
947+
sniA, (word16)XSTRLEN(sniA)), WOLFSSL_SUCCESS);
948+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
949+
/* Sanity: the first handshake was not a resumption. */
950+
ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0);
951+
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));
952+
953+
wolfSSL_free(ssl_c); ssl_c = NULL;
954+
wolfSSL_free(ssl_s); ssl_s = NULL;
955+
956+
/* Step 2: new SSL objects on the SAME WOLFSSL_CTX (so the server's
957+
* session cache still holds the entry from step 1). The client offers
958+
* the saved session but advertises a *different* SNI. The server's
959+
* cache lookup will match by session ID, but per RFC 6066 Section 3 the
960+
* server MUST NOT resume because the SNI differs from the original. */
961+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
962+
ExpectNotNull(ssl_c = wolfSSL_new(ctx_c));
963+
wolfSSL_SetIOReadCtx(ssl_c, &test_ctx);
964+
wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx);
965+
ExpectNotNull(ssl_s = wolfSSL_new(ctx_s));
966+
wolfSSL_SetIOReadCtx(ssl_s, &test_ctx);
967+
wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx);
968+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_c), WOLFSSL_SUCCESS);
969+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_s), WOLFSSL_SUCCESS);
970+
ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME,
971+
sniB, (word16)XSTRLEN(sniB)), WOLFSSL_SUCCESS);
972+
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);
973+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
974+
975+
/* Post-fix expected behavior: server falls back to a full handshake
976+
* because the SNI in the ClientHello does not match the SNI bound to
977+
* the cached session. Pre-fix, the server silently resumes - which is
978+
* the bug. Both sides should report no resumption. */
979+
ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0);
980+
ExpectIntEQ(wolfSSL_session_reused(ssl_c), 0);
981+
982+
wolfSSL_SESSION_free(sess);
983+
wolfSSL_free(ssl_c);
984+
wolfSSL_free(ssl_s);
985+
wolfSSL_CTX_free(ctx_c);
986+
wolfSSL_CTX_free(ctx_s);
987+
#endif
988+
return EXPECT_RESULT();
989+
}
990+
909991
int test_tls_set_curves_list_ecc_fallback(void)
910992
{
911993
EXPECT_DECLS;

tests/api/test_tls.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ int test_tls12_bad_cv_sig_alg(void);
3333
int test_tls12_no_null_compression(void);
3434
int test_tls12_etm_failed_resumption(void);
3535
int test_tls_set_session_min_downgrade(void);
36+
int test_tls12_session_id_resumption_sni_mismatch(void);
3637
int test_tls_set_curves_list_ecc_fallback(void);
3738
int test_tls12_corrupted_finished(void);
3839
int test_tls12_peerauth_failsafe(void);
@@ -51,6 +52,7 @@ int test_wolfSSL_alert_desc_string(void);
5152
TEST_DECL_GROUP("tls", test_tls12_no_null_compression), \
5253
TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \
5354
TEST_DECL_GROUP("tls", test_tls_set_session_min_downgrade), \
55+
TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_sni_mismatch), \
5456
TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback), \
5557
TEST_DECL_GROUP("tls", test_tls12_corrupted_finished), \
5658
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe), \

wolfssl/internal.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6824,9 +6824,21 @@ WOLFSSL_LOCAL int DoClientTicket_ex(const WOLFSSL* ssl, PreSharedKey* psk,
68246824
#endif
68256825

68266826
WOLFSSL_LOCAL int DoClientTicket(WOLFSSL* ssl, const byte* input, word32 len);
6827+
/* TicketSniHash, TicketAlpnHash, and VerifyTicketBinding are defined in
6828+
* internal.c only when !NO_WOLFSSL_SERVER && !NO_TLS - gate the
6829+
* declarations to match so client-only or no-TLS builds don't compile in
6830+
* call sites that would fail to link. */
6831+
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
6832+
#ifdef HAVE_SNI
6833+
WOLFSSL_LOCAL int TicketSniHash(WOLFSSL* ssl, byte* dst);
6834+
#endif
6835+
#ifdef HAVE_ALPN
6836+
WOLFSSL_LOCAL int TicketAlpnHash(WOLFSSL* ssl, byte* dst);
6837+
#endif
68276838
#if defined(HAVE_SNI) || defined(HAVE_ALPN)
68286839
WOLFSSL_LOCAL int VerifyTicketBinding(WOLFSSL* ssl);
68296840
#endif
6841+
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
68306842
#endif /* HAVE_SESSION_TICKET */
68316843
WOLFSSL_LOCAL int SendData(WOLFSSL* ssl, const void* data, size_t sz);
68326844
#ifdef WOLFSSL_THREADED_CRYPT

0 commit comments

Comments
 (0)