Skip to content

Commit ba911a8

Browse files
kazuhoclaude
andcommitted
add test for HRR-accept-SH-reject ECH abort path
Covers the RFC 9849 §6.1.5 case: client offers ECH, HRR confirms acceptance, but the ServerHello's ECH confirmation is corrupted. The client must abort with illegal_parameter rather than fall back to the outer ClientHello. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5957a9a commit ba911a8

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

t/picotls.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,69 @@ static void test_ech_config_mismatch(void)
16351635
free(retry_configs.base);
16361636
}
16371637

1638+
/* Per RFC 9849 §6.1.5, if the HRR confirmed ECH acceptance, the ServerHello MUST also confirm it. Here we force HRR, let the
1639+
* server accept ECH in the HRR, then flip the ECH confirmation bits in the SH and check that the client aborts with
1640+
* illegal_parameter. */
1641+
static void test_ech_hrr_accept_sh_reject(void)
1642+
{
1643+
ptls_t *client, *server;
1644+
ptls_buffer_t cbuf, sbuf;
1645+
size_t consumed;
1646+
int ret;
1647+
ptls_handshake_properties_t client_hs_prop = {
1648+
.client = {
1649+
.ech.configs = ptls_iovec_init(ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1),
1650+
.negotiate_before_key_exchange = 1, /* force HRR */
1651+
}};
1652+
1653+
client = ptls_new(ctx, 0);
1654+
ptls_set_server_name(client, "test.example.com", 0);
1655+
server = ptls_new(ctx_peer, 1);
1656+
ptls_buffer_init(&cbuf, "", 0);
1657+
ptls_buffer_init(&sbuf, "", 0);
1658+
1659+
/* CH1 */
1660+
ret = ptls_handshake(client, &cbuf, NULL, NULL, &client_hs_prop);
1661+
ok(ret == PTLS_ERROR_IN_PROGRESS);
1662+
1663+
/* CH1 -> HRR */
1664+
consumed = cbuf.off;
1665+
ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL);
1666+
ok(ret == PTLS_ERROR_IN_PROGRESS);
1667+
ok(cbuf.off == consumed);
1668+
cbuf.off = 0;
1669+
1670+
/* HRR -> CH2 (client transitions to ECH_STATE_ACCEPTED via HRR confirmation) */
1671+
consumed = sbuf.off;
1672+
ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_hs_prop);
1673+
ok(ret == PTLS_ERROR_IN_PROGRESS);
1674+
ok(sbuf.off == consumed);
1675+
sbuf.off = 0;
1676+
1677+
/* CH2 -> SH + ... */
1678+
consumed = cbuf.off;
1679+
ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL);
1680+
ok(ret == 0);
1681+
ok(cbuf.off == consumed);
1682+
cbuf.off = 0;
1683+
1684+
/* Corrupt last 8 bytes of SH.random (the ECH confirmation signal). SH record layout:
1685+
* [5 record hdr][1 hs_type=0x02][3 hs len][2 legacy_ver][32 random]... -> bytes 35..42 */
1686+
ok(sbuf.off >= 43 && sbuf.base[0] == 0x16 && sbuf.base[5] == 0x02);
1687+
for (size_t i = 35; i < 43; ++i)
1688+
sbuf.base[i] ^= 0xff;
1689+
1690+
/* Corrupted SH -> client MUST abort with illegal_parameter. */
1691+
consumed = sbuf.off;
1692+
ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_hs_prop);
1693+
ok(ret == PTLS_ALERT_ILLEGAL_PARAMETER);
1694+
1695+
ptls_free(client);
1696+
ptls_free(server);
1697+
ptls_buffer_dispose(&cbuf);
1698+
ptls_buffer_dispose(&sbuf);
1699+
}
1700+
16381701
static void do_test_pre_shared_key(int mode)
16391702
{
16401703
ptls_context_t ctx_client = *ctx;
@@ -2147,6 +2210,7 @@ static void test_all_handshakes(void)
21472210
test_client_ech_configs = ptls_iovec_init(ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1);
21482211
}
21492212
subtest("ech-config-mismatch", test_ech_config_mismatch);
2213+
subtest("ech-hrr-accept-sh-reject", test_ech_hrr_accept_sh_reject);
21502214
test_client_ech_configs = ptls_iovec_init(NULL, 0);
21512215
}
21522216

0 commit comments

Comments
 (0)