diff --git a/src/internal.c b/src/internal.c index f19d8fa10d..affb8a943e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -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) */ diff --git a/src/ssl_ech.c b/src/ssl_ech.c index abe2ab8a55..c085468238 100644 --- a/src/ssl_ech.c +++ b/src/ssl_ech.c @@ -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, diff --git a/src/tls.c b/src/tls.c index e727f655d3..f1a14bac23 100644 --- a/src/tls.c +++ b/src/tls.c @@ -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) { diff --git a/tests/api.c b/tests/api.c index 4b0a657f6d..c94f162e78 100644 --- a/tests/api.c +++ b/tests/api.c @@ -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), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 5daab5620f..b5f2c69462 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -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; diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 4dd5a6bd55..6e713d4bdd 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -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