Skip to content

TLS ECH Testing Improvements#9737

Merged
JacobBarthelmeh merged 10 commits intowolfSSL:masterfrom
sebastian-carpenter:tls-ech-confirmation-fix
Mar 11, 2026
Merged

TLS ECH Testing Improvements#9737
JacobBarthelmeh merged 10 commits intowolfSSL:masterfrom
sebastian-carpenter:tls-ech-confirmation-fix

Conversation

@sebastian-carpenter
Copy link
Contributor

@sebastian-carpenter sebastian-carpenter commented Feb 4, 2026

Description

Original issue stems from wolfssl-examples/tls/client-ech not working. This issue was a confirmation value mismatch between Cloudflare and our ECH client implementation. The confirmation value is present in the HelloRetryRequest's encrypted_client_hello extension.

  • Confirmation mismatch cause:
    • HRR requires a synthetic client hello to be hashed (as in normal TLS transcript) -> ECH transcript hash was not handling the synthetic hash correctly.
    • Random value used for HKDF was incorrect.
  • Confirmation mismatch was resolved by refactoring most of the transcript code for ECH.
    • Managed to remove a transcript hash that seemed unecessary. (hsHashesEchInner)
    • Merged most of the client and server calculation for ECH transcript into similar functions.
    • Random value only copied into client hello inner once now
  • Fixed segfault when server does not respond with a confirmation value
  • Added OuterExtensions extension support to the server to allow testing against openssl s_client (second confirmation that transcript hash is now correct)
    • OuterExtensions will copy extensions from the outer hello into the inner hello.
    • Purpose is to have the client send less stuff over the wire.

Issues fixed when writing tests:

  • ssl->options.useEch got out of sync with whether echConfigs is actually a NULL pointer. Removed it.
    • This option was not always combined with echDisabled too, so updated relevant areas.
  • When generating a config the item is appended to the end of the LL of ech Configs. Seems inefficient so changed it to insert at the beginning.
  • Private SNI was never verified and, in fact, the server's private SNI was replaced by the public SNI. Added field in WOLFSSL_ECH struct to store the private SNI and an enum to keep track of when to verify the public vs the private SNI.
    • Tried to preserve the SNI verification procedure regardless of if outer or inner SNI is processed.
  • A wolfssl server with ECH enabled and echConfigs generated would expect ECH connections. Therefore, a client connecting without ECH would fail.
  • Some tests were not checking all return values or did not test distinct issues.

Fixed some non-compliance with the ECH spec here and there too.

Based fixes off of esni draft 25 (https://www.ietf.org/archive/id/draft-ietf-tls-esni-25.html)

Addresses github issue #6925

Testing

Tested the client against a 'wild' ECH server (Cloudflare). This test is part of a wolfssl-example PR (wolfSSL/wolfssl-examples#556).

Added a github workflow to test OpenSSL interoperability. It tests a basic ECH connection between wolf client -> ossl server and ossl client -> wolf server.

Added extra tests to more strenuously test ECH:

  • test_wolfSSL_SubTls13_ECH
    • Verify that ECH does not work below TLS 1.3
  • test_wolfSSL_Tls13_ECH_no_private_name
    • Test what happens when wolfSSL_UseSNI() is never called on either the client or server and a connection is started.
  • test_wolfSSL_Tls13_ECH_bad_configs
    • Make sure neither server nor client will connect when ech configs are incorrect. Mainly a check that SNI's match.
  • test_wolfSSL_Tls13_ECH_new_config
    • Check that the server tries more than just the first config.
  • test_wolfSSL_Tls13_ECH_GREASE
    • End to end test of GREASE ECH: checks that a server supports ECH / retrieves ECH configs from a server
  • test_wolfSSL_Tls13_ECH_disable_conn
    • Test if a mismatch in ECH support between server / client does not cause failures. Additionally, that ECH can even be turned off. It also tests public/private SNI verification through a callback.
  • test_wolfSSL_Tls13_ECH_enable_disable
    • Stress the enable/disable API for ECH

Checklist

  • [X ] added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

@sebastian-carpenter sebastian-carpenter self-assigned this Feb 4, 2026
@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-confirmation-fix branch 3 times, most recently from dee5c47 to 740c55f Compare February 5, 2026 00:00
Copy link
Contributor

@dgarske dgarske left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good. I'd like this PR to include testing as well. I don't see any. Thanks

@sebastian-carpenter sebastian-carpenter linked an issue Feb 10, 2026 that may be closed by this pull request
@sebastian-carpenter
Copy link
Contributor Author

Code looks good. I'd like this PR to include testing as well. I don't see any. Thanks

I've added testing in api.c. Let me know if there's any tests you disagree with or which I should add.

I'll be making the github workflow to test against ECH enabled openssl s_client in the meantime.

@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-confirmation-fix branch 8 times, most recently from 375fbf9 to 97ba44b Compare February 20, 2026 20:24
@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-confirmation-fix branch 3 times, most recently from 40e728e to fad33e9 Compare February 26, 2026 16:15
@sebastian-carpenter sebastian-carpenter changed the title TLS ECH confirmation fix and OuterExtension addition TLS ECH Testing Improvements Feb 26, 2026
@sebastian-carpenter sebastian-carpenter marked this pull request as ready for review February 26, 2026 17:25
@dgarske dgarske self-requested a review February 26, 2026 18:06
@dgarske
Copy link
Contributor

dgarske commented Feb 26, 2026

Jenkins retest this please. Appears all passed, but FIPS 140-3 test reports failed. I don't think its an issue with this PR

@philljj philljj added the For This Release Release version 5.9.0 label Mar 9, 2026
@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-confirmation-fix branch from d922d36 to 06717c1 Compare March 9, 2026 21:23
@sebastian-carpenter
Copy link
Contributor Author

Jenkins retest this please. Two were canceled and the other failures seem like they might have been an accident.

@douzzer douzzer added Staged Staged for merge pending final test results and review and removed Staged Staged for merge pending final test results and review labels Mar 10, 2026
Copy link
Contributor

@douzzer douzzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valgrind UB:

[quantum-safe-wolfssl-all-intelasm-sp-asm-valgrind] [8 of 29] [0826bd007e]
    configure...   real 0m9.828s  user 0m6.227s  sys 0m4.663s
    build...   real 0m12.604s  user 2m37.335s  sys 0m7.508s
    valgrind --leak-check=full unit.test...==35357== Conditional jump or move depends on uninitialised value(s)
6b6ad38e4f (<david@wolfssl.com> 2023-01-18 11:30:46 -0800 5105)         if (ret == 0) {
==35357==    at 0x5107F53: EchCheckAcceptance (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/tls13.c:5105)
f6555fd753 (<john.bland@wolfssl.com> 2023-10-02 01:26:14 -0400 5684)             ret = EchCheckAcceptance(ssl, args->acceptLabel,
==35357==    by 0x5107F53: DoTls13ServerHello (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/tls13.c:5684)
94157634e1 (<sean@wolfssl.com> 2018-04-13 11:53:42 +1000 13017)         ret = DoTls13ServerHello(ssl, input, inOutIdx, size, &type);
==35357==    by 0x510DF5D: DoTls13HandShakeMsgType (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/tls13.c:13017)
2b1e9973ec (<sparkinson@secure-crypto.org> 2016-11-24 01:31:07 +1000 13418)         ret = DoTls13HandShakeMsgType(ssl, input, inOutIdx, type, size,
==35357==    by 0x5110800: DoTls13HandShakeMsg (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/tls13.c:13418)
2b1e9973ec (<sparkinson@secure-crypto.org> 2016-11-24 01:31:07 +1000 23266)                         ret = DoTls13HandShakeMsg(ssl,
==35357==    by 0x5072DBC: DoProcessReplyEx (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/internal.c:23266)
4795e0d920 (<juliusz@wolfssl.com> 2024-12-12 19:06:05 +0100 23648)     ret = DoProcessReplyEx(ssl, allowSocketErr);
==35357==    by 0x50739F6: ProcessReplyEx (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/internal.c:23648)
4795e0d920 (<juliusz@wolfssl.com> 2024-12-12 19:06:05 +0100 23641)     return ProcessReplyEx(ssl, 0);
==35357==    by 0x50739F6: ProcessReply (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/internal.c:23641)
^6b88eb05b (<todd@yassl.com> 2011-02-05 11:14:47 -0800 6398)                 if ( (ssl->error = ProcessReply(ssl)) < 0) {
==35357==    by 0x50AD44A: wolfSSL_connect (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/ssl.c:6398)
7259351a3f (<sean@wolfssl.com> 2023-05-31 17:12:16 +1000 4863)             ret = wolfSSL_connect(ctx->c_ssl);
==35357==    by 0x413F46A: test_ssl_memio_do_handshake (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:4863)
bae4e13171 (<sebastian@wolfssl.com> 2026-02-12 10:50:45 -0700 14628)     ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
==35357==    by 0x4141953: test_wolfSSL_Tls13_ECH_bad_configs_ex (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:14628)
bae4e13171 (<sebastian@wolfssl.com> 2026-02-12 10:50:45 -0700 14674)     ExpectIntEQ(test_wolfSSL_Tls13_ECH_bad_configs_ex(1, 0), WOLFSSL_SUCCESS);
==35357==    by 0x414229B: test_wolfSSL_Tls13_ECH_bad_configs (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:14674)
541ea51ad5 (<sean@wolfssl.com> 2023-05-26 15:49:14 +1000 34655)             ret = testCases[i].func();
==35357==    by 0x414DC99: ApiTest (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:34655)
541ea51ad5 (<sean@wolfssl.com> 2023-05-26 15:49:14 +1000 285)             ret = ApiTest();
==35357==    by 0x406D004: unit_test (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/unit.c:285)
==35357==    by 0x592E253: (below main) (/usr/src/debug/sys-libs/glibc-2.43/glibc-2.43/csu/../sysdeps/nptl/libc_start_call_main.h:59)
==35357==  Uninitialised value was created by a heap allocation
==35357==    at 0x4DD5858: malloc (/tmp/portage/dev-debug/valgrind-3.26.0_p1/work/valgrind-3.26.0/coregrind/m_replacemalloc/vg_replace_malloc.c:447)
038be95a4a (<douzzer@wolfssl.com> 2024-03-29 11:44:52 -0500 11381)     tmp = (byte*)XMALLOC(newSz, ssl->heap, DYNAMIC_TYPE_OUT_BUFFER);
==35357==    by 0x50536FA: GrowOutputBuffer (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/internal.c:11381)
b32bc2ce9f (<todd@yassl.com> 2012-01-24 13:19:03 -0800 11547)         if (GrowOutputBuffer(ssl, size) < 0)
==35357==    by 0x50536FA: CheckAvailableSize (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/internal.c:11547)
2b1e9973ec (<sparkinson@secure-crypto.org> 2016-11-24 01:31:07 +1000 7586)     if ((ret = CheckAvailableSize(ssl, sendSz)) != 0)
==35357==    by 0x510A2BD: SendTls13ServerHello (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/tls13.c:7586)
5f14de33e7 (<sean@wolfssl.com> 2017-11-20 11:07:32 +1000 14800)                 if ((ssl->error = SendTls13ServerHello(ssl,
==35357==    by 0x5111FF9: wolfSSL_accept_TLSv13 (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/src/tls13.c:14800)
7259351a3f (<sean@wolfssl.com> 2023-05-31 17:12:16 +1000 4888)             ret = wolfSSL_accept(ctx->s_ssl);
==35357==    by 0x413F503: test_ssl_memio_do_handshake (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:4888)
bae4e13171 (<sebastian@wolfssl.com> 2026-02-12 10:50:45 -0700 14628)     ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
==35357==    by 0x4141953: test_wolfSSL_Tls13_ECH_bad_configs_ex (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:14628)
bae4e13171 (<sebastian@wolfssl.com> 2026-02-12 10:50:45 -0700 14674)     ExpectIntEQ(test_wolfSSL_Tls13_ECH_bad_configs_ex(1, 0), WOLFSSL_SUCCESS);
==35357==    by 0x414229B: test_wolfSSL_Tls13_ECH_bad_configs (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:14674)
541ea51ad5 (<sean@wolfssl.com> 2023-05-26 15:49:14 +1000 34655)             ret = testCases[i].func();
==35357==    by 0x414DC99: ApiTest (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/api.c:34655)
541ea51ad5 (<sean@wolfssl.com> 2023-05-26 15:49:14 +1000 285)             ret = ApiTest();
==35357==    by 0x406D004: unit_test (/tmp/tmp.4346_28411/wolfssl_test_workdir.15679/wolfssl/tests/unit.c:285)
==35357==    by 0x592E253: (below main) (/usr/src/debug/sys-libs/glibc-2.43/glibc-2.43/csu/../sysdeps/nptl/libc_start_call_main.h:59)
==35357== 
   real 19m13.945s  user 19m17.687s  sys 0m2.226s
    scenario started 2026-03-10T17:24:32.825740Z, real elapsed 19m36.389778s
    quantum-safe-wolfssl-all-intelasm-sp-asm-valgrind fail_analysis
    failed config: 'EXTRA_CPPFLAGS=-Werror' '--srcdir' '.' '--disable-jobserver' '--enable-option-checking=fatal' '--enable-all' '--enable-acert' '--enable-dtls13' '--enable-dtls-mtu' '--enable-dtls-frag-ch' '--enable-dtlscid' '--enable-quic' '--with-sys-crypto-policy' '--enable-intelasm' '--enable-sp-asm' '--enable-experimental' '--enable-kyber=yes,original' '--enable-lms' '--enable-xmss' '--enable-dilithium' '--enable-slhdsa' '--enable-dual-alg-certs' '--disable-qt' 'LDFLAGS=-ggdb -fno-omit-frame-pointer' '--disable-optflags' 'CFLAGS=-DTEST_ALWAYS_RUN_TO_END -ggdb -fno-omit-frame-pointer -O2' 'CPPFLAGS=-DNO_WOLFSSL_CIPHER_SUITE_TEST -DWOLFSSL_OLD_PRIME_CHECK -pedantic -Wdeclaration-after-statement -Wnull-dereference -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE'

@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-confirmation-fix branch 2 times, most recently from 9ace9a3 to d5e6996 Compare March 10, 2026 20:58
@douzzer
Copy link
Contributor

douzzer commented Mar 11, 2026

retest this please

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

tests/api.c:1

  • The comment says the call 'should be able to retrieve length with NULL buffer', but the test currently expects a non-success return. If the intended contract is 'returns an error code but still updates outputLen', the comment should be updated to reflect that; otherwise change the expectation to match the intended API behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-confirmation-fix branch 2 times, most recently from 977679b to f691992 Compare March 11, 2026 17:34
@sebastian-carpenter
Copy link
Contributor Author

Jenkins retest this please. Timeout hit, probably spent too long waiting.

@JacobBarthelmeh JacobBarthelmeh dismissed douzzer’s stale review March 11, 2026 21:10

Comment was addressed.

Scenario run: quantum-safe-wolfssl-all-intelasm-sp-asm-valgrind
Code tested: commit bb7c6a13c89da5da9af46b777838a21f2eb431c8 (ECH tidying)
Result: quantum-safe-wolfssl-all-intelasm-sp-asm-valgrind OK
@JacobBarthelmeh JacobBarthelmeh merged commit c15715e into wolfSSL:master Mar 11, 2026
457 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For This Release Release version 5.9.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't build ECH example on M1 Mac

7 participants