trial decryption for ECH

This commit is contained in:
sebastian-carpenter
2026-05-08 13:26:33 -06:00
parent eecb8cc601
commit e349c68784
6 changed files with 129 additions and 9 deletions
+2 -1
View File
@@ -8024,7 +8024,8 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup)
ssl->options.disallowEncThenMac = ctx->disallowEncThenMac;
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
ssl->options.disableECH = ctx->disableECH;
ssl->options.disableECH = ctx->disableECH;
ssl->options.enableEchTrialDecrypt = ctx->enableEchTrialDecrypt;
#endif
/* default alert state (none) */
+18
View File
@@ -244,6 +244,15 @@ void wolfSSL_CTX_SetEchEnable(WOLFSSL_CTX* ctx, byte enable)
}
}
/* disabled (default) -> only decrypt the ClientHello with configs that have a
* matching configId
* enabled -> try to decrypt the inner ClientHello with all configs */
void wolfSSL_CTX_SetEchEnableTrialDecrypt(WOLFSSL_CTX* ctx, byte enable)
{
if (ctx != NULL)
ctx->enableEchTrialDecrypt = (enable != 0);
}
/* set the ech config from base64 for our client ssl object, base64 is the
* format ech configs are sent using dns records */
int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, const char* echConfigs64,
@@ -480,6 +489,15 @@ void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable)
}
}
/* disabled (default) -> only decrypt the ClientHello with configs that have a
* matching configId
* enabled -> try to decrypt the inner ClientHello with all configs */
void wolfSSL_SetEchEnableTrialDecrypt(WOLFSSL* ssl, byte enable)
{
if (ssl != NULL)
ssl->options.enableEchTrialDecrypt = (enable != 0);
}
/* Walk the ECHConfigExtension list and check for mandatory extensions.
* Returns:
* 0 if all extensions are known/optional,
+11 -8
View File
@@ -14513,23 +14513,26 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
if (echConfig->configId == ech->configId) {
ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen,
ssl->heap);
break;
if (ret == 0)
break;
}
echConfig = echConfig->next;
}
/* otherwise, try to decrypt with all configs */
if (echConfig == NULL || ret != 0) {
/* otherwise, try to decrypt with all configs (trial decryption) */
if (echConfig == NULL && ssl->options.enableEchTrialDecrypt) {
echConfig = ssl->ctx->echConfigs;
while (echConfig != NULL) {
ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen,
ssl->heap);
if (ret == 0)
break;
if (echConfig->configId != ech->configId) {
ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen,
ssl->heap);
if (ret == 0)
break;
}
echConfig = echConfig->next;
}
}
/* if we failed to extract/expand */
if (ret != 0) {
if (ret != 0 || echConfig == NULL) {
WOLFSSL_MSG("ECH rejected");
if (ssl->options.echAccepted == 1) {
+89
View File
@@ -15080,6 +15080,16 @@ static int test_ech_server_ctx_ready(WOLFSSL_CTX* ctx)
return TEST_SUCCESS;
}
/* Server ctx_ready callback: generate ECH config and opt into trial
* decryption at the CTX level so the SSL inherits it on creation */
static int test_ech_server_ctx_ready_trial_decrypt(WOLFSSL_CTX* ctx)
{
int ret = test_ech_server_ctx_ready(ctx);
if (ret == TEST_SUCCESS)
wolfSSL_CTX_SetEchEnableTrialDecrypt(ctx, 1);
return ret;
}
/* Server ssl_ready callback: set SNI */
static int test_ech_server_ssl_ready(WOLFSSL* ssl)
{
@@ -15687,6 +15697,84 @@ static int test_wolfSSL_Tls13_ECH_new_config(void)
return EXPECT_RESULT();
}
/* Test trial decryption for ECH: server has a single config, client receives a
* copy of it but its configId is overwritten so it cannot match the server's */
static int test_wolfSSL_Tls13_ECH_trial_decrypt(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
/* --- CTX-enabled, SSL-disabled override: ECH rejected --- */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
/* *_trial_decrypt sets enableEchTrialDecrypt to 1 - overriding the default
* value of 0 */
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready_trial_decrypt;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* SSL inherited the CTX setting */
ExpectIntEQ(test_ctx.s_ctx->enableEchTrialDecrypt, 1);
ExpectIntEQ(test_ctx.s_ssl->options.enableEchTrialDecrypt, 1);
/* override on the SSL */
wolfSSL_SetEchEnableTrialDecrypt(test_ctx.s_ssl, 0);
ExpectIntEQ(test_ctx.s_ssl->options.enableEchTrialDecrypt, 0);
/* alter the client's configId so it does not match the server's configId */
ExpectNotNull(test_ctx.c_ssl->echConfigs);
ExpectNotNull(test_ctx.s_ctx->echConfigs);
if (EXPECT_SUCCESS()) {
test_ctx.c_ssl->echConfigs->configId =
(byte)(test_ctx.s_ctx->echConfigs->configId ^ 0x01);
}
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(ECH_REQUIRED_E));
test_ssl_memio_cleanup(&test_ctx);
/* --- trial decryption opted in on the SSL: ECH accepted --- */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* opt into trial decryption on the SSL */
wolfSSL_SetEchEnableTrialDecrypt(test_ctx.s_ssl, 1);
ExpectIntEQ(test_ctx.s_ssl->options.enableEchTrialDecrypt, 1);
/* alter the client's configId so it does not match the server's configId */
ExpectNotNull(test_ctx.c_ssl->echConfigs);
ExpectNotNull(test_ctx.s_ctx->echConfigs);
if (EXPECT_SUCCESS()) {
test_ctx.c_ssl->echConfigs->configId =
(byte)(test_ctx.s_ctx->echConfigs->configId ^ 0x01);
}
ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1);
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
/* Test GREASE ECH:
* 1. client sends GREASE ECH extension but server has no ECH configs so it
* ignores it, handshake succeeds normally
@@ -40600,6 +40688,7 @@ TEST_CASE testCases[] = {
TEST_DECL(test_wolfSSL_Tls13_ECH_retry_configs_bad),
TEST_DECL(test_wolfSSL_Tls13_ECH_retry_configs_auth_fail),
TEST_DECL(test_wolfSSL_Tls13_ECH_new_config),
TEST_DECL(test_wolfSSL_Tls13_ECH_trial_decrypt),
TEST_DECL(test_wolfSSL_Tls13_ECH_GREASE),
TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn),
TEST_DECL(test_wolfSSL_Tls13_ECH_long_SNI),
+4
View File
@@ -4044,6 +4044,8 @@ struct WOLFSSL_CTX {
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
byte disableECH:1;
byte enableEchTrialDecrypt:1; /* Trial decryption of the
inner hello */
#endif
word16 minProto:1; /* sets min to min available */
word16 maxProto:1; /* sets max to max available */
@@ -5251,6 +5253,8 @@ struct Options {
word16 disableECH:1; /* Did the user disable ech */
word16 echProcessingInner:1; /* Processing the inner hello */
word16 echRetryConfigsAccepted:1;
word16 enableEchTrialDecrypt:1; /* Trial decryption of the
inner hello */
#endif
#ifdef WOLFSSL_SEND_HRR_COOKIE
word16 cookieGood:1;
+5
View File
@@ -1243,6 +1243,9 @@ WOLFSSL_API int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output,
WOLFSSL_API void wolfSSL_CTX_SetEchEnable(WOLFSSL_CTX* ctx, byte enable);
WOLFSSL_API void wolfSSL_CTX_SetEchEnableTrialDecrypt(WOLFSSL_CTX* ctx,
byte enable);
WOLFSSL_API int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl,
const char* echConfigs64, word32 echConfigs64Len);
@@ -1256,6 +1259,8 @@ WOLFSSL_API int wolfSSL_GetEchRetryConfigs(WOLFSSL* ssl, byte* echConfigs,
word32* echConfigsLen);
WOLFSSL_API void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable);
WOLFSSL_API void wolfSSL_SetEchEnableTrialDecrypt(WOLFSSL* ssl, byte enable);
#endif /* WOLFSSL_TLS13 && HAVE_ECH */
#ifdef HAVE_POLY1305