--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], Simon McVittie <[email protected]>
Control: affects -1 + src:gnutls28
User: [email protected]
Usertags: pu
Hello,
I would like to fix #1130152 for trixie. Simon has done all the heavy
and not so heavy lifting on this, let's quote #1130152:
8X-------------------
A regression in GnuTLS 3.8.5, which started shuffling the extensions
order, causes an interoperability issue leading to handshake failures
with some SSL/TLS servers. I'm reporting this at important severity since
it's an interop regression affecting an unknown number of remote services.
From the linked regression report https://github.com/luakit/luakit/issues/1101,
it seems that at the time of writing, search.dismail.de is a good test-case,
for example:
[...]
# gnutls-cli search.dismail.de
Processed 150 CA certificate(s).
Resolving 'search.dismail.de:443'...
Connecting to '128.140.68.142:443'...
*** Fatal error: A TLS fatal alert has been received.
*** Received alert [47]: Illegal parameter
[...]
I've confirmed that 3.8.12-2 in forky and 3.7.9-2+deb12u6 in bookworm
are both unaffected by this: they successfully connect to that server,
with gnutls-cli output that includes "Handshake was completed". (Press
Ctrl+D to exit after seeing this.)
This appears to have been fixed by
https://gitlab.com/gnutls/gnutls/-/merge_requests/1930
after the 3.8.9 release, commit
[...]
8X-------------------
I have verified the proposed change and that it fixes the issue.
TIA, cu Andreas
--
"You people are noisy," Nia said.
I made the gesture of agreement.
diff --git a/debian/changelog b/debian/changelog
index ee6981c..867d2f8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+gnutls28 (3.8.9-3+deb13u3) trixie; urgency=medium
+
+ [ Simon McVittie ]
+ * d/p/51_handshake-only-shuffle-extensions-in-the-first-Client-Hel.patch:
+ Preserve extension order across client Hello retry.
+ This resolves an interop regression in 3.8.5 with servers that enforce
+ the RFC requirement that the Client Hello after a Hello Retry Request
+ has the same extensions as the original Client Hello, in the same order
+ (Closes: #1130152)
+
+ -- Andreas Metzler <[email protected]> Sat, 14 Mar 2026 07:19:14 +0100
+
gnutls28 (3.8.9-3+deb13u2) trixie-security; urgency=high
* libgnutls: Fix name constraint processing performance issue
diff --git a/debian/patches/51_handshake-only-shuffle-extensions-in-the-first-Client-Hel.patch b/debian/patches/51_handshake-only-shuffle-extensions-in-the-first-Client-Hel.patch
new file mode 100644
index 0000000..dd48dfa
--- /dev/null
+++ b/debian/patches/51_handshake-only-shuffle-extensions-in-the-first-Client-Hel.patch
@@ -0,0 +1,213 @@
+From: Daiki Ueno <[email protected]>
+Date: Sun, 9 Feb 2025 10:31:20 +0900
+Subject: handshake: only shuffle extensions in the first Client Hello
+
+RFC 8446 section 4.1.2 states that the second Client Hello after HRR
+should preserve the same content as the first Client Hello with
+limited exceptions. Since GnuTLS 3.8.5, however, the library started
+shuffling the order of extensions for privacy reasons and that didn't
+comply with the RFC, leading to a connectivity issue against the
+server configuration with a stricter check on that.
+
+Signed-off-by: Daiki Ueno <[email protected]>
+Origin: upstream, 3.8.10, commit:dc5ee80c3a28577e9de0f82fb08164e4c02b96af
+Bug: https://gitlab.com/gnutls/gnutls/-/work_items/1660
+Bug-Debian: https://bugs.debian.org/1130152
+Bug-SteamRT: steamrt/tasks#938
+---
+ lib/gnutls_int.h | 4 +++
+ lib/hello_ext.c | 41 +++++++++++++++++++------------
+ lib/state.c | 2 ++
+ tests/tls13/hello_retry_request.c | 51 ++++++++++++++++++++++++++++++++++++---
+ 4 files changed, 79 insertions(+), 19 deletions(-)
+
+diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
+index d10a028..572de5a 100644
+--- a/lib/gnutls_int.h
++++ b/lib/gnutls_int.h
+@@ -1666,6 +1666,10 @@ typedef struct {
+ /* Compression method for certificate compression */
+ gnutls_compression_method_t compress_certificate_method;
+
++ /* To shuffle extension sending order */
++ extensions_t client_hello_exts[MAX_EXT_TYPES];
++ bool client_hello_exts_set;
++
+ /* If you add anything here, check _gnutls_handshake_internal_state_clear().
+ */
+ } internals_st;
+diff --git a/lib/hello_ext.c b/lib/hello_ext.c
+index 45237b8..94e6d86 100644
+--- a/lib/hello_ext.c
++++ b/lib/hello_ext.c
+@@ -438,8 +438,6 @@ int _gnutls_gen_hello_extensions(gnutls_session_t session,
+ int pos, ret;
+ size_t i;
+ hello_ext_ctx_st ctx;
+- /* To shuffle extension sending order */
+- extensions_t indices[MAX_EXT_TYPES];
+
+ msg &= GNUTLS_EXT_FLAG_SET_ONLY_FLAGS_MASK;
+
+@@ -469,26 +467,39 @@ int _gnutls_gen_hello_extensions(gnutls_session_t session,
+ ret - 4);
+ }
+
+- /* Initializing extensions array */
+- for (i = 0; i < MAX_EXT_TYPES; i++) {
+- indices[i] = i;
+- }
++ if (msg & GNUTLS_EXT_FLAG_CLIENT_HELLO &&
++ !session->internals.client_hello_exts_set) {
++ /* Initializing extensions array */
++ for (i = 0; i < MAX_EXT_TYPES; i++) {
++ session->internals.client_hello_exts[i] = i;
++ }
+
+- if (!session->internals.priorities->no_shuffle_extensions) {
+- /* Ordering padding and pre_shared_key as last extensions */
+- swap_exts(indices, MAX_EXT_TYPES - 2, GNUTLS_EXTENSION_DUMBFW);
+- swap_exts(indices, MAX_EXT_TYPES - 1,
+- GNUTLS_EXTENSION_PRE_SHARED_KEY);
++ if (!session->internals.priorities->no_shuffle_extensions) {
++ /* Ordering padding and pre_shared_key as last extensions */
++ swap_exts(session->internals.client_hello_exts,
++ MAX_EXT_TYPES - 2, GNUTLS_EXTENSION_DUMBFW);
++ swap_exts(session->internals.client_hello_exts,
++ MAX_EXT_TYPES - 1,
++ GNUTLS_EXTENSION_PRE_SHARED_KEY);
+
+- ret = shuffle_exts(indices, MAX_EXT_TYPES - 2);
+- if (ret < 0)
+- return gnutls_assert_val(ret);
++ ret = shuffle_exts(session->internals.client_hello_exts,
++ MAX_EXT_TYPES - 2);
++ if (ret < 0)
++ return gnutls_assert_val(ret);
++ }
++ session->internals.client_hello_exts_set = true;
+ }
+
+ /* hello_ext_send() ensures we don't send duplicates, in case
+ * of overridden extensions */
+ for (i = 0; i < MAX_EXT_TYPES; i++) {
+- size_t ii = indices[i];
++ size_t ii;
++
++ if (msg & GNUTLS_EXT_FLAG_CLIENT_HELLO)
++ ii = session->internals.client_hello_exts[i];
++ else
++ ii = i;
++
+ if (!extfunc[ii])
+ continue;
+
+diff --git a/lib/state.c b/lib/state.c
+index 020f212..8090686 100644
+--- a/lib/state.c
++++ b/lib/state.c
+@@ -518,6 +518,8 @@ static void handshake_internal_state_clear1(gnutls_session_t session)
+
+ session->internals.hrr_cs[0] = CS_INVALID_MAJOR;
+ session->internals.hrr_cs[1] = CS_INVALID_MINOR;
++
++ session->internals.client_hello_exts_set = false;
+ }
+
+ /* This function will clear all the variables in internals
+diff --git a/tests/tls13/hello_retry_request.c b/tests/tls13/hello_retry_request.c
+index f407b64..6c5f698 100644
+--- a/tests/tls13/hello_retry_request.c
++++ b/tests/tls13/hello_retry_request.c
+@@ -51,14 +51,37 @@ static void tls_log_func(int level, const char *str)
+ }
+
+ #define HANDSHAKE_SESSION_ID_POS 34
++#define MAX_EXT_TYPES 64
+
+ struct ctx_st {
+ unsigned hrr_seen;
+ unsigned hello_counter;
+ uint8_t session_id[32];
+ size_t session_id_len;
++ unsigned extensions[MAX_EXT_TYPES];
++ size_t extensions_size1;
++ size_t extensions_size2;
+ };
+
++static int ext_callback(void *_ctx, unsigned tls_id, const unsigned char *data,
++ unsigned size)
++{
++ struct ctx_st *ctx = _ctx;
++ if (ctx->hello_counter == 0) {
++ assert(ctx->extensions_size1 < MAX_EXT_TYPES);
++ ctx->extensions[ctx->extensions_size1++] = tls_id;
++ } else {
++ assert(ctx->extensions_size2 < MAX_EXT_TYPES);
++ if (tls_id != ctx->extensions[ctx->extensions_size2]) {
++ fail("extension doesn't match at position %zu, %u != %u\n",
++ ctx->extensions_size2, tls_id,
++ ctx->extensions[ctx->extensions_size2]);
++ }
++ ctx->extensions_size2++;
++ }
++ return 0;
++}
++
+ static int hello_callback(gnutls_session_t session, unsigned int htype,
+ unsigned post, unsigned int incoming,
+ const gnutls_datum_t *msg)
+@@ -73,15 +96,25 @@ static int hello_callback(gnutls_session_t session, unsigned int htype,
+ post == GNUTLS_HOOK_POST) {
+ size_t session_id_len;
+ uint8_t *session_id;
++ unsigned pos = HANDSHAKE_SESSION_ID_POS;
++ gnutls_datum_t mmsg;
++ int ret;
+
+- assert(msg->size > HANDSHAKE_SESSION_ID_POS + 1);
+- session_id_len = msg->data[HANDSHAKE_SESSION_ID_POS];
+- session_id = &msg->data[HANDSHAKE_SESSION_ID_POS + 1];
++ assert(msg->size > pos + 1);
++ session_id_len = msg->data[pos];
++ session_id = &msg->data[pos + 1];
++
++ SKIP8(pos, msg->size);
++ SKIP16(pos, msg->size);
++ SKIP8(pos, msg->size);
++
++ mmsg.data = &msg->data[pos];
++ mmsg.size = msg->size - pos;
+
+ if (ctx->hello_counter > 0) {
+ assert(msg->size > 4);
+ if (msg->data[0] != 0x03 || msg->data[1] != 0x03) {
+- fail("version is %d.%d expected 3,3\n",
++ fail("version is %d.%d expected 3.3\n",
+ (int)msg->data[0], (int)msg->data[1]);
+ }
+
+@@ -95,6 +128,12 @@ static int hello_callback(gnutls_session_t session, unsigned int htype,
+ ctx->session_id_len = session_id_len;
+ memcpy(ctx->session_id, session_id, session_id_len);
+
++ ret = gnutls_ext_raw_parse(ctx, ext_callback, &mmsg, 0);
++ if (ret < 0) {
++ fail("unable to parse extensions: %s\n",
++ gnutls_strerror(ret));
++ }
++
+ ctx->hello_counter++;
+ }
+
+@@ -164,6 +203,10 @@ void doit(void)
+ myfail("group doesn't match the expected: %s\n",
+ gnutls_group_get_name(gnutls_group_get(server)));
+
++ if (ctx.extensions_size1 != ctx.extensions_size2)
++ myfail("the number of extensions don't match in second Client Hello: %zu != %zu\n",
++ ctx.extensions_size1, ctx.extensions_size2);
++
+ gnutls_bye(client, GNUTLS_SHUT_WR);
+ gnutls_bye(server, GNUTLS_SHUT_WR);
+
diff --git a/debian/patches/series b/debian/patches/series
index 8270734..6d55279 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -21,3 +21,4 @@
50_0007-x509-name_constraints-implement-name_constraints_nod.patch
50_0008-x509-name_constraints-make-types_with_empty_intersec.patch
50_0009-x509-name_constraints-name_constraints_node_list_int.patch
+51_handshake-only-shuffle-extensions-in-the-first-Client-Hel.patch
signature.asc
Description: PGP signature
--- End Message ---