Skip to content

curl ECH+QUIC fix#30727

Closed
sftcd wants to merge 13 commits intoopenssl:masterfrom
sftcd:ech-fixes-for-quic
Closed

curl ECH+QUIC fix#30727
sftcd wants to merge 13 commits intoopenssl:masterfrom
sftcd:ech-fixes-for-quic

Conversation

@sftcd
Copy link
Copy Markdown
Contributor

@sftcd sftcd commented Apr 8, 2026

This PR tweaks how custom extensions are handled by ECH and forces the session id length in the client hello to zero in order to enable use of ECH with QUIC for a curl build.

This PR is a fix for #30697

@mattcaswell
Copy link
Copy Markdown
Member

CI failures look relevant.

Is it possible to have a test for this? I assume our own QUIC stack is similarly affected by this, so perhaps something in quicapitest.c which sets up a QUIC connection with ECH and verifies that a connection is established.

@sftcd
Copy link
Copy Markdown
Contributor Author

sftcd commented Apr 8, 2026

CI failures look relevant.

Looking at that now.

Is it possible to have a test for this? I assume our own QUIC stack is similarly affected by this, so perhaps something in quicapitest.c which sets up a QUIC connection with ECH and verifies that a connection is established.

Will take a look at that. (In an hour-ish.) It should be possible given that this seems to work for openssl s_client -quic as well, but not sure if we'll see something new with a QUIC+ECH server setup. (Worth finding out though.)

@openssl-machine openssl-machine added the approval: review pending This pull request needs review by a committer label Apr 8, 2026
@mattcaswell
Copy link
Copy Markdown
Member

This LGTM so far.

@mattcaswell mattcaswell added branch: master Applies to master branch triaged: bug The issue/pr is/fixes a bug labels Apr 8, 2026
@t8m t8m added the branch: 4.0 Applies to openssl-4.0 label Apr 8, 2026
Comment thread ssl/statem/statem_clnt.c
Comment on lines -1492 to +1495
sess_id_len = sizeof(s->tmp_session_id);
if (SSL_IS_QUIC_HANDSHAKE(s))
sess_id_len = 0;
else
sess_id_len = sizeof(s->tmp_session_id);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am curious why this is needed. What is different between QUIC and regular TLS connection that needs this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That was more or less done by observation;-) I didn't find where the QUIC code does it, but the session id in the ClientHello is 'legacy' for QUIC, which I guess uses QUIC connection ids instead. For TLS/TCP cases we want a standard 32 octet sized session id.

Copy link
Copy Markdown
Member

@mattcaswell mattcaswell Apr 9, 2026

Choose a reason for hiding this comment

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

Hmm. I actually think is more to do with whether the SSL_OP_ENABLE_MIDDLEBOX_COMPAT option is set. This is set by default, but if it's NOT set (which it isn't for QUIC), and we're TLSv1.3 only, then the sess_id_len is 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Edited my note above to actually make sense and not say the opposite of what I actually meant!!

Copy link
Copy Markdown
Contributor Author

@sftcd sftcd Apr 9, 2026

Choose a reason for hiding this comment

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

so if compat mode is set, then use a 32 octet session id, but otherwise zero length? (that matches 8446 all right) - that seems to work ok with curl etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

just pushed a version that I think does that

@sftcd
Copy link
Copy Markdown
Contributor Author

sftcd commented Apr 8, 2026

The fixup above adds a test, which, surprisingly, just worked when I figured out the test code. There are a couple of TODO(ECH) things in the test code though where I could do with some guidance or hints.

@sftcd
Copy link
Copy Markdown
Contributor Author

sftcd commented Apr 9, 2026

After much faffing about to find the right way to make the CI happy, tests now pass - but there's still the couple of TODO(ECH) things in the test code:

  • the first might be ok as-is, not sure - it loads the default provider as without that we get a fail reading in the ECH PEM data (I don't really grok what setup_tests() is doing with loading providers, nor why tbh)
  • the second is more worrisome - according to valgrind, we're leaking 277 bytes when running the new test, mostly by not freeing up some BIO * things inside the qtserv structure (I think). My worry is that might be related to the transcript buffer being handled differently, which might be tricky to figure out (there are a couple of BIO_free calls in ssl/ech/ech_internal.c that could be related, but I'm absolutely not even close to sure in this case;-)

Other than that, if it makes is easier, happy to squash the loads of fixups here, just tell me.

But on the whole, it's a nice surprise that ECH seems to just work (almost if not quite correctly) for QUIC:-)

@mattcaswell mattcaswell modified the milestones: 4.0.0, 4.0.0 Final Release Apr 9, 2026
Comment thread test/quicapitest.c Outdated
Comment thread test/quicapitest.c Outdated
Comment thread test/quicapitest.c Outdated
@sftcd
Copy link
Copy Markdown
Contributor Author

sftcd commented Apr 9, 2026

fixup above have good changes suggested by @mattcaswell

mattcaswell
mattcaswell previously approved these changes Apr 9, 2026
@mattcaswell mattcaswell requested a review from t8m April 9, 2026 10:39
t8m
t8m previously approved these changes Apr 9, 2026
@t8m t8m requested a review from mattcaswell April 9, 2026 15:33
@t8m t8m added the tests: present The PR has suitable tests present label Apr 9, 2026
Comment thread ssl/statem/statem_clnt.c
if ((s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0)
sess_id_len = sizeof(s->tmp_session_id);
else
sess_id_len = 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to end up here and end up not negotiating TLSv1.3? The conditions at the moment for sending a zero length sess id are minimum protocol is TLSv1.3 AND middlebox mode is off.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ECH is only defined for TSLv1.3 though, would have to look to check if there's a code path here before that's enforced

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

could add a version check there too if you'd prefer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adding a check for 1.3 if we're doing ECH (and bailing if not) just before here works fine - want me to push that up?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it looks like this:

$ git diff
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index 819555f24a..36c698c163 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -1489,6 +1489,10 @@ __owur CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, WPACKET *pk
 #ifndef OPENSSL_NO_ECH
     /* same session ID is used for inner/outer when doing ECH */
     if (s->ext.ech.es != NULL) {
+        if (s->version != TLS1_3_VERSION) {
+            SSLfatal(s, SSL_AD_PROTOCOL_VERSION, SSL_R_UNSUPPORTED_SSL_VERSION);
+            return CON_FUNC_ERROR;
+        }
         if ((s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0)
             sess_id_len = sizeof(s->tmp_session_id);
         else

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As an FYI, with the code above our TLSv12 test case fails there, so I'd say it's good to add. I'll push that (and apologies for forcing a re-review)

@openssl-machine openssl-machine added approval: done This pull request has the required number of approvals approval: ready to merge The 24 hour grace period has passed, ready to merge and removed approval: review pending This pull request needs review by a committer approval: done This pull request has the required number of approvals labels Apr 10, 2026
@openssl-machine
Copy link
Copy Markdown
Collaborator

This pull request is ready to merge

openssl-machine pushed a commit that referenced this pull request Apr 11, 2026
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Sat Apr 11 18:29:37 2026
(Merged from #30727)
@npajkovsky
Copy link
Copy Markdown

Thank you. Merged to master and openssl-4.0.

@npajkovsky npajkovsky closed this Apr 11, 2026
openssl-machine pushed a commit that referenced this pull request Apr 11, 2026
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Sat Apr 11 18:34:40 2026
(Merged from #30727)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval: ready to merge The 24 hour grace period has passed, ready to merge branch: master Applies to master branch branch: 4.0 Applies to openssl-4.0 tests: present The PR has suitable tests present triaged: bug The issue/pr is/fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants