@@ -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+
16381701static 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