Skip to content

Commit 139522b

Browse files
Merge pull request #10892 from embhorn/gh10792
Reject HelloRetryRequest lacking supported_versions before downgrade
2 parents 8510488 + 24e6a9c commit 139522b

3 files changed

Lines changed: 170 additions & 0 deletions

File tree

src/tls13.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5577,6 +5577,20 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
55775577
WOLFSSL_ERROR_VERBOSE(VERSION_ERROR);
55785578
return VERSION_ERROR;
55795579
}
5580+
5581+
if (args->extMsgType == hello_retry_request) {
5582+
/* The sentinel Random (RFC 8446 4.1.3) identifies this as a TLS 1.3
5583+
* HelloRetryRequest, which MUST carry supported_versions
5584+
* (4.1.4/4.2.1). Reaching this downgrade branch means it does not,
5585+
* so the HRR is malformed. Reject it before DoServerHello would
5586+
* reinterpret the sentinel as a plain TLS 1.2 ServerHello.Random.
5587+
* The sentinel has already identified this as an HRR, so report it
5588+
* uniformly as an invalid HRR; INVALID_PARAMETER maps to the
5589+
* illegal_parameter alert. */
5590+
WOLFSSL_MSG("HelloRetryRequest with no supported_versions");
5591+
WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER);
5592+
return INVALID_PARAMETER;
5593+
}
55805594
#ifndef WOLFSSL_NO_TLS12
55815595
/* Force client hello version 1.2 to work for static RSA. */
55825596
ssl->chVersion.minor = TLSv1_2_MINOR;
@@ -5632,6 +5646,21 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
56325646
WOLFSSL_ERROR_VERBOSE(VERSION_ERROR);
56335647
return VERSION_ERROR;
56345648
}
5649+
5650+
if (args->extMsgType == hello_retry_request) {
5651+
/* The HelloRetryRequest sentinel Random is reserved for TLS 1.3
5652+
* (RFC 8446 4.1.3), but supported_versions (which an HRR MUST
5653+
* carry, 4.1.4/4.2.1) is absent, so the message is malformed.
5654+
* Reject before the downgrade reparses the remaining extensions
5655+
* as a TLS 1.2 server_hello, where a recognized-but-not-
5656+
* permitted extension (e.g. server_name) would wrongly yield an
5657+
* unsupported_extension alert. illegal_parameter (via
5658+
* EXT_NOT_ALLOWED) is chosen over missing_extension per the
5659+
* RFC 8446 4.2 rule for such extensions. */
5660+
WOLFSSL_MSG("HelloRetryRequest without supported_versions");
5661+
WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED);
5662+
return EXT_NOT_ALLOWED;
5663+
}
56355664
#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \
56365665
defined(WOLFSSL_WPAS_SMALL)
56375666
/* Check if client has disabled TLS 1.2 */

tests/api/test_tls13.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6351,6 +6351,145 @@ int test_tls13_warning_alert_is_fatal(void)
63516351
return EXPECT_RESULT();
63526352
}
63536353

6354+
/* Regression test for a malformed HelloRetryRequest sent to a downgrade capable
6355+
* (TLS 1.2 and TLS 1.3) client. Both crafted messages bear the HelloRetryRequest
6356+
* sentinel Random but omit supported_versions, which an HRR MUST carry, so the
6357+
* client must reject them with illegal_parameter (RFC 8446 Sec. 4.1.4/4.2)
6358+
* rather than downgrading to TLS 1.2 and losing the HRR context:
6359+
* 1. has-extensions form: carries a server_name extension (recognized but not
6360+
* permitted in an HRR). Before the fix the downgrade reparsed it as a plain
6361+
* server_hello, where server_name is allowed, and the client wrongly sent
6362+
* unsupported_extension (when SNI is compiled in). The fix rejects it in
6363+
* the version-negotiation step, so the assertions hold regardless of SNI.
6364+
* 2. no-extensions form: no extensions field at all. Before the fix this
6365+
* downgraded silently, treating the sentinel Random as a normal
6366+
* ServerHello.Random.
6367+
*/
6368+
int test_tls13_hrr_recognized_ext_downgrade(void)
6369+
{
6370+
EXPECT_DECLS;
6371+
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_TLS12) && \
6372+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6373+
!defined(NO_WOLFSSL_CLIENT) && \
6374+
defined(WOLFSSL_AES_128) && defined(HAVE_AESGCM) && !defined(NO_SHA256) && \
6375+
!defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
6376+
WOLFSSL_CTX *ctx_c = NULL;
6377+
WOLFSSL *ssl_c = NULL;
6378+
struct test_memio_ctx test_ctx;
6379+
WOLFSSL_ALERT_HISTORY h;
6380+
/* HelloRetryRequest bearing the sentinel Random and TLS_AES_128_GCM_SHA256,
6381+
* with NO supported_versions extension and a zero-length server_name
6382+
* extension (type 0x0000).
6383+
* extensions length: 4 (0x00,0x04)
6384+
* handshake body length: 44 (0x00,0x00,0x2c)
6385+
* record body length: 48 (0x00,0x30)
6386+
*/
6387+
static const unsigned char hrr_sni_no_sv[] = {
6388+
/* TLS record header: handshake, TLS 1.2 compat, len=48 */
6389+
0x16, 0x03, 0x03, 0x00, 0x30,
6390+
/* Handshake header: ServerHello, len=44 */
6391+
0x02, 0x00, 0x00, 0x2c,
6392+
/* legacy_version: TLS 1.2 */
6393+
0x03, 0x03,
6394+
/* HelloRetryRequest magic random */
6395+
0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11,
6396+
0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91,
6397+
0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e,
6398+
0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c,
6399+
/* session ID length: 0 */
6400+
0x00,
6401+
/* cipher suite: TLS_AES_128_GCM_SHA256 */
6402+
0x13, 0x01,
6403+
/* compression: null */
6404+
0x00,
6405+
/* extensions length: 4 */
6406+
0x00, 0x04,
6407+
/* server_name extension type 0x0000, zero-length value */
6408+
0x00, 0x00, 0x00, 0x00
6409+
};
6410+
/* HelloRetryRequest bearing the sentinel Random with NO extensions field.
6411+
* handshake body length: 38 (0x00,0x00,0x26)
6412+
* record body length: 42 (0x00,0x2a)
6413+
*/
6414+
static const unsigned char hrr_no_ext[] = {
6415+
/* TLS record header: handshake, TLS 1.2 compat, len=42 */
6416+
0x16, 0x03, 0x03, 0x00, 0x2a,
6417+
/* Handshake header: ServerHello, len=38 */
6418+
0x02, 0x00, 0x00, 0x26,
6419+
/* legacy_version: TLS 1.2 */
6420+
0x03, 0x03,
6421+
/* HelloRetryRequest magic random */
6422+
0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11,
6423+
0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91,
6424+
0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e,
6425+
0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c,
6426+
/* session ID length: 0 */
6427+
0x00,
6428+
/* cipher suite: TLS_AES_128_GCM_SHA256 */
6429+
0x13, 0x01,
6430+
/* compression: null */
6431+
0x00
6432+
};
6433+
6434+
/* Scenario 1: HelloRetryRequest with a not-permitted server_name extension
6435+
* and no supported_versions. */
6436+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
6437+
XMEMSET(&h, 0, sizeof(h));
6438+
6439+
/* Client allows both TLS 1.2 and TLS 1.3 so the missing supported_versions
6440+
* would otherwise trigger a downgrade. */
6441+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL,
6442+
wolfSSLv23_client_method, NULL), 0);
6443+
6444+
/* Inject the crafted HelloRetryRequest before the client starts the
6445+
* handshake. wolfSSL_connect sends the ClientHello then reads this. */
6446+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 1,
6447+
(const char *)hrr_sni_no_sv, sizeof(hrr_sni_no_sv)), 0);
6448+
6449+
/* Handshake must fail with EXT_NOT_ALLOWED (server_name is not permitted in
6450+
* a HelloRetryRequest), not UNSUPPORTED_EXTENSION. */
6451+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
6452+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1),
6453+
WC_NO_ERR_TRACE(EXT_NOT_ALLOWED));
6454+
6455+
/* RFC 8446 Sec. 4.2: the client MUST abort with illegal_parameter (47),
6456+
* not unsupported_extension (110). */
6457+
ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS);
6458+
ExpectIntEQ(h.last_tx.code, illegal_parameter);
6459+
ExpectIntEQ(h.last_tx.level, alert_fatal);
6460+
6461+
wolfSSL_free(ssl_c);
6462+
ssl_c = NULL;
6463+
wolfSSL_CTX_free(ctx_c);
6464+
ctx_c = NULL;
6465+
6466+
/* Scenario 2: HelloRetryRequest sentinel with no extensions field at all.
6467+
* Must also be rejected with illegal_parameter rather than downgraded. */
6468+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
6469+
XMEMSET(&h, 0, sizeof(h));
6470+
6471+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL,
6472+
wolfSSLv23_client_method, NULL), 0);
6473+
6474+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 1,
6475+
(const char *)hrr_no_ext, sizeof(hrr_no_ext)), 0);
6476+
6477+
/* No offending extension is present here, so the client reports the message
6478+
* as an invalid HRR; the wire alert is still illegal_parameter. */
6479+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
6480+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1),
6481+
WC_NO_ERR_TRACE(INVALID_PARAMETER));
6482+
6483+
ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS);
6484+
ExpectIntEQ(h.last_tx.code, illegal_parameter);
6485+
ExpectIntEQ(h.last_tx.level, alert_fatal);
6486+
6487+
wolfSSL_free(ssl_c);
6488+
wolfSSL_CTX_free(ctx_c);
6489+
#endif
6490+
return EXPECT_RESULT();
6491+
}
6492+
63546493
/* Test that wolfSSL_set1_sigalgs_list() is honored in TLS 1.3
63556494
*/
63566495
int test_tls13_cert_req_sigalgs(void)

tests/api/test_tls13.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ int test_tls13_middlebox_compat_empty_session_id(void);
4747
int test_tls13_plaintext_alert(void);
4848
int test_tls13_warning_alert_is_fatal(void);
4949
int test_tls13_unknown_ext_rejected(void);
50+
int test_tls13_hrr_recognized_ext_downgrade(void);
5051
int test_tls13_cert_req_sigalgs(void);
5152
int test_tls13_derive_keys_no_key(void);
5253
int test_tls13_pqc_hybrid_truncated_keyshare(void);
@@ -140,6 +141,7 @@ int test_tls13_pqc_hybrid_async_server(void);
140141
TEST_DECL_GROUP("tls13", test_tls13_remove_session_return), \
141142
TEST_DECL_GROUP("tls13", test_tls13_0rtt_ext_cache_eviction), \
142143
TEST_DECL_GROUP("tls13", test_tls13_unknown_ext_rejected), \
144+
TEST_DECL_GROUP("tls13", test_tls13_hrr_recognized_ext_downgrade), \
143145
TEST_DECL_GROUP("tls13", test_tls13_corrupted_finished), \
144146
TEST_DECL_GROUP("tls13", test_tls13_certificate_verify_bad_sigalgo), \
145147
TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \

0 commit comments

Comments
 (0)