@@ -14589,7 +14589,7 @@ static int test_wolfSSL_Tls13_ECH_HRR(void)
1458914589/* Static storage for passing ECH config between server and client callbacks */
1459014590static byte echCbTestConfigs[512];
1459114591static word32 echCbTestConfigsLen;
14592- static const char* echCbTestPublicName = "ech-public-name .com";
14592+ static const char* echCbTestPublicName = "example .com";
1459314593static const char* echCbTestPrivateName = "ech-private-name.com";
1459414594static word16 echCbTestKemID = 0;
1459514595static 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