From 69a18d118b0f695d6cdd94e92fa20cc13850ef08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:37:51 +0000 Subject: [PATCH 1/9] Enable hybrid TLS tests on AL3 CI Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29820c09c03..24394b9980a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: ./scripts/ci-checks.sh mkdir build cd build - cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCLANG_TIDY=ON .. + cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCLANG_TIDY=ON -DTEST_HYBRID_TLS_GROUPS=ON .. ninja shell: bash From 0bf7387f4e1c4211621cda27e58bfc4b4be10b2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:43:36 +0000 Subject: [PATCH 2/9] Remove hybrid TLS test flag Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- .github/workflows/ci-al4.yml | 2 +- .github/workflows/ci.yml | 2 +- CMakeLists.txt | 22 +++++----------------- src/tls/test/main.cpp | 7 +------ tests/ci-buckets.txt | 2 +- 5 files changed, 9 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci-al4.yml b/.github/workflows/ci-al4.yml index ca1e74f8f7a..0f7b4bd94f0 100644 --- a/.github/workflows/ci-al4.yml +++ b/.github/workflows/ci-al4.yml @@ -49,7 +49,7 @@ jobs: git config --global --add safe.directory /__w/CCF/CCF mkdir build cd build - cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DTEST_HYBRID_TLS_GROUPS=ON .. + cmake -GNinja -DCMAKE_BUILD_TYPE=Debug .. ninja shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24394b9980a..29820c09c03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: ./scripts/ci-checks.sh mkdir build cd build - cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCLANG_TIDY=ON -DTEST_HYBRID_TLS_GROUPS=ON .. + cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCLANG_TIDY=ON .. ninja shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cf14b23386..07e6dfa7f1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,12 +134,6 @@ option(LONG_TESTS "Enable long end-to-end tests" OFF) option(USE_SNMALLOC "Link against snmalloc" ON) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/snmalloc.cmake) -option( - TEST_HYBRID_TLS_GROUPS - "Assert that TLS handshakes negotiate a hybrid post-quantum group, which requires OpenSSL 3.5 or later" - OFF -) - option(CLANG_TIDY "Run clang-tidy on the codebase" OFF) option( @@ -881,10 +875,6 @@ if(BUILD_TESTS) add_unit_test(tls_test ${CMAKE_CURRENT_SOURCE_DIR}/src/tls/test/main.cpp) target_link_libraries(tls_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) - target_compile_definitions( - tls_test - PRIVATE TEST_HYBRID_TLS_GROUPS=$ - ) add_unit_test( base64_test @@ -1360,13 +1350,11 @@ if(BUILD_TESTS) BUCKET bucket_c ) - if(TEST_HYBRID_TLS_GROUPS) - add_e2e_test( - NAME tls_groups_test - PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/tls_groups.py - BUCKET bucket_a - ) - endif() + add_e2e_test( + NAME tls_groups_test + PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/tls_groups.py + BUCKET bucket_a + ) if(CLIENT_PROTOCOLS_TEST) add_e2e_test( diff --git a/src/tls/test/main.cpp b/src/tls/test/main.cpp index e36106e4998..3436ca48189 100644 --- a/src/tls/test/main.cpp +++ b/src/tls/test/main.cpp @@ -526,10 +526,6 @@ class InspectableServer : public tls::Server } }; -#ifndef TEST_HYBRID_TLS_GROUPS -# define TEST_HYBRID_TLS_GROUPS 0 -#endif - // Hybrid groups offered by src/tls/context.h, in the order they are offered constexpr auto secp384r1_mlkem1024 = "SecP384r1MLKEM1024"; constexpr auto secp256r1_mlkem768 = "SecP256r1MLKEM768"; @@ -607,8 +603,7 @@ TEST_CASE("group negotiation") } } -TEST_CASE( - "hybrid group negotiation" * doctest::skip(TEST_HYBRID_TLS_GROUPS == 0)) +TEST_CASE("hybrid group negotiation") { const std::vector hybrid_groups = { secp384r1_mlkem1024, secp256r1_mlkem768, x25519_mlkem768}; diff --git a/tests/ci-buckets.txt b/tests/ci-buckets.txt index 7ca731569c2..9024e99ac11 100644 --- a/tests/ci-buckets.txt +++ b/tests/ci-buckets.txt @@ -1,4 +1,5 @@ bucket_a: + tls_groups_test lts_compatibility bucket_b: @@ -42,4 +43,3 @@ no_bucket: recovery_test_suite reconfiguration_test_suite full_test_suite - From 5eb2834315591e789f32f84352bfdb34f9848f34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:46:01 +0000 Subject: [PATCH 3/9] Fix CI test bucket snapshot Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- tests/ci-buckets.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ci-buckets.txt b/tests/ci-buckets.txt index 9024e99ac11..c21da141b93 100644 --- a/tests/ci-buckets.txt +++ b/tests/ci-buckets.txt @@ -43,3 +43,4 @@ no_bucket: recovery_test_suite reconfiguration_test_suite full_test_suite + From a2703d701fa51b745910f5dea5efbd40f3880d81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:18:57 +0000 Subject: [PATCH 4/9] Support AL3 TLS group traces Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- tests/tls_groups.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/tls_groups.py b/tests/tls_groups.py index 759c4c14c9e..59707d6b443 100644 --- a/tests/tls_groups.py +++ b/tests/tls_groups.py @@ -15,11 +15,23 @@ WEAKEST_GROUP = "P-256" WEAKEST_GROUP_REPORTED = "prime256v1" +# TLS NamedGroup IDs reported by OpenSSL 3.3 +GROUP_IDS = { + "23": WEAKEST_GROUP_REPORTED, + "4587": "SecP256r1MLKEM768", + "4588": "X25519MLKEM768", + "4589": "SecP384r1MLKEM1024", +} + # A group CCF does not offer UNSUPPORTED_GROUP = "X448" -# s_client reports hybrid groups by name, and classical groups as a peer key +# s_client output varies between OpenSSL 3.3 and 3.5 NEGOTIATED_GROUP = re.compile(r"^Negotiated TLS1\.3 group: (\S+)$", re.MULTILINE) +NEGOTIATED_GROUP_ID = re.compile( + r"ServerHello,.*?extension_type=key_share\(51\).*?" r"NamedGroup: [^\n]*\((\d+)\)", + re.DOTALL, +) PEER_TEMP_KEY = re.compile(r"^Peer Temp Key: ECDH, ([^,]+),", re.MULTILINE) @@ -29,7 +41,7 @@ def negotiate_group(address, groups): group they agreed on, or None if they could not agree. """ completed = subprocess.run( - ["openssl", "s_client", "-connect", address, "-groups", groups], + ["openssl", "s_client", "-trace", "-connect", address, "-groups", groups], input="", capture_output=True, text=True, @@ -42,6 +54,10 @@ def negotiate_group(address, groups): if match is not None and match.group(1) != "": return match.group(1) + match = NEGOTIATED_GROUP_ID.search(output) + if match is not None: + return GROUP_IDS.get(match.group(1)) + match = PEER_TEMP_KEY.search(output) if match is not None: return match.group(1) From 1e80c61714eec611147964ec68dfb62129bb88d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:21:04 +0000 Subject: [PATCH 5/9] Scope TLS trace parsing to ServerHello Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- tests/tls_groups.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/tls_groups.py b/tests/tls_groups.py index 59707d6b443..15539524727 100644 --- a/tests/tls_groups.py +++ b/tests/tls_groups.py @@ -28,8 +28,8 @@ # s_client output varies between OpenSSL 3.3 and 3.5 NEGOTIATED_GROUP = re.compile(r"^Negotiated TLS1\.3 group: (\S+)$", re.MULTILINE) -NEGOTIATED_GROUP_ID = re.compile( - r"ServerHello,.*?extension_type=key_share\(51\).*?" r"NamedGroup: [^\n]*\((\d+)\)", +TRACED_GROUP_ID = re.compile( + r"extension_type=key_share\(51\).*?NamedGroup: [^\n]*\((\d+)\)", re.DOTALL, ) PEER_TEMP_KEY = re.compile(r"^Peer Temp Key: ECDH, ([^,]+),", re.MULTILINE) @@ -54,9 +54,11 @@ def negotiate_group(address, groups): if match is not None and match.group(1) != "": return match.group(1) - match = NEGOTIATED_GROUP_ID.search(output) - if match is not None: - return GROUP_IDS.get(match.group(1)) + for record in output.split("Received TLS Record"): + if "ServerHello," in record: + match = TRACED_GROUP_ID.search(record) + if match is not None: + return GROUP_IDS.get(match.group(1)) match = PEER_TEMP_KEY.search(output) if match is not None: From 9bbcaf7ff292da01a8b459bdc76840f6bec066c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:22:59 +0000 Subject: [PATCH 6/9] Report unknown negotiated group IDs Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- tests/tls_groups.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tls_groups.py b/tests/tls_groups.py index 15539524727..c5538f17df0 100644 --- a/tests/tls_groups.py +++ b/tests/tls_groups.py @@ -58,7 +58,8 @@ def negotiate_group(address, groups): if "ServerHello," in record: match = TRACED_GROUP_ID.search(record) if match is not None: - return GROUP_IDS.get(match.group(1)) + group_id = match.group(1) + return GROUP_IDS.get(group_id, group_id) match = PEER_TEMP_KEY.search(output) if match is not None: From bc098624dcf3cac9dab708e09bf497dcd659a5ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:24:56 +0000 Subject: [PATCH 7/9] Tighten TLS trace group parsing Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- tests/tls_groups.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/tls_groups.py b/tests/tls_groups.py index c5538f17df0..33d2c38579f 100644 --- a/tests/tls_groups.py +++ b/tests/tls_groups.py @@ -29,8 +29,7 @@ # s_client output varies between OpenSSL 3.3 and 3.5 NEGOTIATED_GROUP = re.compile(r"^Negotiated TLS1\.3 group: (\S+)$", re.MULTILINE) TRACED_GROUP_ID = re.compile( - r"extension_type=key_share\(51\).*?NamedGroup: [^\n]*\((\d+)\)", - re.DOTALL, + r"extension_type=key_share\(51\),[^\n]*\n" r"\s+NamedGroup: [^\n]*\((\d+)\)" ) PEER_TEMP_KEY = re.compile(r"^Peer Temp Key: ECDH, ([^,]+),", re.MULTILINE) @@ -54,7 +53,7 @@ def negotiate_group(address, groups): if match is not None and match.group(1) != "": return match.group(1) - for record in output.split("Received TLS Record"): + for record in output.split("Received TLS Record")[1:]: if "ServerHello," in record: match = TRACED_GROUP_ID.search(record) if match is not None: From b5eacdbe6ef6f0a331a1f46110931a06f3b0a1f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:26:44 +0000 Subject: [PATCH 8/9] Stop after traced ServerHello Co-authored-by: maxtropets <16566519+maxtropets@users.noreply.github.com> --- tests/tls_groups.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tls_groups.py b/tests/tls_groups.py index 33d2c38579f..b615bb0860a 100644 --- a/tests/tls_groups.py +++ b/tests/tls_groups.py @@ -59,6 +59,7 @@ def negotiate_group(address, groups): if match is not None: group_id = match.group(1) return GROUP_IDS.get(group_id, group_id) + break match = PEER_TEMP_KEY.search(output) if match is not None: From 1b099dc664bf9e88a1b7bc34bb92e9334d67ea6f Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Fri, 14 Aug 2026 13:55:30 +0100 Subject: [PATCH 9/9] Apply suggestion from @achamayou --- tests/tls_groups.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tls_groups.py b/tests/tls_groups.py index b615bb0860a..7cba3c9006c 100644 --- a/tests/tls_groups.py +++ b/tests/tls_groups.py @@ -16,6 +16,8 @@ WEAKEST_GROUP_REPORTED = "prime256v1" # TLS NamedGroup IDs reported by OpenSSL 3.3 +# Match IANA Group IDs defined in +# https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 GROUP_IDS = { "23": WEAKEST_GROUP_REPORTED, "4587": "SecP256r1MLKEM768",