@@ -861,6 +861,106 @@ int test_tls12_etm_failed_resumption(void)
861861 return EXPECT_RESULT ();
862862}
863863
864+ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES ) && \
865+ !defined(WOLFSSL_NO_TLS12 ) && defined(HAVE_SNI ) && \
866+ defined(HAVE_SESSION_TICKET ) && !defined(NO_SESSION_CACHE )
867+ /* Accept-all SNI callback used by test_tls12_session_id_resumption_sni_mismatch.
868+ * Registering any sniRecvCb causes the server to keep the client-provided
869+ * SNI in ssl->extensions (see TLSX_SNI_Parse, "Forcing SSL object to store
870+ * SNI parameter"), which is what the binding code reads. */
871+ static int accept_any_sni_cb (WOLFSSL * ssl , int * ret , void * arg )
872+ {
873+ (void )ssl ; (void )ret ; (void )arg ;
874+ return 0 ; /* accept */
875+ }
876+ #endif
877+
878+ /* RFC 6066 Section 3 requires:
879+ * "A server that implements this extension MUST NOT accept the request to
880+ * resume the session if the server_name extension contains a different
881+ * name. Instead, it proceeds with a full handshake to establish a new
882+ * session."
883+ *
884+ * wolfSSL's SNI/ALPN ticket-binding hardening (see VerifyTicketBinding,
885+ * added in PR #10279) covers the session ticket path but short-circuits on
886+ * !ssl->options.useTicket, so it does not apply to the TLS 1.2 stateful
887+ * session-ID cache resumption path. SetupSession() does not store the
888+ * original SNI on the cached WOLFSSL_SESSION, and TlsSessionCacheGetAndLock()
889+ * keys only on (sessionID, sessionIDSz, side). The result is that a session
890+ * established under one SNI can be resumed under a different SNI via the
891+ * session-ID cache, in violation of the MUST NOT above.
892+ *
893+ * This test forces the session-ID resumption path (no tickets) and offers a
894+ * different SNI on the resumption attempt. The server must NOT resume. */
895+ int test_tls12_session_id_resumption_sni_mismatch (void )
896+ {
897+ EXPECT_DECLS ;
898+ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES ) && \
899+ !defined(WOLFSSL_NO_TLS12 ) && defined(HAVE_SNI ) && \
900+ defined(HAVE_SESSION_TICKET ) && !defined(NO_SESSION_CACHE )
901+ WOLFSSL_CTX * ctx_c = NULL , * ctx_s = NULL ;
902+ WOLFSSL * ssl_c = NULL , * ssl_s = NULL ;
903+ WOLFSSL_SESSION * sess = NULL ;
904+ struct test_memio_ctx test_ctx ;
905+ const char * sniA = "public.example" ;
906+ const char * sniB = "admin.example" ;
907+
908+ /* Step 1: full TLS 1.2 handshake under SNI=public.example, with the
909+ * session ticket path disabled so resumption can only happen via the
910+ * server's session-ID cache. The server-side SNI callback ensures
911+ * ssl->extensions retains the client's SNI in builds that don't
912+ * compile in WOLFSSL_ALWAYS_KEEP_SNI. */
913+ XMEMSET (& test_ctx , 0 , sizeof (test_ctx ));
914+ ExpectIntEQ (test_memio_setup (& test_ctx , & ctx_c , & ctx_s , & ssl_c , & ssl_s ,
915+ wolfTLSv1_2_client_method , wolfTLSv1_2_server_method ), 0 );
916+ wolfSSL_CTX_set_servername_callback (ctx_s , accept_any_sni_cb );
917+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_c ), WOLFSSL_SUCCESS );
918+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_s ), WOLFSSL_SUCCESS );
919+ ExpectIntEQ (wolfSSL_UseSNI (ssl_c , WOLFSSL_SNI_HOST_NAME ,
920+ sniA , (word16 )XSTRLEN (sniA )), WOLFSSL_SUCCESS );
921+ ExpectIntEQ (test_memio_do_handshake (ssl_c , ssl_s , 10 , NULL ), 0 );
922+ /* Sanity: the first handshake was not a resumption. */
923+ ExpectIntEQ (wolfSSL_session_reused (ssl_s ), 0 );
924+ ExpectNotNull (sess = wolfSSL_get1_session (ssl_c ));
925+
926+ wolfSSL_free (ssl_c ); ssl_c = NULL ;
927+ wolfSSL_free (ssl_s ); ssl_s = NULL ;
928+
929+ /* Step 2: new SSL objects on the SAME WOLFSSL_CTX (so the server's
930+ * session cache still holds the entry from step 1). The client offers
931+ * the saved session but advertises a *different* SNI. The server's
932+ * cache lookup will match by session ID, but per RFC 6066 Section 3 the
933+ * server MUST NOT resume because the SNI differs from the original. */
934+ XMEMSET (& test_ctx , 0 , sizeof (test_ctx ));
935+ ExpectNotNull (ssl_c = wolfSSL_new (ctx_c ));
936+ wolfSSL_SetIOReadCtx (ssl_c , & test_ctx );
937+ wolfSSL_SetIOWriteCtx (ssl_c , & test_ctx );
938+ ExpectNotNull (ssl_s = wolfSSL_new (ctx_s ));
939+ wolfSSL_SetIOReadCtx (ssl_s , & test_ctx );
940+ wolfSSL_SetIOWriteCtx (ssl_s , & test_ctx );
941+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_c ), WOLFSSL_SUCCESS );
942+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_s ), WOLFSSL_SUCCESS );
943+ ExpectIntEQ (wolfSSL_UseSNI (ssl_c , WOLFSSL_SNI_HOST_NAME ,
944+ sniB , (word16 )XSTRLEN (sniB )), WOLFSSL_SUCCESS );
945+ ExpectIntEQ (wolfSSL_set_session (ssl_c , sess ), WOLFSSL_SUCCESS );
946+ ExpectIntEQ (test_memio_do_handshake (ssl_c , ssl_s , 10 , NULL ), 0 );
947+
948+ /* Post-fix expected behavior: server falls back to a full handshake
949+ * because the SNI in the ClientHello does not match the SNI bound to
950+ * the cached session. Pre-fix, the server silently resumes - which is
951+ * the bug. Both sides should report no resumption. */
952+ ExpectIntEQ (wolfSSL_session_reused (ssl_s ), 0 );
953+ ExpectIntEQ (wolfSSL_session_reused (ssl_c ), 0 );
954+
955+ wolfSSL_SESSION_free (sess );
956+ wolfSSL_free (ssl_c );
957+ wolfSSL_free (ssl_s );
958+ wolfSSL_CTX_free (ctx_c );
959+ wolfSSL_CTX_free (ctx_s );
960+ #endif
961+ return EXPECT_RESULT ();
962+ }
963+
864964int test_tls_set_curves_list_ecc_fallback (void )
865965{
866966 EXPECT_DECLS ;
0 commit comments