flaky ECH test: fix method for finding ECH extension

This commit is contained in:
sebastian-carpenter
2026-05-22 09:43:15 -06:00
parent abe15d260b
commit d4ed43853f
+69 -71
View File
@@ -16027,6 +16027,57 @@ 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. */
@@ -16130,7 +16181,7 @@ static int test_wolfSSL_Tls13_ECH_ch2_decrypt_error(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
int i;
word16 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
@@ -16163,22 +16214,20 @@ static int test_wolfSSL_Tls13_ECH_ch2_decrypt_error(void)
/* Corrupt one byte of the ECH ciphertext in the CH2 record in s_buff.
* ECH outer extension layout after the 0xFE0D type marker:
* extLen(2) + outerType(1) + kdfId(2) + aeadId(2) + configId(1)
* + encLen(2, always 0 in CH2) + payloadLen(2) = 12 bytes, so the
* ciphertext starts 14 bytes past the first 0xFE byte. */
for (i = 0; i < test_ctx.s_len - 1; i++) {
if (test_ctx.s_buff[i] == 0xFE && test_ctx.s_buff[i + 1] == 0x0D) {
if (i + 14 < test_ctx.s_len)
test_ctx.s_buff[i + 14] ^= 0xFF;
break;
}
}
* + encLen(2) + payloadLen(2) = 12 bytes,
* so the ciphertext starts 14 bytes past the first 0xFE byte. */
ExpectIntEQ(ech_find_extension(test_ctx.s_buff, &idx, TLSXT_ECH), 0);
if (EXPECT_SUCCESS() && (idx + 14 < test_ctx.s_len))
test_ctx.s_buff[idx + 14] ^= 0xFF;
/* Server processes the corrupted CH2.
* hpkeContext is preserved, TLSX_ECH_Parse correctly identifies the CH2
* round and sends decrypt_error. */
(void)wolfSSL_accept(test_ctx.s_ssl);
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(DECRYPT_ERROR));
if (EXPECT_SUCCESS()) {
(void)wolfSSL_accept(test_ctx.s_ssl);
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(DECRYPT_ERROR));
}
}
test_ssl_memio_cleanup(&test_ctx);
@@ -16388,65 +16437,14 @@ static int test_wolfSSL_Tls13_ECH_enable_disable(void)
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \
defined(WOLFSSL_TEST_ECH) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(WOLFSSL_NO_TLS12)
static int ech_tamper_seek_extension(byte* innerCh, word16* innerExtLen)
{
word16 idx;
byte sessionIdLen;
word16 cipherSuitesLen;
byte compressionLen;
idx = OPAQUE16_LEN + RAN_LEN;
sessionIdLen = innerCh[idx++];
idx += sessionIdLen;
ato16(innerCh + idx, &cipherSuitesLen);
idx += OPAQUE16_LEN + cipherSuitesLen;
compressionLen = innerCh[idx++];
idx += compressionLen;
ato16(innerCh + idx, innerExtLen);
idx += OPAQUE16_LEN;
return idx;
}
static int ech_tamper_find_extension(byte* innerCh, word16* idx_p,
word16 extType)
{
word16 idx;
word16 innerExtIdx;
word16 innerExtLen;
idx = innerExtIdx = ech_tamper_seek_extension(innerCh, &innerExtLen);
while (idx - innerExtIdx < innerExtLen) {
word16 type;
word16 len;
ato16(innerCh + idx, &type);
if (type == extType) {
*idx_p = idx;
return 0;
}
idx += OPAQUE16_LEN;
ato16(innerCh + idx, &len);
idx += OPAQUE16_LEN + len;
}
return BAD_FUNC_ARG;
}
static int ech_tamper_downgrade(byte* innerCh, word32 innerChLen)
{
int ret;
word16 idx;
word16 idx = 0;
(void)innerChLen;
ret = ech_tamper_find_extension(innerCh, &idx, TLSXT_SUPPORTED_VERSIONS);
ret = ech_find_extension(innerCh, &idx, TLSXT_SUPPORTED_VERSIONS);
if (ret == 0) {
/* change extension type to something unknown */
innerCh[idx] = 0xFA;
@@ -16464,7 +16462,7 @@ static int ech_tamper_padding(byte* innerCh, word32 innerChLen)
word16 innerExtLen;
/* get the unpadded length */
idx = ech_tamper_seek_extension(innerCh, &innerExtLen);
idx = ech_seek_extensions(innerCh, &innerExtLen);
idx += innerExtLen;
/* no padding, but the test would fail if the message is not incorrect...
@@ -16481,11 +16479,11 @@ static int ech_tamper_padding(byte* innerCh, word32 innerChLen)
static int ech_tamper_type(byte* innerCh, word32 innerChLen)
{
int ret;
word16 idx;
word16 idx = 0;
(void)innerChLen;
ret = ech_tamper_find_extension(innerCh, &idx, TLSXT_ECH);
ret = ech_find_extension(innerCh, &idx, TLSXT_ECH);
if (ret == 0) {
/* change type to outer */
innerCh[idx + 4] = ECH_TYPE_OUTER;
@@ -16499,12 +16497,12 @@ static int ech_tamper_type(byte* innerCh, word32 innerChLen)
static int ech_tamper_key_share(byte* innerCh, word32 innerChLen)
{
int ret;
word16 idx;
word16 idx = 0;
word16 len;
(void)innerChLen;
ret = ech_tamper_find_extension(innerCh, &idx, TLSXT_KEY_SHARE);
ret = ech_find_extension(innerCh, &idx, TLSXT_KEY_SHARE);
if (ret == 0) {
ato16(innerCh + idx + 8, &len);
if (len == 0) {