From e349c687847e28218b2b329629996a69a0e2ea51 Mon Sep 17 00:00:00 2001 From: sebastian-carpenter Date: Fri, 8 May 2026 13:26:33 -0600 Subject: [PATCH 1/2] trial decryption for ECH --- src/internal.c | 3 +- src/ssl_ech.c | 18 ++++++++++ src/tls.c | 19 +++++----- tests/api.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++ wolfssl/internal.h | 4 +++ wolfssl/ssl.h | 5 +++ 6 files changed, 129 insertions(+), 9 deletions(-) diff --git a/src/internal.c b/src/internal.c index f19d8fa10d6..affb8a943e5 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 abe2ab8a55c..c0854682382 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 e727f655d32..f1a14bac23b 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 4b0a657f6d9..c94f162e78f 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 5daab5620f1..b5f2c694629 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 4dd5a6bd556..6e713d4bddd 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 From 335714d6587d3c4c258af63cf1fbc539b12550f2 Mon Sep 17 00:00:00 2001 From: sebastian-carpenter Date: Mon, 11 May 2026 11:09:42 -0600 Subject: [PATCH 2/2] API to get ECH connection status --- src/ssl_ech.c | 56 +++++++++++++++++++++++++ src/tls.c | 11 ++--- tests/api.c | 113 +++++++++++++++++++++++++++++++++++++++----------- wolfssl/ssl.h | 6 +++ 4 files changed, 153 insertions(+), 33 deletions(-) diff --git a/src/ssl_ech.c b/src/ssl_ech.c index c0854682382..3760501fdbc 100644 --- a/src/ssl_ech.c +++ b/src/ssl_ech.c @@ -498,6 +498,62 @@ void wolfSSL_SetEchEnableTrialDecrypt(WOLFSSL* ssl, byte enable) ssl->options.enableEchTrialDecrypt = (enable != 0); } +/* Return the status of the ECH connection. Possible return values: + * WOLFSSL_ECH_STATUS_NOT_OFFERED: + * server - client did not send ECH or it is not setup + * client - ECH is not setup + * pending - connection has not been initiated + * + * WOLFSSL_ECH_STATUS_GREASE: + * client - GREASE ECH extension sent + * + * WOLFSSL_ECH_STATUS_REJECTED: + * server - ECH was not accepted (decryption of inner ClientHello failed) + * client - ECH was offered but the server rejected it + * + * In both cases the connection fell back to the outer transcript. + * + * WOLFSSL_ECH_STATUS_ACCEPTED: + * Decryption of the inner ClientHello was successful and the inner + * transcript was used. + * + * Returns BAD_FUNC_ARG if ssl is NULL. */ +int wolfSSL_GetEchStatus(const WOLFSSL* ssl) +{ + if (ssl == NULL) + return BAD_FUNC_ARG; + + if (ssl->options.disableECH) + return WOLFSSL_ECH_STATUS_NOT_OFFERED; + if (ssl->options.echAccepted) + return WOLFSSL_ECH_STATUS_ACCEPTED; + + if (ssl->options.side == WOLFSSL_SERVER_END) { + TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH); + WOLFSSL_ECH* ech; + + if (echX == NULL || echX->data == NULL) + return WOLFSSL_ECH_STATUS_NOT_OFFERED; + + /* state stays at ECH_WRITE_NONE and innerClientHello stays NULL when + * the client did not send an ECH extension */ + ech = (WOLFSSL_ECH*)echX->data; + if (ech->state == ECH_WRITE_NONE && ech->innerClientHello == NULL) + return WOLFSSL_ECH_STATUS_NOT_OFFERED; + + return WOLFSSL_ECH_STATUS_REJECTED; + } + + /* client */ + if (ssl->options.connectState < CLIENT_HELLO_SENT) + return WOLFSSL_ECH_STATUS_NOT_OFFERED; + + if (ssl->echConfigs == NULL) + return WOLFSSL_ECH_STATUS_GREASE; + + return WOLFSSL_ECH_STATUS_REJECTED; +} + /* 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 f1a14bac23b..6792dcfd196 100644 --- a/src/tls.c +++ b/src/tls.c @@ -14551,20 +14551,15 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, } } else { + WOLFSSL_MSG("ECH accepted"); + ssl->options.echAccepted = 1; + ret = TLSX_ECH_CheckInnerPadding(ssl, ech); if (ret == 0) { /* expand EchOuterExtensions if present. * Also, if it exists, copy sessionID from outer hello */ ret = TLSX_ECH_ExpandOuterExtensions(ssl, ech, ssl->heap); } - - if (ret == 0){ - WOLFSSL_MSG("ECH accepted"); - ssl->options.echAccepted = 1; - } - else { - WOLFSSL_MSG("ECH rejected"); - } } if (ret != 0) { XFREE(ech->innerClientHello, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/api.c b/tests/api.c index c94f162e78f..0d9440e7add 100644 --- a/tests/api.c +++ b/tests/api.c @@ -14976,7 +14976,7 @@ static int test_wolfSSL_ECH_conn_ex(method_provider serverMeth, /* connect like normal */ ExpectIntEQ(wolfSSL_set_fd(ssl, sockfd), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_connect(ssl), WOLFSSL_SUCCESS); - ExpectIntEQ(ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(ssl), WOLFSSL_ECH_STATUS_ACCEPTED); ExpectIntEQ(wolfSSL_write(ssl, privateName, privateNameLen), privateNameLen); ExpectIntGT((replyLen = wolfSSL_read(ssl, reply, sizeof(reply))), 0); @@ -15137,7 +15137,10 @@ static int test_wolfSSL_Tls13_ECH_all_algos_ex(void) ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); if (echCbTestKemID != 0 && echCbTestKdfID != 0 && echCbTestAeadID != 0) { TLSX* echX = TLSX_Find(test_ctx.c_ssl->extensions, TLSX_ECH); @@ -15250,7 +15253,10 @@ static int test_wolfSSL_Tls13_ECH_no_private_name(void) ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); test_ssl_memio_cleanup(&test_ctx); @@ -15270,7 +15276,10 @@ static int test_wolfSSL_Tls13_ECH_no_private_name(void) echCbTestConfigsLen), WOLFSSL_SUCCESS); ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); test_ssl_memio_cleanup(&test_ctx); @@ -15289,7 +15298,10 @@ static int test_wolfSSL_Tls13_ECH_no_private_name(void) echCbTestConfigsLen), WOLFSSL_SUCCESS); ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); test_ssl_memio_cleanup(&test_ctx); @@ -15348,7 +15360,10 @@ static int test_wolfSSL_Tls13_ECH_bad_configs_ex(int hrr, int sniCb) } ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_REJECTED); test_ssl_memio_cleanup(&test_ctx); @@ -15384,7 +15399,12 @@ static int test_wolfSSL_Tls13_ECH_bad_configs_ex(int hrr, int sniCb) } ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + /* server decrypts inner successfully but rejects SNI, thus the client does + * not receive the acceptance signal */ + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); test_ssl_memio_cleanup(&test_ctx); @@ -15445,7 +15465,10 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr) /* ECH must fail and retry configs must be present */ ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_REJECTED); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), WC_NO_ERR_TRACE(ECH_REQUIRED_E)); @@ -15483,7 +15506,10 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr) ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); wolfSSL_CTX_free(test_ctx.s_ctx); test_ctx.s_ctx = NULL; @@ -15605,7 +15631,10 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_bad(void) /* bad retry configs are discarded - failure must be ECH_REQUIRED_E, * not a retry-config parse error */ ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_REJECTED); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), WC_NO_ERR_TRACE(ECH_REQUIRED_E)); @@ -15690,7 +15719,10 @@ static int test_wolfSSL_Tls13_ECH_new_config(void) WOLFSSL_SUCCESS); ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); test_ssl_memio_cleanup(&test_ctx); @@ -15736,7 +15768,10 @@ static int test_wolfSSL_Tls13_ECH_trial_decrypt(void) } ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_REJECTED); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), WC_NO_ERR_TRACE(ECH_REQUIRED_E)); @@ -15768,7 +15803,10 @@ static int test_wolfSSL_Tls13_ECH_trial_decrypt(void) } ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); test_ssl_memio_cleanup(&test_ctx); @@ -15810,9 +15848,11 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) /* handshake should succeed - server ignores the GREASE ECH extension */ ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - /* ECH should NOT be accepted since this was GREASE */ - ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 0); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + /* server has no configs and client did not offer real ECH */ + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_GREASE); /* verify no ECH configs are received */ ExpectNull(test_ctx.c_ssl->echConfigs); /* retry configs must not be saved */ @@ -15848,9 +15888,12 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) /* handshake should succeed - server responds to the GREASE ECH extension */ ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - /* ECH should NOT be accepted since this was GREASE */ - ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 0); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + /* server was unable to decrypt the ECH extension's payload + * client never offered real ECH */ + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_GREASE); /* verify no ECH configs are received */ ExpectNull(test_ctx.c_ssl->echConfigs); /* retry configs must not be saved */ @@ -15895,6 +15938,8 @@ static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, * normally but ECH is not accepted */ ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); } else if (!enableServer) { /* client sends ECH but server can't process it: server has no ECH @@ -15902,8 +15947,11 @@ static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, * rejection and aborts the handshake */ ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); } - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); test_ssl_memio_cleanup(&test_ctx); @@ -16008,7 +16056,10 @@ static int test_wolfSSL_Tls13_ECH_HRR_rejection(void) /* Handshake must fail: client aborts with ech_required */ ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); /* hsHashesEch must have been freed by the HRR rejection code path */ ExpectNull(test_ctx.c_ssl->hsHashesEch); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), @@ -16046,7 +16097,10 @@ static int test_wolfSSL_Tls13_ECH_ch2_no_ech(void) /* server must have committed to ECH acceptance in the HRR */ ExpectIntEQ(test_ctx.s_ssl->options.serverState, SERVER_HELLO_RETRY_REQUEST_COMPLETE); - ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); /* disable ECH on the client so CH2 omits the ECH extension entirely */ wolfSSL_SetEchEnable(test_ctx.c_ssl, 0); @@ -16088,7 +16142,10 @@ static int test_wolfSSL_Tls13_ECH_ch2_decrypt_error(void) ExpectIntEQ(test_ctx.s_ssl->options.serverState, SERVER_HELLO_RETRY_REQUEST_COMPLETE); - ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 1); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); if (EXPECT_SUCCESS()) { /* Client reads HRR and writes CH2 into s_buff */ @@ -16166,7 +16223,10 @@ static int test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex(const char* publicName, /* client sends ECH but server can't process it, however it is possible to * fall back to the outer handshake */ ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); if (validName) { /* the server should see the handshake as successful @@ -16244,7 +16304,10 @@ static int test_wolfSSL_Tls13_ECH_rejected_empty_client_cert(void) publicName, (word16)XSTRLEN(publicName)), WOLFSSL_SUCCESS); ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_NOT_OFFERED); /* Server cert is valid for public_name, cert check passes, ech_required * is sent on the client side. */ diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 6e713d4bddd..7cf3b19eefa 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1261,6 +1261,12 @@ WOLFSSL_API int wolfSSL_GetEchRetryConfigs(WOLFSSL* ssl, byte* echConfigs, WOLFSSL_API void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable); WOLFSSL_API void wolfSSL_SetEchEnableTrialDecrypt(WOLFSSL* ssl, byte enable); + +#define WOLFSSL_ECH_STATUS_NOT_OFFERED 0 +#define WOLFSSL_ECH_STATUS_GREASE 1 +#define WOLFSSL_ECH_STATUS_REJECTED 2 +#define WOLFSSL_ECH_STATUS_ACCEPTED 3 +WOLFSSL_API int wolfSSL_GetEchStatus(const WOLFSSL* ssl); #endif /* WOLFSSL_TLS13 && HAVE_ECH */ #ifdef HAVE_POLY1305