Skip to content

Commit a05745b

Browse files
Check SNI/ALPN in TLS 1.2 stateful session ID resumption
1 parent 9096bcc commit a05745b

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
@@ -38156,6 +38156,30 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3815638156
ssl->options.resuming = 0;
3815738157
return ret;
3815838158
}
38159+
#if defined(HAVE_SESSION_TICKET) && \
38160+
(defined(HAVE_SNI) || defined(HAVE_ALPN))
38161+
/* Do not resume session if sniHash/alpnHash do not match. */
38162+
if (!ssl->options.useTicket) {
38163+
byte curHash[TICKET_BINDING_HASH_SZ];
38164+
#ifdef HAVE_SNI
38165+
if (TicketSniHash(ssl, curHash) != 0 ||
38166+
XMEMCMP(curHash, session->sniHash,
38167+
TICKET_BINDING_HASH_SZ) != 0) {
38168+
WOLFSSL_MSG("Resumed session SNI mismatch, full handshake");
38169+
ssl->options.resuming = 0;
38170+
}
38171+
#endif
38172+
#ifdef HAVE_ALPN
38173+
if (ssl->options.resuming &&
38174+
(TicketAlpnHash(ssl, curHash) != 0 ||
38175+
XMEMCMP(curHash, session->alpnHash,
38176+
TICKET_BINDING_HASH_SZ) != 0)) {
38177+
WOLFSSL_MSG("Resumed session ALPN mismatch, full handshake");
38178+
ssl->options.resuming = 0;
38179+
}
38180+
#endif
38181+
}
38182+
#endif /* HAVE_SESSION_TICKET && (HAVE_SNI || HAVE_ALPN) */
3815938183
#if !defined(WOLFSSL_NO_TICKET_EXPIRE) && !defined(NO_ASN_TIME)
3816038184
/* check if the ticket is valid */
3816138185
if (LowResTimer() > session->bornOn + ssl->timeout) {
@@ -39523,7 +39547,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3952339547

3952439548
#ifdef HAVE_SNI
3952539549
/* Hash server-selected SNI; zeros dst when none. */
39526-
static int TicketSniHash(WOLFSSL* ssl, byte* dst)
39550+
int TicketSniHash(WOLFSSL* ssl, byte* dst)
3952739551
{
3952839552
char* name = NULL;
3952939553
word16 nameLen;
@@ -39543,16 +39567,23 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3954339567

3954439568
#ifdef HAVE_ALPN
3954539569
/* Hash negotiated ALPN; zeros dst when none. */
39546-
static int TicketAlpnHash(WOLFSSL* ssl, byte* dst)
39570+
int TicketAlpnHash(WOLFSSL* ssl, byte* dst)
3954739571
{
39548-
char* proto = NULL;
39549-
word16 protoLen = 0;
39550-
39551-
if (TLSX_ALPN_GetRequest(ssl->extensions, (void**)&proto,
39552-
&protoLen) == WOLFSSL_SUCCESS &&
39553-
proto != NULL && protoLen > 0) {
39554-
return wc_Hash(TICKET_BINDING_HASH_TYPE, (const byte*)proto,
39555-
protoLen, dst, TICKET_BINDING_HASH_SZ);
39572+
TLSX* extension;
39573+
ALPN* alpn;
39574+
39575+
extension = TLSX_Find(ssl->extensions, TLSX_APPLICATION_LAYER_PROTOCOL);
39576+
if (extension != NULL) {
39577+
alpn = (ALPN*)extension->data;
39578+
if (alpn != NULL && alpn->negotiated == 1 &&
39579+
alpn->protocol_name != NULL) {
39580+
word32 protoLen = (word32)XSTRLEN(alpn->protocol_name);
39581+
if (protoLen > 0) {
39582+
return wc_Hash(TICKET_BINDING_HASH_TYPE,
39583+
(const byte*)alpn->protocol_name,
39584+
protoLen, dst, TICKET_BINDING_HASH_SZ);
39585+
}
39586+
}
3955639587
}
3955739588

3955839589
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
@@ -905,6 +905,88 @@ int test_tls_set_session_min_downgrade(void)
905905
return EXPECT_RESULT();
906906
}
907907

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

68236823
WOLFSSL_LOCAL int DoClientTicket(WOLFSSL* ssl, const byte* input, word32 len);
6824+
/* TicketSniHash, TicketAlpnHash, and VerifyTicketBinding are defined in
6825+
* internal.c only when !NO_WOLFSSL_SERVER && !NO_TLS - gate the
6826+
* declarations to match so client-only or no-TLS builds don't compile in
6827+
* call sites that would fail to link. */
6828+
#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS)
6829+
#ifdef HAVE_SNI
6830+
WOLFSSL_LOCAL int TicketSniHash(WOLFSSL* ssl, byte* dst);
6831+
#endif
6832+
#ifdef HAVE_ALPN
6833+
WOLFSSL_LOCAL int TicketAlpnHash(WOLFSSL* ssl, byte* dst);
6834+
#endif
68246835
#if defined(HAVE_SNI) || defined(HAVE_ALPN)
68256836
WOLFSSL_LOCAL int VerifyTicketBinding(WOLFSSL* ssl);
68266837
#endif
6838+
#endif /* !NO_WOLFSSL_SERVER && !NO_TLS */
68276839
#endif /* HAVE_SESSION_TICKET */
68286840
WOLFSSL_LOCAL int SendData(WOLFSSL* ssl, const void* data, size_t sz);
68296841
#ifdef WOLFSSL_THREADED_CRYPT

0 commit comments

Comments
 (0)