From 5c281273f0b164d754d0333c0118b62d3fde63b5 Mon Sep 17 00:00:00 2001 From: sebastian-carpenter Date: Wed, 27 May 2026 09:39:09 -0600 Subject: [PATCH] test public sni is always in outer ch --- src/tls.c | 5 +- tests/api.c | 227 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 177 insertions(+), 55 deletions(-) diff --git a/src/tls.c b/src/tls.c index 62118d0678b..e8567582c89 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16459,10 +16459,7 @@ static int TLSX_EchChangeSNI(WOLFSSL* ssl, TLSX** pEchX, echX = TLSX_Find(ssl->ctx->extensions, TLSX_ECH); /* if type is outer change sni to public name */ - if (echX != NULL && - ((WOLFSSL_ECH*)echX->data)->type == ECH_TYPE_OUTER && - (ssl->options.echAccepted || - ((WOLFSSL_ECH*)echX->data)->innerCount == 0)) { + if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->type == ECH_TYPE_OUTER) { if (ssl->extensions) { serverNameX = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME); diff --git a/tests/api.c b/tests/api.c index fe92bdee85e..31ec6d2f729 100644 --- a/tests/api.c +++ b/tests/api.c @@ -15044,6 +15044,63 @@ static word16 echCbTestKemID = 0; static word16 echCbTestKdfID = 0; static word16 echCbTestAeadID = 0; +/* return the index of the first extension + * extLen will be updated with the length of the extensions field */ +static int ech_seek_extensions(byte* buf, word16* extLen) +{ + word16 idx; + byte sessionIdLen; + word16 cipherSuitesLen; + byte compressionLen; + + idx = OPAQUE16_LEN + RAN_LEN; + + sessionIdLen = buf[idx++]; + idx += sessionIdLen; + + ato16(buf + idx, &cipherSuitesLen); + idx += OPAQUE16_LEN + cipherSuitesLen; + + compressionLen = buf[idx++]; + idx += compressionLen; + + ato16(buf + idx, extLen); + idx += OPAQUE16_LEN; + + return idx; +} + +/* locate a particular extension: + * idx_p is updated with the location of that extension + * -> idx_p should start just after the handshake header + * 0 returned on success, error otherwise */ +static int ech_find_extension(byte* buf, word16* idx_p, word16 extType) +{ + word16 idx; + word16 extIdx; + word16 extLen; + + extIdx = ech_seek_extensions(buf + *idx_p, &extLen) + *idx_p; + idx = extIdx; + + while (idx - extIdx < extLen) { + word16 type; + word16 len; + + ato16(buf + idx, &type); + if (type == extType) { + *idx_p = idx; + return 0; + } + + idx += OPAQUE16_LEN; + ato16(buf + idx, &len); + idx += OPAQUE16_LEN + len; + } + + return BAD_FUNC_ARG; +} + /* the arg is whether the client has ech enabled or not */ static int test_ech_server_sni_callback(WOLFSSL* ssl, int* ad, void* arg) { @@ -15915,6 +15972,124 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) return EXPECT_RESULT(); } +/* The public name must be visible and the private name must not be visible */ +static int test_wolfSSL_Tls13_ECH_wire_sni_ex(int accept) +{ + EXPECT_DECLS; + test_ssl_memio_ctx test_ctx; + word16 idx; + word16 nameLen; + word16 publicLen = (word16)XSTRLEN(echCbTestPublicName); + + 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; + /* Accept path uses the correct configs */ + if (accept) + test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + + /* Reject path installs bad configs (with the correct public name) */ + if (!accept) { + WOLFSSL_CTX* tempCtx = NULL; + byte badConfig[128]; + word32 badConfigLen = sizeof(badConfig); + + ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, echCbTestPublicName, + 0, 0, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(tempCtx, badConfig, + &badConfigLen), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(tempCtx); + ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, badConfig, + badConfigLen), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, + echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)), + WOLFSSL_SUCCESS); + } + + /* force HelloRetryRequest */ + ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); + + /* On reject, client aborts with ech_required and won't send a cert. */ + if (!accept) { + wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); + } + + /* client writes CH1 into s_buff */ + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + /* check sent SNI is correct */ + idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + ExpectIntEQ(ech_find_extension(test_ctx.s_buff, &idx, TLSXT_SERVER_NAME), + 0); + ExpectIntEQ(test_ctx.s_buff[idx + 6], WOLFSSL_SNI_HOST_NAME); + ato16(test_ctx.s_buff + idx + 7, &nameLen); + ExpectIntEQ(nameLen, publicLen); + ExpectIntEQ(XMEMCMP(test_ctx.s_buff + idx + 9, echCbTestPublicName, + publicLen), 0); + + /* server consumes CH1 and writes HRR into c_buff */ + ExpectIntEQ(wolfSSL_accept(test_ctx.s_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_ssl->options.serverState, + SERVER_HELLO_RETRY_REQUEST_COMPLETE); + + /* client reads HRR from c_buff and writes CH2 into s_buff */ + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + /* check sent SNI is correct */ + idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + ExpectIntEQ(ech_find_extension(test_ctx.s_buff, &idx, TLSXT_SERVER_NAME), + 0); + ExpectIntEQ(test_ctx.s_buff[idx + 6], WOLFSSL_SNI_HOST_NAME); + ato16(test_ctx.s_buff + idx + 7, &nameLen); + ExpectIntEQ(nameLen, publicLen); + ExpectIntEQ(XMEMCMP(test_ctx.s_buff + idx + 9, echCbTestPublicName, + publicLen), 0); + + /* sanity check: finish the handshake and verify ECH acceptance */ + if (accept) { + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_ACCEPTED); + } + else { + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_SUCCESS); + 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); + + return EXPECT_RESULT(); +} + +static int test_wolfSSL_Tls13_ECH_wire_sni(void) +{ + EXPECT_DECLS; + ExpectIntEQ(test_wolfSSL_Tls13_ECH_wire_sni_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_wire_sni_ex(1), TEST_SUCCESS); + return EXPECT_RESULT(); +} + static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, int enableClient) { @@ -16028,57 +16203,6 @@ static int test_wolfSSL_Tls13_ECH_long_SNI(void) return EXPECT_RESULT(); } -static int ech_seek_extensions(byte* buf, word16* innerExtLen) -{ - word16 idx; - byte sessionIdLen; - word16 cipherSuitesLen; - byte compressionLen; - - idx = OPAQUE16_LEN + RAN_LEN; - - sessionIdLen = buf[idx++]; - idx += sessionIdLen; - - ato16(buf + idx, &cipherSuitesLen); - idx += OPAQUE16_LEN + cipherSuitesLen; - - compressionLen = buf[idx++]; - idx += compressionLen; - - ato16(buf + idx, innerExtLen); - idx += OPAQUE16_LEN; - - return idx; -} - -static int ech_find_extension(byte* buf, word16* idx_p, word16 extType) -{ - word16 idx; - word16 innerExtIdx; - word16 innerExtLen; - - innerExtIdx = ech_seek_extensions(buf + *idx_p, &innerExtLen) + *idx_p; - idx = innerExtIdx; - - while (idx - innerExtIdx < innerExtLen) { - word16 type; - word16 len; - - ato16(buf + idx, &type); - if (type == extType) { - *idx_p = idx; - return 0; - } - - idx += OPAQUE16_LEN; - ato16(buf + idx, &len); - idx += OPAQUE16_LEN + len; - } - - return BAD_FUNC_ARG; -} - /* Test the HRR ECH rejection fallback path: * client offers ECH, HRR is triggered, server sends HRR without ECH extension, * client falls back to the outer transcript, then aborts with ech_required. */ @@ -40780,6 +40904,7 @@ TEST_CASE testCases[] = { 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_wire_sni), TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn), TEST_DECL(test_wolfSSL_Tls13_ECH_long_SNI), TEST_DECL(test_wolfSSL_Tls13_ECH_HRR_rejection),