From 6a19d6a3af82bc5b0638427717eb3b80916c307a Mon Sep 17 00:00:00 2001 From: Johan Bevemyr Date: Sun, 9 Aug 2026 14:06:54 +0200 Subject: [PATCH] ssl: expose the negotiated TLS-1.3 group in connection_information The named group of the TLS-1.3 key exchange is negotiated via the supported_groups and key_share extensions and is already kept in the connection state (session.ecc), but connection_information/1,2 never exposed it: the existing ecc item only covers pre-TLS-1.3 named-curve cipher suites. With the arrival of post-quantum hybrid groups (X25519MLKEM768 and friends) the negotiated group has become operationally interesting: an application or an operator auditing a fleet wants to know whether a given connection actually negotiated a quantum-safe key exchange or fell back to a classical group. Today that is only visible by packet inspection or debug-level handshake logging. Add a selected_group item, returned for TLS-1.3 connections on both the client and the server side. --- lib/ssl/src/ssl.erl | 2 ++ lib/ssl/src/ssl_gen_statem.erl | 11 ++++++++++- lib/ssl/test/ssl_api_SUITE.erl | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 1509688ebd92..60fc34edb952 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -2128,6 +2128,7 @@ Key value list convening some information about the established connection. -type connection_info() :: [{protocol, protocol_version()} | {session_resumption, boolean()} | {selected_cipher_suite, erl_cipher_suite()} | + {selected_group, group()} | {sni_hostname, term()} | {ciphers, [erl_cipher_suite()]}] | connection_info_pre_tls13() | @@ -2163,6 +2164,7 @@ TLS connection keys for which information can be retrieved. """. -type connection_info_keys() :: [ protocol | selected_cipher_suite + | selected_group | sni_hostname | session_resumption | ciphers diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index 7d1fc074a60a..5695066db14e 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -1931,13 +1931,22 @@ connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname _ -> [] end, + GroupInfo = case Version of + ?TLS_1_3 when is_atom(ECCCurve), ECCCurve =/= undefined -> + %% For TLS-1.3 the named group of the key exchange is + %% negotiated via the supported_groups and key_share + %% extensions and is kept in session.ecc + [{selected_group, ECCCurve}]; + _ -> + [] + end, [{protocol, RecordCB:protocol_version(Version)}, {session_id, SessionId}, {session_data, term_to_binary(Session)}, {session_resumption, Resumption}, {selected_cipher_suite, CipherSuiteDef}, {sni_hostname, SNIHostname}, - {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts). + {srp_username, SrpUsername} | CurveInfo] ++ GroupInfo ++ MFLInfo ++ ssl_options_list(Opts). security_info(#state{connection_states = #{current_read := Read, current_write := Write}, diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index edb7e5e4ce12..931c9a332650 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -4098,6 +4098,15 @@ secs_since_1970() -> connection_information_result(Socket) -> {ok, Info = [_ | _]} = ssl:connection_information(Socket), + case proplists:get_value(protocol, Info) of + 'tlsv1.3' -> + %% For TLS-1.3 the negotiated key exchange group is exposed + {ok, [{selected_group, Group}]} = + ssl:connection_information(Socket, [selected_group]), + true = lists:member(Group, ssl:groups()); + _ -> + false = proplists:is_defined(selected_group, Info) + end, case length(Info) > 3 of true -> %% At least one ssl_option() is set