@@ -730,6 +730,95 @@ int test_tls12_no_null_compression(void)
730730 * uppercase names like "SECP384R1" do not match the lowercase "secp384r1"
731731 * entry; they fall through to the wolfCrypt ECC look-up which uses
732732 * XSTRCASECMP. */
733+ /* Regression test for the encrypt-then-MAC silent-disable bug.
734+ *
735+ * Before the fix, when a client sent a 32-byte session ID in its ClientHello
736+ * (so the server set ssl->options.resuming = 1) but the server's session
737+ * cache did not contain that session, DoClientHello would run an
738+ * encrypt_then_mac decision *before* MatchSuite/SetCipherSpecs had populated
739+ * ssl->specs.cipher_type. Because cipher_type was zero-initialized
740+ * (== stream, not block), the ETM block cleared encThenMac to 0, and the
741+ * post-MatchSuite block could not re-enable it. The connection then
742+ * silently negotiated MAC-then-encrypt instead of encrypt-then-MAC.
743+ *
744+ * This test forces a stale-resumption ClientHello against a server with an
745+ * empty session cache, using a CBC-mode cipher suite, and asserts that the
746+ * server still negotiates encrypt-then-MAC. */
747+ int test_tls12_etm_failed_resumption (void )
748+ {
749+ EXPECT_DECLS ;
750+ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES ) && \
751+ !defined(WOLFSSL_NO_TLS12 ) && defined(HAVE_ENCRYPT_THEN_MAC ) && \
752+ !defined(WOLFSSL_AEAD_ONLY ) && !defined(NO_RSA ) && !defined(NO_AES ) && \
753+ defined(HAVE_AES_CBC ) && !defined(NO_SHA256 ) && \
754+ defined(HAVE_SESSION_TICKET ) && defined(HAVE_ECC )
755+ /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - a CBC suite, where ETM applies. */
756+ const char * cbcSuite = "ECDHE-RSA-AES128-SHA256" ;
757+ WOLFSSL_CTX * ctx_c = NULL , * ctx_s = NULL ;
758+ WOLFSSL * ssl_c = NULL , * ssl_s = NULL ;
759+ WOLFSSL_SESSION * sess = NULL ;
760+ struct test_memio_ctx test_ctx ;
761+
762+ /* First handshake: establish a session-ID-based session on the client.
763+ * Disable TLS 1.2 session tickets on both sides so resumption uses the
764+ * session ID path (not tickets), which is the path the bug lives on. */
765+ XMEMSET (& test_ctx , 0 , sizeof (test_ctx ));
766+ ExpectIntEQ (test_memio_setup (& test_ctx , & ctx_c , & ctx_s , & ssl_c , & ssl_s ,
767+ wolfTLSv1_2_client_method , wolfTLSv1_2_server_method ), 0 );
768+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_c ), WOLFSSL_SUCCESS );
769+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_s ), WOLFSSL_SUCCESS );
770+ ExpectIntEQ (wolfSSL_set_cipher_list (ssl_c , cbcSuite ), WOLFSSL_SUCCESS );
771+ ExpectIntEQ (wolfSSL_set_cipher_list (ssl_s , cbcSuite ), WOLFSSL_SUCCESS );
772+ ExpectIntEQ (test_memio_do_handshake (ssl_c , ssl_s , 10 , NULL ), 0 );
773+ /* Sanity: the first handshake itself must use ETM. */
774+ ExpectIntEQ (ssl_s -> options .encThenMac , 1 );
775+ ExpectNotNull (sess = wolfSSL_get1_session (ssl_c ));
776+
777+ wolfSSL_free (ssl_c ); ssl_c = NULL ;
778+ wolfSSL_free (ssl_s ); ssl_s = NULL ;
779+ wolfSSL_CTX_free (ctx_c ); ctx_c = NULL ;
780+ wolfSSL_CTX_free (ctx_s ); ctx_s = NULL ;
781+
782+ /* Second handshake against a *fresh* server context (empty cache). The
783+ * client offers the saved session, so the server's ClientHello parser
784+ * sets options.resuming = 1, but HandleTlsResumption then fails to find
785+ * the session and clears resuming. Pre-fix, ETM was silently dropped
786+ * here. */
787+ XMEMSET (& test_ctx , 0 , sizeof (test_ctx ));
788+ ExpectIntEQ (test_memio_setup (& test_ctx , & ctx_c , & ctx_s , & ssl_c , & ssl_s ,
789+ wolfTLSv1_2_client_method , wolfTLSv1_2_server_method ), 0 );
790+ /* The internal session cache is process-global, so the saved session is
791+ * still findable via the cache. Disable lookups on this server SSL
792+ * directly so that HandleTlsResumption hits its "session lookup failed"
793+ * path - exactly the scenario the bug fix targets. */
794+ if (ssl_s != NULL )
795+ ssl_s -> options .sessionCacheOff = 1 ;
796+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_c ), WOLFSSL_SUCCESS );
797+ ExpectIntEQ (wolfSSL_NoTicketTLSv12 (ssl_s ), WOLFSSL_SUCCESS );
798+ ExpectIntEQ (wolfSSL_set_cipher_list (ssl_c , cbcSuite ), WOLFSSL_SUCCESS );
799+ ExpectIntEQ (wolfSSL_set_cipher_list (ssl_s , cbcSuite ), WOLFSSL_SUCCESS );
800+ ExpectIntEQ (wolfSSL_set_session (ssl_c , sess ), WOLFSSL_SUCCESS );
801+ ExpectIntEQ (test_memio_do_handshake (ssl_c , ssl_s , 10 , NULL ), 0 );
802+ if (ssl_s != NULL ) {
803+ /* The server should NOT have actually resumed (fresh ctx, empty
804+ * cache). */
805+ ExpectIntEQ (ssl_s -> options .resuming , 0 );
806+ /* And - the regression check - encrypt-then-MAC must still be
807+ * active. */
808+ ExpectIntEQ (ssl_s -> options .encThenMac , 1 );
809+ }
810+ if (ssl_c != NULL )
811+ ExpectIntEQ (ssl_c -> options .encThenMac , 1 );
812+
813+ wolfSSL_SESSION_free (sess );
814+ wolfSSL_free (ssl_c );
815+ wolfSSL_free (ssl_s );
816+ wolfSSL_CTX_free (ctx_c );
817+ wolfSSL_CTX_free (ctx_s );
818+ #endif
819+ return EXPECT_RESULT ();
820+ }
821+
733822int test_tls_set_curves_list_ecc_fallback (void )
734823{
735824 EXPECT_DECLS ;
0 commit comments