@@ -30876,6 +30876,130 @@ static int test_wrong_cs_downgrade(void)
3087630876}
3087730877#endif
3087830878
30879+ #if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES)
30880+
30881+ /* A syntactically valid TLS 1.2 ServerHello with NO extensions, sent to a
30882+ * TLS 1.3-only client. Per RFC 8446 4.2.1 the absence of a supported_versions
30883+ * extension means the server negotiated TLS 1.2 or below. A TLS 1.3-only
30884+ * client (downgrade disabled) must reject this as a version mismatch with a
30885+ * protocol_version alert (RFC 8446 6.2), NOT a decode_error - the message is
30886+ * well-formed, it just offers an unsupported protocol version.
30887+ *
30888+ * Layout: legacy_version = 0x0303, 32-byte random, 32-byte session id,
30889+ * cipher_suite = TLS_AES_128_GCM_SHA256 (0x1301), null compression, and no
30890+ * extensions field at all. */
30891+ static const byte test_tls13_no_ext_sh[] = {
30892+ 0x16, 0x03, 0x03, 0x00, 0x4a, /* record: handshake, TLS 1.2, len 74 */
30893+ 0x02, 0x00, 0x00, 0x46, /* server_hello, body len 70 */
30894+ 0x03, 0x03, /* legacy_version = TLS 1.2 */
30895+ /* 32-byte random (not the HelloRetryRequest magic) */
30896+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30897+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30898+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30899+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30900+ 0x20, /* legacy_session_id length = 32 */
30901+ /* 32-byte session id */
30902+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30903+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30904+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30905+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30906+ 0x13, 0x01, /* cipher_suite = TLS_AES_128_GCM_SHA256 */
30907+ 0x00 /* compression = null; no extensions */
30908+ };
30909+
30910+ /* Same as above but with a single trailing byte after the compression method:
30911+ * a TRUNCATED (partial) extensions length field. This is genuinely malformed,
30912+ * so it must be rejected with decode_error (50), not protocol_version - the
30913+ * fix must not relabel a real decode error as a version mismatch. Record and
30914+ * handshake lengths are bumped by one and a 0x00 trailing byte is appended. */
30915+ static const byte test_tls13_trunc_ext_sh[] = {
30916+ 0x16, 0x03, 0x03, 0x00, 0x4b, /* record: handshake, TLS 1.2, len 75 */
30917+ 0x02, 0x00, 0x00, 0x47, /* server_hello, body len 71 */
30918+ 0x03, 0x03, /* legacy_version = TLS 1.2 */
30919+ /* 32-byte random (not the HelloRetryRequest magic) */
30920+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30921+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30922+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30923+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
30924+ 0x20, /* legacy_session_id length = 32 */
30925+ /* 32-byte session id */
30926+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30927+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30928+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30929+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
30930+ 0x13, 0x01, /* cipher_suite = TLS_AES_128_GCM_SHA256 */
30931+ 0x00, /* compression = null */
30932+ 0x00 /* partial (1-byte) extensions length */
30933+ };
30934+
30935+ /* Drive a TLS 1.3-only client through a ClientHello, inject the supplied
30936+ * (crafted) ServerHello, and assert the handshake aborts with the expected
30937+ * fatal alert. */
30938+ static int test_tls13_sh_expect_alert(const byte* sh, int shSz, int expectAlert)
30939+ {
30940+ EXPECT_DECLS;
30941+ struct test_memio_ctx test_ctx;
30942+ WOLFSSL_CTX *ctx_c = NULL;
30943+ WOLFSSL *ssl_c = NULL;
30944+ WOLFSSL_ALERT_HISTORY h;
30945+
30946+ XMEMSET(&test_ctx, 0, sizeof(test_ctx));
30947+ ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL,
30948+ wolfTLSv1_3_client_method, NULL), 0);
30949+
30950+ /* Produce the ClientHello. */
30951+ ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
30952+ ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)),
30953+ WOLFSSL_ERROR_WANT_READ);
30954+
30955+ /* Discard the ClientHello and inject the crafted ServerHello. */
30956+ test_memio_clear_buffer(&test_ctx, 0);
30957+ ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, (const char *)sh, shSz),
30958+ 0);
30959+
30960+ /* The handshake must fail (not WANT_READ) on the ServerHello. */
30961+ ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
30962+ ExpectIntNE(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)),
30963+ WOLFSSL_ERROR_WANT_READ);
30964+
30965+ ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS);
30966+ ExpectIntEQ(h.last_tx.code, expectAlert);
30967+ ExpectIntEQ(h.last_tx.level, alert_fatal);
30968+
30969+ wolfSSL_free(ssl_c);
30970+ wolfSSL_CTX_free(ctx_c);
30971+
30972+ return EXPECT_RESULT();
30973+ }
30974+
30975+ /* RFC 8446 6.2: a recognized but unsupported protocol version (TLS 1.2
30976+ * ServerHello with no extensions) must be rejected with protocol_version (70),
30977+ * not decode_error (50). */
30978+ static int test_tls13_no_ext_sh_alert(void)
30979+ {
30980+ return test_tls13_sh_expect_alert(test_tls13_no_ext_sh,
30981+ (int)sizeof(test_tls13_no_ext_sh), wolfssl_alert_protocol_version);
30982+ }
30983+
30984+ /* A truncated extensions length field is a real syntax error and must still be
30985+ * reported as decode_error (50), proving the version-mismatch fix did not
30986+ * over-broaden to genuinely malformed messages. */
30987+ static int test_tls13_trunc_ext_sh_alert(void)
30988+ {
30989+ return test_tls13_sh_expect_alert(test_tls13_trunc_ext_sh,
30990+ (int)sizeof(test_tls13_trunc_ext_sh), decode_error);
30991+ }
30992+ #else
30993+ static int test_tls13_no_ext_sh_alert(void)
30994+ {
30995+ return TEST_SKIPPED;
30996+ }
30997+ static int test_tls13_trunc_ext_sh_alert(void)
30998+ {
30999+ return TEST_SKIPPED;
31000+ }
31001+ #endif
31002+
3087931003#if !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_EXTRA_ALERTS) && \
3088031004 defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(WOLFSSL_SP_MATH)
3088131005
@@ -35695,6 +35819,8 @@ TEST_CASE testCases[] = {
3569535819 TEST_DECL(test_ticket_ret_create),
3569635820 TEST_DECL(test_ticket_enc_corrupted),
3569735821 TEST_DECL(test_wrong_cs_downgrade),
35822+ TEST_DECL(test_tls13_no_ext_sh_alert),
35823+ TEST_DECL(test_tls13_trunc_ext_sh_alert),
3569835824 TEST_DECL(test_extra_alerts_wrong_cs),
3569935825 TEST_DECL(test_extra_alerts_skip_hs),
3570035826 TEST_DECL(test_extra_alerts_bad_psk),
0 commit comments