Skip to content

Commit 79e783b

Browse files
extra HRR testing for ECH
1 parent 1cf4940 commit 79e783b

2 files changed

Lines changed: 126 additions & 8 deletions

File tree

src/tls13.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6911,19 +6911,19 @@ static int EchWriteAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz,
69116911
output + acceptOffset);
69126912

69136913
if (ret == 0) {
6914+
WOLFSSL_MSG("ECH accepted");
6915+
69146916
tmpHashes = ssl->hsHashes;
69156917
ssl->hsHashes = ssl->hsHashesEch;
69166918

6919+
ssl->options.echAccepted = 1;
69176920
/* after HRR, hsHashesEch must contain:
69186921
* message_hash(ClientHelloInner1) || HRR (actual, not zeros) */
69196922
if (msgType == hello_retry_request) {
69206923
ret = HashRaw(ssl, output, helloSz);
69216924
}
69226925
/* normal TLS code will calculate transcript of ServerHello */
69236926
else {
6924-
WOLFSSL_MSG("ECH accepted");
6925-
ssl->options.echAccepted = 1;
6926-
69276927
ssl->hsHashes = tmpHashes;
69286928
FreeHandshakeHashes(ssl);
69296929
tmpHashes = ssl->hsHashesEch;
@@ -7890,6 +7890,10 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
78907890
if (extMsgType == hello_retry_request) {
78917891
/* reset the ech state for round 2 */
78927892
((WOLFSSL_ECH*)echX->data)->state = ECH_WRITE_NONE;
7893+
/* inner hello no longer needed, free it */
7894+
XFREE(((WOLFSSL_ECH*)echX->data)->innerClientHello,
7895+
ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
7896+
((WOLFSSL_ECH*)echX->data)->innerClientHello = NULL;
78937897
}
78947898
else {
78957899
if (ret == 0) {

tests/api.c

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14589,7 +14589,7 @@ static int test_wolfSSL_Tls13_ECH_HRR(void)
1458914589
/* Static storage for passing ECH config between server and client callbacks */
1459014590
static byte echCbTestConfigs[512];
1459114591
static word32 echCbTestConfigsLen;
14592-
static const char* echCbTestPublicName = "ech-public-name.com";
14592+
static const char* echCbTestPublicName = "example.com";
1459314593
static const char* echCbTestPrivateName = "ech-private-name.com";
1459414594
static word16 echCbTestKemID = 0;
1459514595
static word16 echCbTestKdfID = 0;
@@ -14884,6 +14884,10 @@ static int test_wolfSSL_Tls13_ECH_bad_configs_ex(int hrr, int sniCb)
1488414884
echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)),
1488514885
WOLFSSL_SUCCESS);
1488614886

14887+
/* client will send empty cert on rejection, so server should not ask for
14888+
* cert */
14889+
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
14890+
1488714891
if (hrr) {
1488814892
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
1488914893
}
@@ -14916,6 +14920,10 @@ static int test_wolfSSL_Tls13_ECH_bad_configs_ex(int hrr, int sniCb)
1491614920
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME,
1491714921
badPrivateName, (word16)XSTRLEN(badPrivateName)), WOLFSSL_SUCCESS);
1491814922

14923+
/* client will send empty cert on rejection, so server should not ask for
14924+
* cert */
14925+
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
14926+
1491914927
if (hrr) {
1492014928
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
1492114929
}
@@ -15062,9 +15070,12 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(int hrr)
1506215070

1506315071
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
1506415072

15065-
/* wrong ECH config so server sends retry_configs in EncryptedExtensions */
15073+
/* wrong ECH config so server sends retry_configs in EncryptedExtensions;
15074+
* use a public name that does NOT appear in the server cert so that the
15075+
* outer handshake cert check fails with DOMAIN_NAME_MISMATCH */
15076+
const char* badPublicName = "ech-public-name.com";
1506615077
ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
15067-
ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, echCbTestPublicName,
15078+
ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, badPublicName,
1506815079
0, 0, 0), WOLFSSL_SUCCESS);
1506915080
ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(tempCtx, badConfigs, &badConfigsLen),
1507015081
WOLFSSL_SUCCESS);
@@ -15082,9 +15093,9 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(int hrr)
1508215093
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
1508315094
wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL);
1508415095

15085-
/* outer handshake uses public_name, which doesn't match the server cert */
15096+
/* outer handshake uses badPublicName, which doesn't match the server cert */
1508615097
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME,
15087-
echCbTestPublicName, (word16)XSTRLEN(echCbTestPublicName)),
15098+
badPublicName, (word16)XSTRLEN(badPublicName)),
1508815099
WOLFSSL_SUCCESS);
1508915100

1509015101
if (hrr)
@@ -15390,6 +15401,107 @@ static int test_wolfSSL_Tls13_ECH_long_SNI(void)
1539015401
return EXPECT_RESULT();
1539115402
}
1539215403

15404+
/* Test the HRR ECH rejection fallback path:
15405+
* client offers ECH, HRR is triggered, server sends HRR without ECH extension
15406+
* (confBuf == NULL), client frees hsHashesEch and falls back to the outer
15407+
* transcript, then aborts with ech_required.
15408+
*
15409+
* When disableECH is set on the server the ECH-aware SNI matching in
15410+
* TLSX_SNI_Parse (which normally accepts the ECH public name) is bypassed, so
15411+
* the server SNI must be set explicitly to avoid an unrecognized_name fatal
15412+
* alert before HRR is even sent. */
15413+
static int test_wolfSSL_Tls13_ECH_HRR_rejection(void)
15414+
{
15415+
EXPECT_DECLS;
15416+
test_ssl_memio_ctx test_ctx;
15417+
15418+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
15419+
15420+
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
15421+
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
15422+
15423+
/* Server generates ECH config (public name = echCbTestPublicName =
15424+
* "example.com", which appears in the server cert SAN so cert
15425+
* verification passes when ECH is rejected and the outer name is used) */
15426+
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
15427+
/* Client sets the correct ECH config and private SNI */
15428+
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
15429+
15430+
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
15431+
15432+
/* Server must not require a client certificate */
15433+
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
15434+
wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL);
15435+
15436+
/* Disable ECH on the server SSL object: the server ignores ECH in CH1 and
15437+
* sends HRR without an ECH extension (confBuf stays NULL on the client).
15438+
* Because ECH is disabled, the SNI code's ECH-aware public-name fallback
15439+
* is skipped; set the server SNI to the public name explicitly. */
15440+
wolfSSL_SetEchEnable(test_ctx.s_ssl, 0);
15441+
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME,
15442+
echCbTestPublicName, (word16)XSTRLEN(echCbTestPublicName)),
15443+
WOLFSSL_SUCCESS);
15444+
15445+
/* Force HRR so the code path at tls13.c:5717-5725 is exercised:
15446+
* client receives HRR with no ECH extension, detects confBuf == NULL
15447+
* and frees hsHashesEch, falling back to the outer transcript */
15448+
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
15449+
15450+
/* Handshake must fail: client aborts with ech_required after Finished */
15451+
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
15452+
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
15453+
/* hsHashesEch must have been freed by the HRR rejection code path */
15454+
ExpectNull(test_ctx.c_ssl->hsHashesEch);
15455+
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
15456+
WC_NO_ERR_TRACE(ECH_REQUIRED_E));
15457+
15458+
test_ssl_memio_cleanup(&test_ctx);
15459+
15460+
return EXPECT_RESULT();
15461+
}
15462+
15463+
/* RFC 9849 §6.1.5: server must abort if CH2 omits the ECH extension after the
15464+
* server accepted ECH in the HRR round. */
15465+
static int test_wolfSSL_Tls13_ECH_HRR_ch2_no_ech(void)
15466+
{
15467+
EXPECT_DECLS;
15468+
test_ssl_memio_ctx test_ctx;
15469+
15470+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
15471+
15472+
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
15473+
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
15474+
15475+
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
15476+
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
15477+
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
15478+
15479+
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
15480+
15481+
/* withhold key shares from CH1 so the server is forced to send HRR */
15482+
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
15483+
15484+
/* one round: client sends CH1, server processes it and sends HRR */
15485+
(void)test_ssl_memio_do_handshake(&test_ctx, 1, NULL);
15486+
15487+
/* server must have committed to ECH acceptance in the HRR */
15488+
ExpectIntEQ(test_ctx.s_ssl->options.serverState,
15489+
SERVER_HELLO_RETRY_REQUEST_COMPLETE);
15490+
ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 1);
15491+
15492+
/* disable ECH on the client so CH2 omits the ECH extension entirely */
15493+
wolfSSL_SetEchEnable(test_ctx.c_ssl, 0);
15494+
15495+
/* rest of handshake must fail: server enforces RFC 9849 §6.1.5 */
15496+
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
15497+
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
15498+
WC_NO_ERR_TRACE(INCOMPLETE_DATA));
15499+
15500+
test_ssl_memio_cleanup(&test_ctx);
15501+
15502+
return EXPECT_RESULT();
15503+
}
15504+
1539315505
/* when ECH is rejected the certificate must match the public name of the chosen
1539415506
* ech config
1539515507
* the cert check should pass and the client aborts with ech_required */
@@ -36877,6 +36989,8 @@ TEST_CASE testCases[] = {
3687736989
TEST_DECL(test_wolfSSL_Tls13_ECH_GREASE),
3687836990
TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn),
3687936991
TEST_DECL(test_wolfSSL_Tls13_ECH_long_SNI),
36992+
TEST_DECL(test_wolfSSL_Tls13_ECH_HRR_rejection),
36993+
TEST_DECL(test_wolfSSL_Tls13_ECH_HRR_ch2_no_ech),
3688036994
TEST_DECL(test_wolfSSL_Tls13_ECH_rejected_cert_valid),
3688136995
TEST_DECL(test_wolfSSL_Tls13_ECH_rejected_empty_client_cert),
3688236996
#endif

0 commit comments

Comments
 (0)