From 5b92b1a055f6f1e1276768b0d995dbdfef261dee Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 12:33:15 +0100 Subject: [PATCH] Implement TLSv1.3 tls-exporter channel binding --- src/escalus_auth.erl | 18 ++++++++++++------ src/escalus_connection.erl | 30 ++++++++++++++++++++++++------ src/escalus_tcp.erl | 37 +++++++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/escalus_auth.erl b/src/escalus_auth.erl index de99b62..4189392 100644 --- a/src/escalus_auth.erl +++ b/src/escalus_auth.erl @@ -38,6 +38,8 @@ -include_lib("exml/include/exml.hrl"). +-define(CB_LABEL, <<"EXPORTER-Channel-Binding">>). + %%-------------------------------------------------------------------- %% Public API %%-------------------------------------------------------------------- @@ -92,31 +94,31 @@ auth_sasl_scram_sha512(Conn, Props) -> %% SCRAM PLUS -spec auth_sasl_scram_sha1_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha1_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha, + Options = #{plus_variant => tls_exporter, hash_type => sha, xmpp_method => <<"SCRAM-SHA-1-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha224_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha224_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha224, + Options = #{plus_variant => tls_exporter, hash_type => sha224, xmpp_method => <<"SCRAM-SHA-224-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha256_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha256_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha256, + Options = #{plus_variant => tls_exporter, hash_type => sha256, xmpp_method => <<"SCRAM-SHA-256-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha384_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha384_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha384, + Options = #{plus_variant => tls_exporter, hash_type => sha384, xmpp_method => <<"SCRAM-SHA-384-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha512_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha512_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha512, + Options = #{plus_variant => tls_exporter, hash_type => sha512, xmpp_method => <<"SCRAM-SHA-512-PLUS">>}, auth_sasl_scram(Options, Conn, Props). @@ -222,7 +224,11 @@ md5_digest_response(ChallengeData, Props) -> scram_sha_auth_payload(undefined, _) -> {undefined, <<>>}; scram_sha_auth_payload(none, _) -> - {none, <<>>}. + {none, <<>>}; +scram_sha_auth_payload(tls_exporter, Conn) -> + {ok, [Material | _]} = escalus_connection:export_key_materials( + Conn, [?CB_LABEL], [no_context], [32], true), + {<<"tls-exporter">>, Material}. hex_md5(Data) -> binary:encode_hex(crypto:hash(md5, Data), lowercase). diff --git a/src/escalus_connection.erl b/src/escalus_connection.erl index 1a69cbf..27508ea 100644 --- a/src/escalus_connection.erl +++ b/src/escalus_connection.erl @@ -31,7 +31,7 @@ get_sm_h/1, set_sm_h/2, set_filter_predicate/2, - get_tls_last_message/1, + export_key_materials/5, reset_parser/1, is_connected/1, wait_for_close/1, @@ -87,6 +87,16 @@ -callback set_filter_predicate(pid(), filter_pred()) -> ok. -callback stop(pid()) -> ok | already_stopped. -callback kill(pid()) -> ok | already_stopped. +-callback export_key_materials(pid(), Labels, Contexts, WantedLengths, ConsumeSecret) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +-optional_callbacks([export_key_materials/5]). -callback stream_start_req(user_spec()) -> exml_stream:element(). -callback stream_end_req(user_spec()) -> exml_stream:element(). @@ -390,11 +400,19 @@ set_sm_h(#client{module = Mod}, _) -> set_filter_predicate(#client{module = Module, rcv_pid = Pid}, Pred) -> Module:set_filter_predicate(Pid, Pred). --spec get_tls_last_message(client()) -> {ok, binary()} | {error, undefined_tls_message}. -get_tls_last_message(#client{module = escalus_tcp, rcv_pid = Pid}) -> - escalus_tcp:get_tls_last_message(Pid); -get_tls_last_message(#client{module = Mod}) -> - error({get_tls_last_message, {undefined_for_escalus_module, Mod}}). +-spec export_key_materials(client(), Labels, Contexts, WantedLengths, ConsumeSecret) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +export_key_materials(#client{module = escalus_tcp, rcv_pid = Pid}, Labels, Contexts, WantedLengths, ConsumeSecret) -> + escalus_tcp:export_key_materials(Pid, Labels, Contexts, WantedLengths, ConsumeSecret); +export_key_materials(#client{module = Mod}, _Labels, _Contexts, _WantedLengths, _ConsumeSecret) -> + error({export_key_materials, {undefined_for_escalus_module, Mod}}). -spec reset_parser(client()) -> ok. reset_parser(#client{module = Mod, rcv_pid = Pid}) -> diff --git a/src/escalus_tcp.erl b/src/escalus_tcp.erl index df09b18..a2e0d4c 100644 --- a/src/escalus_tcp.erl +++ b/src/escalus_tcp.erl @@ -24,7 +24,7 @@ set_sm_h/2, is_using_compression/1, is_using_ssl/1, - get_tls_last_message/1 + export_key_materials/5 ]). %% Connection stream start and end callbacks -export([stream_start_req/1, @@ -119,9 +119,17 @@ is_using_ssl(Pid) -> set_filter_predicate(Pid, Pred) -> gen_server:call(Pid, {set_filter_pred, Pred}). --spec get_tls_last_message(pid()) -> {ok, binary()} | {error, undefined_tls_message}. -get_tls_last_message(Pid) -> - gen_server:call(Pid, get_tls_last_message). +-spec export_key_materials(pid(), Labels, Contexts, WantedLengths, ConsumeSecret) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +export_key_materials(Pid, Labels, Contexts, WantedLengths, ConsumeSecret) -> + gen_server:call(Pid, {export_key_materials, {Labels, Contexts, WantedLengths, ConsumeSecret}}). -spec stop(pid()) -> ok | already_stopped. stop(Pid) -> @@ -249,8 +257,8 @@ handle_call({set_active, Active}, _From, State) -> {reply, ok, set_active_opt(State, Active)}; handle_call({set_filter_pred, Pred}, _From, State) -> {reply, ok, State#state{filter_pred = Pred}}; -handle_call(get_tls_last_message, _From, #state{} = S) -> - {reply, {error, undefined_tls_message}, S}; +handle_call({export_key_materials, Data}, _From, #state{socket = Socket, ssl = true} = S) -> + {reply, do_export_key_materials(Socket, Data), S}; handle_call(kill_connection, _, #state{socket = Socket, ssl = SSL} = S) -> case SSL of true -> ssl:close(Socket); @@ -522,3 +530,20 @@ get_socket_opts(#{socket_opts := SocketOpts}) -> -spec opts_to_map([proplists:property()] | opts()) -> opts(). opts_to_map(Opts) when is_map(Opts) -> Opts; opts_to_map(Opts) when is_list(Opts) -> maps:from_list(Opts). + +-spec do_export_key_materials(ssl:sslsocket(), {Labels, Contexts, WantedLengths, ConsumeSecret}) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +-if(?OTP_RELEASE >= 27). +do_export_key_materials(SslSocket, {Labels, Contexts, WantedLengths, ConsumeSecret}) -> + ssl:export_key_materials(SslSocket, Labels, Contexts, WantedLengths, ConsumeSecret). +-else. +do_export_key_materials(_SslSocket, {_, _, _, _}) -> + {error, undefined_tls_material}. +-endif.