Skip to content

Commit 27e160f

Browse files
authored
Merge pull request #10764 from embhorn/gh10761
Fix TLS1.2 error code correction
2 parents 0ecc0c5 + 17523c6 commit 27e160f

2 files changed

Lines changed: 146 additions & 2 deletions

File tree

src/tls13.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5538,8 +5538,26 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
55385538
}
55395539

55405540
if ((args->idx - args->begin) + OPAQUE16_LEN > helloSz) {
5541-
if (!ssl->options.downgrade)
5542-
return BUFFER_ERROR;
5541+
if (!ssl->options.downgrade) {
5542+
/* Fewer than OPAQUE16_LEN bytes remain after the compression
5543+
* method, so there is no complete extensions length field. */
5544+
if ((args->idx - args->begin) < helloSz) {
5545+
/* A partial extensions length field is genuinely malformed:
5546+
* report it as a decode error. */
5547+
WOLFSSL_MSG("Truncated extensions length in ServerHello");
5548+
return BUFFER_ERROR;
5549+
}
5550+
/* No extensions field at all, so the server is not offering TLS 1.3
5551+
* (no supported_versions extension - see RFC 8446 4.2.1) but TLS 1.2
5552+
* or below. This is a well-formed message, so a TLS 1.3-only client
5553+
* (downgrade disabled) must reject it as a version mismatch, not as
5554+
* a malformed message. Returning VERSION_ERROR makes the caller send
5555+
* a protocol_version alert (RFC 8446 6.2) rather than decode_error. */
5556+
WOLFSSL_MSG("Server offered TLS 1.2 (no supported_versions ext) "
5557+
"but downgrade not allowed");
5558+
WOLFSSL_ERROR_VERBOSE(VERSION_ERROR);
5559+
return VERSION_ERROR;
5560+
}
55435561
#ifndef WOLFSSL_NO_TLS12
55445562
/* Force client hello version 1.2 to work for static RSA. */
55455563
ssl->chVersion.minor = TLSv1_2_MINOR;

tests/api.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)