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

