Skip to content

Commit

Permalink
Implement TLSv1.3 tls-exporter channel binding
Browse files Browse the repository at this point in the history
  • Loading branch information
NelsonVides committed Jan 9, 2025
1 parent 01ca83c commit 5b92b1a
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 18 deletions.
18 changes: 12 additions & 6 deletions src/escalus_auth.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

-include_lib("exml/include/exml.hrl").

-define(CB_LABEL, <<"EXPORTER-Channel-Binding">>).

%%--------------------------------------------------------------------
%% Public API
%%--------------------------------------------------------------------
Expand Down Expand Up @@ -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).

Expand Down Expand Up @@ -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).
Expand Down
30 changes: 24 additions & 6 deletions src/escalus_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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().
Expand Down Expand Up @@ -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}) ->
Expand Down
37 changes: 31 additions & 6 deletions src/escalus_tcp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) ->
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.

0 comments on commit 5b92b1a

Please sign in to comment.