File 0715-ssl-Improve-maintainability-and-correct-keylogging.patch of Package erlang
From 23e79dcf78c67c0b3d396b092b0efcbbe6c1e6d4 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Mon, 22 Dec 2025 09:38:06 +0100
Subject: [PATCH 5/5] ssl: Improve maintainability and correct keylogging
---
lib/ssl/src/ssl.erl | 25 +-
lib/ssl/src/ssl_gen_statem.erl | 255 ++++++++++-----
lib/ssl/src/ssl_logger.erl | 10 +-
lib/ssl/src/tls_client_connection_1_3.erl | 26 +-
lib/ssl/src/tls_gen_connection.erl | 25 +-
lib/ssl/src/tls_gen_connection_1_3.erl | 3 +-
lib/ssl/src/tls_handshake_1_3.erl | 379 +++++++++++-----------
lib/ssl/src/tls_record.erl | 2 +-
lib/ssl/src/tls_server_connection_1_3.erl | 77 ++---
lib/ssl/test/ssl_session_ticket_SUITE.erl | 9 -
lib/ssl/test/tls_1_3_version_SUITE.erl | 159 +++++++--
11 files changed, 600 insertions(+), 370 deletions(-)
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index a29047a410..cc47041b85 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1757,7 +1757,7 @@ opt_protocol_versions(UserOpts, Opts, Env) ->
{_, LL} = get_opt_of(log_level, LogLevels, DefaultLevel, UserOpts, Opts),
- Opts1 = set_opt_bool(keep_secrets, false, UserOpts, Opts),
+ Opts1 = opt_keep_secrets(UserOpts, Opts),
{DistW, Dist} = get_opt_bool(erl_dist, false, UserOpts, Opts1),
option_incompatible(PRC =:= dtls andalso Dist, [{protocol, PRC}, {erl_dist, Dist}]),
@@ -2501,6 +2501,22 @@ opt_process(UserOpts, Opts0, _Env) ->
%% Opts = Opts1#{receiver_spawn_opts => RSO, sender_spawn_opts => SSO},
set_opt_int(hibernate_after, 0, infinity, infinity, UserOpts, Opts2).
+opt_keep_secrets(UserOpts, Opts) ->
+ case get_opt(keep_secrets, false, UserOpts, Opts) of
+ {new, Value} when is_boolean(Value) ->
+ Opts#{keep_secrets => Value};
+ {new, {FunType, Fun} = Value}
+ when FunType == keylog orelse FunType == keylog_hs,
+ is_function(Fun) ->
+ Opts#{keep_secrets => Value};
+ {old, _} ->
+ Opts;
+ {default, _} -> %% Keep default implicit
+ Opts;
+ {_, Value} ->
+ option_error(keep_secrets, Value)
+ end.
+
%%%%
get_opt(Opt, Default, UserOpts, Opts) ->
@@ -2571,13 +2587,6 @@ get_opt_file(Opt, Default, UserOpts, Opts) ->
Res -> Res
end.
-set_opt_bool(Opt, Default, UserOpts, Opts) ->
- case maps:get(Opt, UserOpts, Default) of
- Default -> Opts;
- Value when is_boolean(Value) -> Opts#{Opt => Value};
- Value -> option_error(Opt, Value)
- end.
-
get_opt_map(Opt, Default, UserOpts, Opts) ->
case get_opt(Opt, Default, UserOpts, Opts) of
{new, Err} when not is_map(Err) -> option_error(Opt, Err);
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
index 7eb9452a6f..4ade5e86d3 100644
--- a/lib/ssl/src/ssl_gen_statem.erl
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -87,6 +87,7 @@
%% Alert and close handling
-export([send_alert/3,
+ opposite_role/1,
handle_own_alert/3,
handle_alert/3,
handle_normal_shutdown/3,
@@ -1100,6 +1101,7 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role =
handshake_env = #handshake_env{renegotiation = {false, first}},
start_or_recv_from = StartFrom} = State) ->
Pids = Connection:pids(State),
+ maybe_keylog_hs_callback(Alert, StateName, State),
alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
@@ -1112,6 +1114,7 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role =
socket_options = Opts,
start_or_recv_from = RecvFrom} = State) ->
Pids = Connection:pids(State),
+ maybe_keylog_hs_callback(Alert, StateName, State),
alert_user(Pids, Transport, Trackers, Socket, Type, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
handle_alert(#alert{level = ?FATAL} = Alert, StateName, State) ->
@@ -1227,6 +1230,7 @@ handle_fatal_alert(Alert0, StateName,
StateName, Alert),
Pids = Connection:pids(State),
alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
+ handle_normal_shutdown(Alert, StateName, State),
{stop, {shutdown, normal}, State}.
handle_trusted_certs_db(#state{ssl_options =#{cacerts := []} = Opts})
@@ -2208,79 +2212,6 @@ ssl_options_list([{ciphers = Key, Value}|T], Acc) ->
ssl_options_list([{Key, Value}|T], Acc) ->
ssl_options_list(T, [{Key, Value} | Acc]).
-%% Maybe add NSS keylog info according to
-%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
-maybe_add_keylog(Info) ->
- maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info).
-
-maybe_add_keylog({_, 'tlsv1.3'}, Info) ->
- try
- {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
- %% after traffic key update current traffic secret
- %% is stored in tls_sender process state
- MaybeUpdateTrafficSecret =
- fun({Direction, {Sender, TrafficSecret0}}) ->
- TrafficSecret =
- case call(Sender, get_application_traffic_secret) of
- {ok, SenderAppTrafSecretWrite} ->
- SenderAppTrafSecretWrite;
- _ ->
- TrafficSecret0
- end,
- {Direction, TrafficSecret};
- (TrafficSecret0) ->
- TrafficSecret0
- end,
- {client_traffic_secret_0, ClientTrafficSecret0Bin} =
- MaybeUpdateTrafficSecret(lists:keyfind(client_traffic_secret_0, 1, Info)),
- {server_traffic_secret_0, ServerTrafficSecret0Bin} =
- MaybeUpdateTrafficSecret(lists:keyfind(server_traffic_secret_0, 1, Info)),
- {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info),
- {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info),
- {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info),
- ClientRandom = binary:decode_unsigned(ClientRandomBin),
- ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf),
- ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf),
- ClientHSecret = keylog_secret(ClientHSecretBin, Prf),
- ServerHSecret = keylog_secret(ServerHSecretBin, Prf),
- Keylog0 = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret,
- io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret,
- io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0,
- io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0],
- Keylog = case lists:keyfind(client_early_data_secret, 1, Info) of
- {client_early_data_secret, EarlySecret} ->
- ClientEarlySecret = keylog_secret(EarlySecret, Prf),
- [io_lib:format("CLIENT_EARLY_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientEarlySecret
- | Keylog0];
- _ ->
- Keylog0
- end,
- Info ++ [{keylog,Keylog}]
- catch
- _Cxx:_Exx ->
- Info
- end;
-maybe_add_keylog({_, _}, Info) ->
- try
- {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
- {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info),
- ClientRandom = binary:decode_unsigned(ClientRandomBin),
- MasterSecret = binary:decode_unsigned(MasterSecretBin),
- Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])],
- Info ++ [{keylog,Keylog}]
- catch
- _Cxx:_Exx ->
- Info
- end;
-maybe_add_keylog(_, Info) ->
- Info.
-
-keylog_secret(SecretBin, sha256) ->
- io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]);
-keylog_secret(SecretBin, sha384) ->
- io_lib:format("~96.16.0B", [binary:decode_unsigned(SecretBin)]);
-keylog_secret(SecretBin, sha512) ->
- io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]).
maybe_generate_client_shares(#{versions := [?TLS_1_3|_],
supported_groups :=
@@ -2291,6 +2222,184 @@ maybe_generate_client_shares(#{versions := [?TLS_1_3|_],
maybe_generate_client_shares(_) ->
undefined.
+%% Maybe add NSS keylog info according to
+%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+maybe_add_keylog(Info) ->
+ case lists:keyfind(keep_secrets, 1, Info) of
+ {keep_secrets, true} ->
+ case lists:keyfind(protocol, 1, Info) of
+ {protocol, 'tlsv1.3'} ->
+ Info ++ [{keylog, keylog_1_3(Info)}];
+ {protocol, _} ->
+ Info ++ [{keylog, keylog_pre_1_3(Info)}]
+ end;
+ {keep_secrets, false} ->
+ Info
+ end.
+
+maybe_keylog_hs_callback(#alert{level = ?FATAL}, StateName,
+ #state{ssl_options = #{keep_secrets := {keylog_hs, Fun}}}
+ = State) when is_function(Fun) ->
+ case keylog_hs_alert(StateName, State) of
+ {[], _} ->
+ ok;
+ {KeyLog, Random} ->
+ ssl_logger:keylog(KeyLog, Random, Fun)
+ end;
+maybe_keylog_hs_callback(_, _, _) ->
+ ok.
+
+keylog_hs_alert(start, _) -> %% TLS 1.3: No secrets yet established
+ {[], undefined};
+keylog_hs_alert(wait_sh, _) -> %% TLS 1.3: No secrets yet established
+ {[], undefined};
+keylog_hs_alert(negotiated, #state{static_env = #static_env{role = server},
+ connection_env =
+ #connection_env{negotiated_version = TlsVersion},
+ connection_states = #{current_read := Read,
+ current_write := Write
+ }})
+ when ?TLS_GTE(TlsVersion, ?TLS_1_3) ->
+ #{server_handshake_traffic_secret := ServerHSSecret,
+ security_parameters :=
+ #security_parameters{client_random = ClientRandomBin,
+ client_early_data_secret = EarlySecret,
+ prf_algorithm = Prf
+ }} = Write,
+ #{client_handshake_traffic_secret := ClientHSSecret} = Read,
+ {keylog_hs_1_3(ClientRandomBin, Prf, EarlySecret, ServerHSSecret, ClientHSSecret),
+ ClientRandomBin};
+keylog_hs_alert(wait_eoed, #state{static_env = #static_env{role = server},
+ connection_env =
+ #connection_env{negotiated_version = TlsVersion},
+ connection_states = #{pending_read := Read,
+ current_write := Write
+ }})
+ when ?TLS_GTE(TlsVersion, ?TLS_1_3) ->
+ keylog_1_3_client_finished(Read, Write);
+keylog_hs_alert(StateName, #state{static_env = #static_env{role = server},
+ connection_states =
+ #{current_read := Read,
+ current_write := Write
+ }}) when StateName == wait_cert;
+ StateName == wait_cv;
+ StateName == wait_finished ->
+ keylog_1_3_client_finished(Read, Write);
+keylog_hs_alert(connection, #state{static_env = #static_env{role = client},
+ connection_env =
+ #connection_env{negotiated_version = TlsVersion},
+ connection_states = #{current_read := Read,
+ current_write := Write
+ }})
+ when ?TLS_GTE(TlsVersion, ?TLS_1_3) ->
+ #{server_handshake_traffic_secret := ServerHSSecret,
+ security_parameters :=
+ #security_parameters{client_random = ClientRandomBin,
+ client_early_data_secret = EarlySecret,
+ prf_algorithm = Prf,
+ master_secret = {master_secret, STrafficSecret}
+ }}
+ = Read,
+ #{client_handshake_traffic_secret := ClientHSSecret,
+ security_parameters :=
+ #security_parameters{master_secret = {master_secret, CTrafficSecret}
+ }} = Write,
+
+ {keylog_hs_1_3(ClientRandomBin, Prf, EarlySecret, ClientHSSecret, ServerHSSecret) ++
+ ssl_logger:keylog_traffic_1_3(client, ClientRandomBin, Prf, CTrafficSecret, 0) ++
+ ssl_logger:keylog_traffic_1_3(server, ClientRandomBin, Prf, STrafficSecret, 0),
+ ClientRandomBin};
+keylog_hs_alert(_, #state{static_env = #static_env{role = client},
+ connection_env =
+ #connection_env{negotiated_version = TlsVersion},
+ connection_states = #{current_read := Read,
+ current_write := Write
+ }})
+ when ?TLS_GTE(TlsVersion, ?TLS_1_3) ->
+ #{server_handshake_traffic_secret := ServerHSSecret,
+ security_parameters :=
+ #security_parameters{client_random = ClientRandomBin,
+ client_early_data_secret = EarlySecret,
+ prf_algorithm = Prf
+ }}
+ = Write,
+ #{client_handshake_traffic_secret := ClientHSSecret} = Read,
+ {keylog_hs_1_3(ClientRandomBin, Prf, EarlySecret, ClientHSSecret, ServerHSSecret),
+ ClientRandomBin};
+keylog_hs_alert(_, _) -> % NOT Relevant pre TLS-1.3
+ {[], undefined}.
+
+keylog_hs_1_3(ClientRandomBin, Prf, EarlySecret, ClientSecret, ServerSecret) ->
+ HSItems =
+ ssl_logger:keylog_hs(ClientRandomBin, Prf, ClientSecret, ServerSecret),
+ case EarlySecret of
+ undefined ->
+ HSItems;
+ _ ->
+ [ssl_logger:keylog_early_data(ClientRandomBin, Prf, EarlySecret) | HSItems]
+ end.
+
+keylog_1_3_client_finished(Read, Write) ->
+ #{server_handshake_traffic_secret := ServerHSSecret,
+ security_parameters :=
+ #security_parameters{client_random = ClientRandomBin,
+ client_early_data_secret = EarlySecret,
+ prf_algorithm = Prf,
+ master_secret = {master_secret, TrafficSecret}
+ }}
+ = Write,
+ #{client_handshake_traffic_secret := ClientHSSecret} = Read,
+ {keylog_hs_1_3(ClientRandomBin, Prf, EarlySecret, ServerHSSecret, ClientHSSecret) ++
+ ssl_logger:keylog_traffic_1_3(server, ClientRandomBin, Prf, TrafficSecret, 0),
+ ClientRandomBin}.
+
+keylog_1_3(Info) ->
+ {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
+ {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info),
+ {Role, Sender} = case lists:keyfind(server, 1, Info) of
+ false ->
+ lists:keyfind(client, 1, Info);
+ Result ->
+ Result
+ end,
+ keylog_1_3(ClientRandomBin, Prf, Role, Sender, Info).
+
+keylog_1_3(ClientRandom, Prf, Role, Sender, Info) ->
+ {ok, SecretWrite, NWrite} = call(Sender, get_application_traffic_secret),
+ EarlySecret = proplists:get_value(client_early_data_secret, Info, undefined),
+ case Role of
+ client ->
+ {server_traffic_secret, SecretRead, NRead} = lists:keyfind(server_traffic_secret, 1, Info),
+ hs_logs(NRead, ClientRandom, Prf, EarlySecret, Info) ++
+ ssl_logger:keylog_traffic_1_3(Role, ClientRandom, Prf, SecretRead, NRead) ++
+ ssl_logger:keylog_traffic_1_3(server, ClientRandom, Prf, SecretWrite, NWrite);
+ server ->
+ {client_traffic_secret, SecretRead, NRead} = lists:keyfind(client_traffic_secret, 1, Info),
+ hs_logs(NRead, ClientRandom, Prf, EarlySecret, Info) ++
+ ssl_logger:keylog_traffic_1_3(client, ClientRandom, Prf, SecretWrite, NWrite) ++
+ ssl_logger:keylog_traffic_1_3(Role, ClientRandom, Prf, SecretRead, NRead)
+ end.
+
+hs_logs(0, ClientRandom, Prf, EarlySecret, Info) ->
+ {client_handshake_traffic_secret, ClientHSecret} =
+ lists:keyfind(client_handshake_traffic_secret, 1, Info),
+ {server_handshake_traffic_secret, ServerHSecret} =
+ lists:keyfind(server_handshake_traffic_secret, 1, Info),
+ HSItems = ssl_logger:keylog_hs(ClientRandom, Prf, ClientHSecret, ServerHSecret),
+ case EarlySecret of
+ undefined ->
+ HSItems;
+ _ ->
+ [ssl_logger:keylog_early_data(ClientRandom, Prf, EarlySecret) | HSItems]
+ end;
+hs_logs(_,_,_,_, _) ->
+ [].
+
+keylog_pre_1_3(Info) ->
+ {client_random, ClientRandom} = lists:keyfind(client_random, 1, Info),
+ {master_secret, MasterSecret} = lists:keyfind(master_secret, 1, Info),
+ ssl_logger:keylog_traffic_pre_1_3(ClientRandom, MasterSecret).
+
%%%################################################################
%%%#
%%%# Tracing
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index af893c389c..95a726d78f 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -51,6 +51,11 @@
%% Internal API -- Stateful logging
%%-------------------------------------------------------------------------
+
+%%---%%-------------------------------------------------------------------------
+%% Internal API -- Stateful logging
+%%-------------------------------------------------------------------------
+
log(Level, LogLevel, ReportMap, Meta) ->
case logger:compare_levels(LogLevel, Level) of
lt ->
@@ -63,6 +68,8 @@ log(Level, LogLevel, ReportMap, Meta) ->
ok
end.
+debug(undefined, _Direction, _Protocol, _Message) ->
+ ok;
debug(LogLevel, Direction, Protocol, Message)
when (Direction =:= inbound orelse Direction =:= outbound) andalso
(Protocol =:= 'record' orelse Protocol =:= 'handshake') ->
@@ -134,7 +141,8 @@ format(#{msg:= {report, Msg}}, _Config0) ->
_Other ->
[]
end.
-%%-------------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
%% Keylog functions (not erlang logger related)
%%-------------------------------------------------------------------------
diff --git a/lib/ssl/src/tls_client_connection_1_3.erl b/lib/ssl/src/tls_client_connection_1_3.erl
index 4c5a6cecf0..928360374a 100644
--- a/lib/ssl/src/tls_client_connection_1_3.erl
+++ b/lib/ssl/src/tls_client_connection_1_3.erl
@@ -459,12 +459,13 @@ wait_finished(internal,
State4 = Connection:queue_handshake(Finished, State3),
%% Send first flight
{State5, _} = Connection:send_handshake_flight(State4),
- State6 = tls_handshake_1_3:calculate_traffic_secrets(State5),
- State7 = tls_handshake_1_3:maybe_calculate_resumption_master_secret(State6),
- State8 = ssl_record:step_encryption_state(State7),
- State9 = tls_handshake_1_3:prepare_connection(State8),
+ State6 = tls_handshake_1_3:calculate_write_traffic_secrets(State5),
+ State7 = tls_handshake_1_3:calculate_read_traffic_secrets(State6),
+ State8 = tls_handshake_1_3:maybe_calculate_resumption_master_secret(State7),
+ State9 = ssl_record:step_encryption_state(State8),
+ State10 = tls_handshake_1_3:prepare_connection(State9),
{Record, State} =
- ssl_gen_statem:prepare_connection(State9, tls_gen_connection),
+ ssl_gen_statem:prepare_connection(State10, tls_gen_connection),
KeepSecrets = maps:get(keep_secrets, SSLOpts, false),
tls_gen_connection_1_3:maybe_traffic_keylog_1_3(KeepSecrets, Role,
State#state.connection_states, 0),
@@ -483,6 +484,21 @@ wait_finished(Type, Msg, State) ->
term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+connection(enter, _, #state{ssl_options = Opts} = State) ->
+ case maps:get(keep_secrets, Opts, undefined) of
+ {keylog_hs, _} ->
+ %% Mitigate consequences of keylog_hs being activated, as
+ %% this forces the client to remember secrets longer, that
+ %% is the client certification can fail after the client
+ %% reached connection state.
+ {next_state, ?FUNCTION_NAME, State, [{timeout, 1000, forget}]};
+ _ ->
+ {keep_state, State}
+ end;
+connection(timeout, forget, State) ->
+ {next_state, ?FUNCTION_NAME, tls_handshake_1_3:forget_master_secret(State)};
+connection(info, Msg, State) ->
+ tls_gen_connection:gen_info(Msg, connection, State);
connection(Type, Msg, State) ->
tls_gen_connection_1_3:connection(Type, Msg, State).
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
index 36da0f0ec2..edaab26406 100644
--- a/lib/ssl/src/tls_gen_connection.erl
+++ b/lib/ssl/src/tls_gen_connection.erl
@@ -62,7 +62,8 @@
-export([socket/4,
setopts/3,
getopts/3,
- handle_info/3]).
+ handle_info/3,
+ gen_info/3]).
%% Alert and close handling
-export([send_alert/2,
@@ -288,6 +289,28 @@ setopts(Transport, Socket, Other) ->
getopts(Transport, Socket, Tag) ->
tls_socket:getopts(Transport, Socket, Tag).
+
+gen_info(Event, connection = StateName, State) ->
+ try
+ handle_info(Event, StateName, State)
+ catch
+ _:Reason:ST ->
+ ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ malformed_data),
+ StateName, State)
+ end;
+gen_info(Event, StateName, State) ->
+ try
+ handle_info(Event, StateName, State)
+ catch
+ _:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ StateName, State)
+ end.
+
%% raw data from socket, upack records
handle_info({Protocol, _, Data}, StateName,
#state{static_env = #static_env{data_tag = Protocol}} = State0) ->
diff --git a/lib/ssl/src/tls_gen_connection_1_3.erl b/lib/ssl/src/tls_gen_connection_1_3.erl
index 5261d77a8a..5c9f14fb12 100644
--- a/lib/ssl/src/tls_gen_connection_1_3.erl
+++ b/lib/ssl/src/tls_gen_connection_1_3.erl
@@ -50,8 +50,7 @@
maybe_forget_hs_secrets/2,
do_maybe/0]).
-%%--------------------------------------------------------------------
-%% Internal API
+%% Internal API
%%--------------------------------------------------------------------
initial_state(Role, Sender, Host, Port, Socket,
{SSLOptions, SocketOptions, Trackers}, User,
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 63ac4ba177..e2bea6bfe2 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -79,7 +79,6 @@
get_pre_shared_key/4,
get_pre_shared_key_early_data/2,
get_supported_groups/1,
- calculate_traffic_secrets/1,
calculate_client_early_traffic_secret/5,
calculate_client_early_traffic_secret/2,
calculate_read_traffic_secrets/1,
@@ -87,6 +86,7 @@
maybe_calculate_resumption_master_secret/1,
early_data_secret/1,
hs_traffic_secrets/2,
+ forget_master_secret/1,
encode_early_data/2,
get_ticket_data/3,
ciphers_for_early_data/1,
@@ -870,49 +870,136 @@ message_hash(ClientHello1, HKDFAlgo) ->
0,0,ssl_cipher:hash_size(HKDFAlgo),
crypto:hash(HKDFAlgo, ClientHello1)].
-prepare_connection(State0) ->
+prepare_connection(#state{static_env = #static_env{role = Role}, ssl_options = Opts} =State0) ->
%% Handle different secrets on transition to the connection state
State1 = #state{protocol_specific = PS} = maybe_calculate_resumption_master_secret(State0),
ExporterSecret = calculate_exporter_master_secret(State1),
- State = State0#state{protocol_specific = PS#{exporter_master_secret => ExporterSecret}},
- forget_master_secret(State).
+ State = State0#state{protocol_specific = PS#{exporter_master_secret => ExporterSecret}},
+ Keep = case maps:get(keep_secrets, Opts, undefined) of
+ {keylog_hs, _} ->
+ true;
+ _ ->
+ false
+ end,
+ case Role == client andalso Keep of
+ true ->
+ %% Remember
+ State;
+ false ->
+ forget_master_secret(State)
+ end.
-calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK,
- #state{connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- tls_handshake_history = HHistory}} = State0) ->
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDFAlgo,
- cipher_suite = CipherSuite} = SecParamsR,
+calculate_handshake_secrets(PublicKey, PrivateKeyOrSecret, SelectedGroup, PSK,
+ #state{connection_states = ConnectionStates0,
+ ssl_options = Opts} = State0) ->
+ PendingRead1 = calculate_read_handshake_secrets(PublicKey, PrivateKeyOrSecret,
+ SelectedGroup, PSK, State0),
+ PendingWrite1 = calculate_write_handshake_secrets(PublicKey, PrivateKeyOrSecret,
+ SelectedGroup, PSK, State0),
+ {PendingRead, PendingWrite} = maybe_handshake_keylog(PendingRead1, PendingWrite1, Opts),
+ ConnectionStates = ConnectionStates0#{pending_read => PendingRead,
+ pending_write => PendingWrite},
+ State0#state{connection_states = ConnectionStates}.
+
+calculate_read_handshake_secrets(PublicKey, PrivateKeyOrSecret, SelectedGroup, PSK,
+ #state{ssl_options = Opts,
+ static_env = #static_env{role = Role},
+ connection_states = #{pending_read := PendingRead0},
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}}) ->
+ #{security_parameters := SecParams} = PendingRead0,
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParams,
+ HandshakeSecret = calculate_handshake_master_secret(PublicKey, PrivateKeyOrSecret,
+ SelectedGroup, PSK, HKDFAlgo),
+ Keep = maps:get(keep_secrets, Opts, false),
+ calculate_handshake_secret(HandshakeSecret, PendingRead0, read, Role, HHistory, Keep).
+
+calculate_write_handshake_secrets(PublicKey, PrivateKeyOrSecret, SelectedGroup, PSK,
+ #state{ssl_options = Opts,
+ static_env = #static_env{role = Role},
+ connection_states = #{pending_write := PendingWrite0},
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}}) ->
+ #{security_parameters := SecParams} = PendingWrite0,
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParams,
+ HandshakeSecret = calculate_handshake_master_secret(PublicKey, PrivateKeyOrSecret,
+ SelectedGroup, PSK, HKDFAlgo),
+ Keep = maps:get(keep_secrets, Opts, false),
+ calculate_handshake_secret(HandshakeSecret, PendingWrite0, write, Role, HHistory, Keep).
+
+calculate_handshake_master_secret(PublicKey, PrivateKeyOrSecret, SelectedGroup,
+ PSK, HKDFAlgo) ->
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
+ IKM = calculate_shared_secret(PublicKey, PrivateKeyOrSecret, SelectedGroup),
+ tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret).
- IKM = calculate_shared_secret(PublicKey, PrivateKey, SelectedGroup),
- HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
-
- %% Calculate [sender]_handshake_traffic_secret
- {Messages, _} = HHistory,
- ClientHSTrafficSecret =
- tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
- ServerHSTrafficSecret =
- tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
+calculate_handshake_secret(HandshakeSecret, ConnectionState, ReadorWrite, Role, HHistory, Keep) ->
+ #{security_parameters := SecParams} = ConnectionState,
+ #security_parameters{prf_algorithm = HKDFAlgo,
+ bulk_cipher_algorithm = BulkCipherAlgo,
+ cipher_suite = CipherSuite} = SecParams,
+ Messages = get_handshake_context(Role, HHistory),
+ {HsTrafficSecret, SecretType} =
+ case {Role, ReadorWrite} of
+ {server, write} ->
+ {tls_v1:server_handshake_traffic_secret(HKDFAlgo,
+ HandshakeSecret, Messages), server} ;
+ {client, read} ->
+ {tls_v1:server_handshake_traffic_secret(HKDFAlgo,
+ HandshakeSecret, Messages), server};
+ {client, write} ->
+ {tls_v1:client_handshake_traffic_secret(HKDFAlgo,
+ HandshakeSecret, Messages), client};
+ {server, read} ->
+ {tls_v1:client_handshake_traffic_secret(HKDFAlgo,
+ HandshakeSecret, Messages), client}
+ end,
- %% Calculate traffic keys
KeyLength = tls_v1:key_length(CipherSuite),
- {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientHSTrafficSecret),
- {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerHSTrafficSecret),
-
- %% Calculate Finished Keys
- ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo),
- WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo),
-
- State1 = maybe_store_handshake_traffic_secret(State0, ClientHSTrafficSecret, ServerHSTrafficSecret),
+ {Key, IV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, HsTrafficSecret),
+
+ FinishedKey = tls_v1:finished_key(HsTrafficSecret, HKDFAlgo),
+
+ NewSecParams =
+ SecParams#security_parameters{master_secret = HandshakeSecret},
+
+ NewConnectionState = ConnectionState#{security_parameters => NewSecParams,
+ cipher_state =>
+ cipher_init(BulkCipherAlgo, Key, IV, FinishedKey)},
+ maybe_store_handshake_traffic_secret(SecretType, NewConnectionState, HsTrafficSecret, Keep).
+
+maybe_store_handshake_traffic_secret(client, ConnectionState, HsTrafficSecret, Keep)
+ when Keep =/= false ->
+ ConnectionState#{client_handshake_traffic_secret => HsTrafficSecret};
+maybe_store_handshake_traffic_secret(server, ConnectionState, HsTrafficSecret, Keep)
+ when Keep =/= false ->
+ ConnectionState#{server_handshake_traffic_secret => HsTrafficSecret};
+maybe_store_handshake_traffic_secret(_,ConnectionState, _,_) ->
+ ConnectionState.
+
+maybe_handshake_keylog(Read0, Write0, Opts) ->
+ Keep = maps:get(keep_secrets, Opts, undefined),
+ #{security_parameters := SecParams} = Read0,
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParams,
+ case Keep of
+ {keylog, Fun} ->
+ #{security_parameters := SecParams0} = Read0,
+ ClientRand = SecParams0#security_parameters.client_random,
+ {ClientHSTrafficSecret, ServerHSTrafficSecret} =
+ hs_secrets(Read0, Write0),
+ KeyLog = ssl_logger:keylog_hs(ClientRand, HKDFAlgo,
+ ClientHSTrafficSecret, ServerHSTrafficSecret),
+ ssl_logger:keylog(KeyLog, ClientRand, Fun),
+ {maps:without([client_handshake_traffic_secret, server_handshake_traffic_secret],
+ Read0),
+ maps:without([client_handshake_traffic_secret, server_handshake_traffic_secret],
+ Write0)};
+ _ ->
+ {Read0, Write0}
+ end.
- update_pending_connection_states(State1, HandshakeSecret, undefined,
- undefined, undefined,
- ReadKey, ReadIV, ReadFinishedKey,
- WriteKey, WriteIV, WriteFinishedKey).
hs_secrets(Read, Write) ->
{hs_traffic_secret(client_handshake_traffic_secret, Read, Write),
hs_traffic_secret(server_handshake_traffic_secret, Read, Write)}.
@@ -944,7 +1031,8 @@ calculate_client_early_traffic_secret(#state{connection_states = ConnectionState
%% Client
calculate_client_early_traffic_secret(
ClientHello, PSK, Cipher, HKDFAlgo,
- #state{connection_states = ConnectionStates,
+ #state{connection_states = #{pending_write := PendingWrite0,
+ pending_read := PendingRead0} = ConnectionStates,
ssl_options = Opts,
static_env = #static_env{role = Role}} = State0) ->
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
@@ -957,20 +1045,21 @@ calculate_client_early_traffic_secret(
%% Update pending connection states
case Role of
client ->
- PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write),
- PendingWrite1 = maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
- PendingWrite0),
- PendingWrite = update_connection_state(PendingWrite1, undefined, undefined,
- undefined,
- Key, IV, undefined),
+ PendingWrite1 = #{security_parameters := SecParams} =
+ maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
+ PendingWrite0, HKDFAlgo),
+ BulkCipherAlgo = SecParams#security_parameters.bulk_cipher_algorithm,
+ PendingWrite = PendingWrite1#{cipher_state =>
+ cipher_init(BulkCipherAlgo, Key, IV, undefined)},
+
State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}};
server ->
- PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
- PendingRead1 = maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
- PendingRead0),
- PendingRead = update_connection_state(PendingRead1, undefined, undefined,
- undefined,
- Key, IV, undefined),
+ PendingRead1 = #{security_parameters := SecParams} =
+ maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
+ PendingRead0, HKDFAlgo),
+ BulkCipherAlgo = SecParams#security_parameters.bulk_cipher_algorithm,
+ PendingRead = PendingRead1#{cipher_state =>
+ cipher_init(BulkCipherAlgo, Key, IV, undefined)},
State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}}
end.
@@ -988,13 +1077,21 @@ hs_traffic_secrets(Read, Write) ->
{server_handshake_traffic_secret, ServerHSTrafficSecret}]
end.
-maybe_store_early_data_secret(#{keep_secrets := true}, EarlySecret, State) ->
- #{security_parameters := SecParams0} = State,
- SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret},
- State#{security_parameters := SecParams};
-maybe_store_early_data_secret(_, _, State) ->
- State.
-
+maybe_store_early_data_secret(#{keep_secrets := Keep}, EarlySecret,
+ #{security_parameters := SecParams0} = CSState,
+ Prf) when Keep =/= false ->
+ case Keep of
+ {keylog, Fun} ->
+ ClientRand = SecParams0#security_parameters.client_random,
+ KeyLog = ssl_logger:keylog_early_data(ClientRand, Prf, EarlySecret),
+ ssl_logger:keylog(KeyLog, ClientRand, Fun),
+ CSState;
+ _ ->
+ SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret},
+ CSState#{security_parameters := SecParams}
+ end;
+maybe_store_early_data_secret(_, _, CSState, _) ->
+ CSState.
%% Server
%% get_pre_shared_key(undefined, HKDFAlgo) ->
%% binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo));
@@ -1071,42 +1168,6 @@ choose_psk([#ticket_data{
choose_psk([_|T], SelectedIdentity) ->
choose_psk(T, SelectedIdentity).
-
-calculate_traffic_secrets(#state{
- static_env = #static_env{role = Role},
- connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- tls_handshake_history = HHistory}} = State0) ->
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDFAlgo,
- cipher_suite = CipherSuite,
- master_secret = HandshakeSecret} = SecParamsR,
-
- MasterSecret =
- tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
-
- %% Get the correct list messages for the handshake context.
- Messages = get_handshake_context(Role, HHistory),
-
- %% Calculate [sender]_application_traffic_secret_0
- ClientAppTrafficSecret0 =
- tls_v1:client_application_traffic_secret_0(HKDFAlgo, MasterSecret, Messages),
- ServerAppTrafficSecret0 =
- tls_v1:server_application_traffic_secret_0(HKDFAlgo, MasterSecret, Messages),
-
- %% Calculate traffic keys
- KeyLength = tls_v1:key_length(CipherSuite),
- {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientAppTrafficSecret0),
- {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerAppTrafficSecret0),
-
- update_pending_connection_states(State0, MasterSecret, undefined,
- ClientAppTrafficSecret0, ServerAppTrafficSecret0,
- ReadKey, ReadIV, undefined,
- WriteKey, WriteIV, undefined).
-
-
calculate_read_traffic_secrets(#state{
static_env = #static_env{role = Role},
connection_states = #{pending_read := PendingRead0} =
@@ -1114,29 +1175,7 @@ calculate_read_traffic_secrets(#state{
handshake_env =
#handshake_env{
tls_handshake_history = HHistory}} = State0) ->
- #{security_parameters := SecParamsR,
- cipher_state := #cipher_state{finished_key = FinishedKey}} =
- ssl_record:pending_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDFAlgo,
- cipher_suite = CipherSuite,
- master_secret = HandshakeSecret} = SecParamsR,
-
- MasterSecret =
- tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
-
- %% Get the correct list messages for the handshake context.
- Messages = get_handshake_context(Role, HHistory),
-
- %% Calculate [sender]_application_traffic_secret_0
- ClientAppTrafficSecret0 =
- tls_v1:client_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
-
- %% Calculate traffic keys
- KeyLength = tls_v1:key_length(CipherSuite),
- {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientAppTrafficSecret0),
- PendingRead = update_connection_state(PendingRead0, MasterSecret, undefined,
- ClientAppTrafficSecret0,
- ReadKey, ReadIV, FinishedKey),
+ PendingRead = calculate_traffic_secrets(PendingRead0, read, Role, HHistory),
State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}}.
calculate_write_traffic_secrets(#state{
@@ -1146,31 +1185,47 @@ calculate_write_traffic_secrets(#state{
handshake_env =
#handshake_env{
tls_handshake_history = HHistory}} = State0) ->
- #{security_parameters := SecParamsR,
- cipher_state := #cipher_state{finished_key = FinishedKey}} =
- ssl_record:pending_connection_state(ConnectionStates, write),
+ PendingWrite = calculate_traffic_secrets(PendingWrite0, write, Role, HHistory),
+ State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}}.
+
+
+calculate_traffic_secrets(ConnectionState, ReadorWrite, Role, HHistory) ->
+ #{security_parameters := SecParams,
+ cipher_state := #cipher_state{finished_key = FinishedKey}} = ConnectionState,
#security_parameters{prf_algorithm = HKDFAlgo,
cipher_suite = CipherSuite,
- master_secret = HandshakeSecret} = SecParamsR,
-
+ master_secret = HandshakeSecret,
+ bulk_cipher_algorithm = BulkCipherAlgo} = SecParams,
MasterSecret =
tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
-
- %% Get the correct list messages for the handshake context.
Messages = get_handshake_context(Role, HHistory),
- %% Calculate [sender]_application_traffic_secret_0
- ServerAppTrafficSecret0 =
- tls_v1:server_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
+ AppTrafficSecret0 =
+ case {Role, ReadorWrite} of
+ {server, write} ->
+ tls_v1:server_application_traffic_secret_0(HKDFAlgo,
+ MasterSecret, Messages);
+ {client, read} ->
+ tls_v1:server_application_traffic_secret_0(HKDFAlgo,
+ MasterSecret, Messages);
+ {client, write} ->
+ tls_v1:client_application_traffic_secret_0(HKDFAlgo,
+ MasterSecret, Messages);
+ {server, read} ->
+ tls_v1:client_application_traffic_secret_0(HKDFAlgo,
+ MasterSecret, Messages)
+ end,
- %% Calculate traffic keys
KeyLength = tls_v1:key_length(CipherSuite),
- {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerAppTrafficSecret0),
- PendingWrite = update_connection_state(PendingWrite0, MasterSecret, undefined,
- ServerAppTrafficSecret0,
- WriteKey, WriteIV, FinishedKey),
- State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}}.
+ {Key, IV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, AppTrafficSecret0),
+
+ NewSecParams =
+ SecParams#security_parameters{master_secret = MasterSecret,
+ application_traffic_secret = AppTrafficSecret0},
+ ConnectionState#{security_parameters => NewSecParams,
+ cipher_state =>
+ cipher_init(BulkCipherAlgo, Key, IV, FinishedKey)}.
%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
@@ -1252,70 +1307,6 @@ overwrite_client_random(ConnectionState = #{security_parameters := SecurityParam
SecurityParameters = SecurityParameters0#security_parameters{client_random = ClientRandom},
ConnectionState#{security_parameters => SecurityParameters}.
-
-maybe_store_handshake_traffic_secret(#state{connection_states =
- #{pending_read := PendingRead} = CS,
- ssl_options = #{keep_secrets := true}} = State,
- ClientHSTrafficSecret, ServerHSTrafficSecret) ->
- PendingRead1 = store_handshake_traffic_secret(PendingRead, ClientHSTrafficSecret, ServerHSTrafficSecret),
- State#state{connection_states = CS#{pending_read => PendingRead1}};
-maybe_store_handshake_traffic_secret(State, _, _) ->
- State.
-
-store_handshake_traffic_secret(ConnectionState, ClientHSTrafficSecret, ServerHSTrafficSecret) ->
- ConnectionState#{client_handshake_traffic_secret => ClientHSTrafficSecret,
- server_handshake_traffic_secret => ServerHSTrafficSecret}.
-
-
-update_pending_connection_states(#state{
- static_env = #static_env{role = server},
- connection_states =
- CS = #{pending_read := PendingRead0,
- pending_write := PendingWrite0}} = State,
- HandshakeSecret, ResumptionMasterSecret,
- ClientAppTrafficSecret, ServerAppTrafficSecret,
- ReadKey, ReadIV, ReadFinishedKey,
- WriteKey, WriteIV, WriteFinishedKey) ->
- PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ResumptionMasterSecret,
- ClientAppTrafficSecret,
- ReadKey, ReadIV, ReadFinishedKey),
- PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, ResumptionMasterSecret,
- ServerAppTrafficSecret,
- WriteKey, WriteIV, WriteFinishedKey),
- State#state{connection_states = CS#{pending_read => PendingRead,
- pending_write => PendingWrite}};
-update_pending_connection_states(#state{
- static_env = #static_env{role = client},
- connection_states =
- CS = #{pending_read := PendingRead0,
- pending_write := PendingWrite0}} = State,
- HandshakeSecret, ResumptionMasterSecret,
- ClientAppTrafficSecret, ServerAppTrafficSecret,
- ReadKey, ReadIV, ReadFinishedKey,
- WriteKey, WriteIV, WriteFinishedKey) ->
- PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ResumptionMasterSecret,
- ServerAppTrafficSecret,
- WriteKey, WriteIV, WriteFinishedKey),
- PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, ResumptionMasterSecret,
- ClientAppTrafficSecret,
- ReadKey, ReadIV, ReadFinishedKey),
- State#state{connection_states = CS#{pending_read => PendingRead,
- pending_write => PendingWrite}}.
-
-
-update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0},
- HandshakeSecret, ResumptionMasterSecret,
- ApplicationTrafficSecret, Key, IV, FinishedKey) ->
- %% Store secret
- SecurityParameters = SecurityParameters0#security_parameters{
- master_secret = HandshakeSecret,
- resumption_master_secret = ResumptionMasterSecret,
- application_traffic_secret = ApplicationTrafficSecret},
- BulkCipherAlgo = SecurityParameters#security_parameters.bulk_cipher_algorithm,
- ConnectionState#{security_parameters => SecurityParameters,
- cipher_state => cipher_init(BulkCipherAlgo, Key, IV, FinishedKey)}.
-
-
update_start_state(State, Map) ->
Cipher = maps:get(cipher, Map, undefined),
KeyShare = maps:get(key_share, Map, undefined),
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 6bd4f060ab..82c4db86c4 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -488,7 +488,7 @@ initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
pending_early_data_size => MaxEarlyDataSize,
max_fragment_length => undefined,
trial_decryption => false,
- early_data_expected => false
+ early_data_accepted => false
}.
%% Used by logging to recreate the received bytes
diff --git a/lib/ssl/src/tls_server_connection_1_3.erl b/lib/ssl/src/tls_server_connection_1_3.erl
index 75babb99b4..b844e6ab83 100644
--- a/lib/ssl/src/tls_server_connection_1_3.erl
+++ b/lib/ssl/src/tls_server_connection_1_3.erl
@@ -256,15 +256,14 @@ negotiated(internal = Type, #change_cipher_spec{} = Msg, State) ->
negotiated(internal, {start_handshake, _} = Message, State0) ->
case send_hello_flight(Message, State0) of
#alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, negotiated, State0);
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0);
{State1, NextState} ->
State2 = tls_handshake_1_3:calculate_write_traffic_secrets(State1),
State = ssl_record:step_encryption_state_write(State2),
- maybe_keylog(State),
{next_state, NextState, State, []}
end;
negotiated(info, Msg, State) ->
- tls_connection:gen_info(Msg, negotiated, State);
+ tls_connection:gen_info(Msg, ?FUNCTION_NAME, State);
negotiated(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
@@ -476,10 +475,10 @@ do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
Opts = State2#state.ssl_options,
State3 = case maps:get(keep_secrets, Opts, false) of
- true ->
- tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random);
false ->
- State2
+ State2;
+ _ ->
+ tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random)
end,
State4 = tls_handshake_1_3:update_start_state(State3,
@@ -554,14 +553,7 @@ send_hello_flight({start_handshake, PSK0},
true ->
ssl_record:step_encryption_state_write(State3);
false ->
- %% Read state is overwritten when handshake secrets are set.
- %% Trial_decryption and early_data_accepted must be set here!
- update_current_read(
- ssl_record:step_encryption_state(State3),
- true, %% trial_decryption
- false %% early_data_accepted
- )
-
+ ssl_record:step_encryption_state(State3)
end,
%% Create EncryptedExtensions
@@ -617,32 +609,41 @@ validate_cookie(#cookie{cookie = Cookie0}, #state{ssl_options = #{cookie := true
validate_cookie(_,_) ->
{error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}.
-session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State, negotiated}, _) ->
+session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State,
+ negotiated}, _) ->
{ok, {State, negotiated, undefined}}; % Resumption prohibited
-session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined = PSK)
+session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State,
+ negotiated}, undefined = PSK)
when Tickets =/= disabled ->
{ok, {State, negotiated, PSK}}; % No resumption
session_resumption({#state{ssl_options = #{session_tickets := Tickets},
handshake_env = #handshake_env{
- early_data_accepted = false}} = State0, negotiated}, PSKInfo)
+ early_data_accepted = false}} = State0,
+ negotiated}, PSKInfo)
when Tickets =/= disabled -> % Resumption but early data prohibited
State1 = tls_gen_connection_1_3:handle_resumption(State0, ok),
{Index, PSK, PeerCert} = PSKInfo,
- State = maybe_store_peer_cert(State1, PeerCert),
- State = maybe_store_peer_cert(State1, PeerCert),
- {ok, {State, negotiated, {Index, PSK}}};
+ State = #state{connection_states = #{pending_read := Read0} = CS} =
+ maybe_store_peer_cert(State1, PeerCert),
+ Read = Read0#{trial_decryption => true},
+ {ok, {State#state{connection_states = CS#{pending_read => Read}}, negotiated, {Index, PSK}}};
session_resumption({#state{ssl_options = #{session_tickets := Tickets},
handshake_env = #handshake_env{
- early_data_accepted = true}} = State0, negotiated}, PSKInfo)
+ early_data_accepted = true}} = State0,
+ negotiated}, PSKInfo)
when Tickets =/= disabled -> % Resumption with early data allowed
State1 = tls_gen_connection_1_3:handle_resumption(State0, ok),
%% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed.
{Index, PSK, PeerCert} = PSKInfo,
- State2 = tls_handshake_1_3:calculate_client_early_traffic_secret(State1, PSK),
+ State2 = maybe_store_peer_cert(State1, PeerCert),
+ State3 =
+ tls_handshake_1_3:calculate_client_early_traffic_secret(State2, PSK),
%% Set 0-RTT traffic keys for reading early_data
- State3 = ssl_record:step_encryption_state_read(State2),
- State4 = maybe_store_peer_cert(State3, PeerCert),
- State = update_current_read(State4, true, true),
+ State4 = #state{connection_states = #{current_read := Read0} = CS}
+ = ssl_record:step_encryption_state_read(State3),
+ Read = Read0#{trial_decryption => true,
+ early_data_accepted => true},
+ State = State4#state{connection_states = CS#{current_read => Read}},
{ok, {State, negotiated, {Index, PSK}}}.
maybe_store_peer_cert(State, undefined) ->
@@ -799,12 +800,6 @@ send_hello_retry_request(State0, _, _, _) ->
%% Suitable key found.
{ok, {State0, negotiated}}.
-update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataExpected) ->
- Read0 = ssl_record:current_connection_state(CS, read),
- Read = Read0#{trial_decryption => TrialDecryption,
- early_data_accepted => EarlyDataExpected},
- State#state{connection_states = CS#{current_read => Read}}.
-
handle_early_data(State, enabled, #early_data_indication{}) ->
%% Accept early data
HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true},
@@ -925,23 +920,3 @@ handle_alpn([ServerProtocol|T], ClientProtocols) ->
false ->
handle_alpn(T, ClientProtocols)
end.
-
-maybe_keylog(#state{ssl_options = Options,
- connection_states = ConnectionStates,
- protocol_specific = PS})->
- KeylogFun = maps:get(keep_secrets, Options, undefined),
- maybe_keylog(KeylogFun, PS, ConnectionStates).
-
-maybe_keylog({Keylog, Fun}, ProtocolSpecific, ConnectionStates) when Keylog == keylog_hs;
- Keylog == keylog ->
- N = maps:get(num_key_updates, ProtocolSpecific, 0),
- #{security_parameters := #security_parameters{client_random = ClientRandom,
- prf_algorithm = Prf,
- application_traffic_secret = TrafficSecret}}
- = ssl_record:current_connection_state(ConnectionStates, write),
- TrafficKeyLog = ssl_logger:keylog_traffic_1_3(server, ClientRandom,
- Prf, TrafficSecret, N),
-
- ssl_logger:keylog(TrafficKeyLog, ClientRandom, Fun);
-maybe_keylog(_,_,_) ->
- ok.
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
index a6295d7818..42159708bd 100644
--- a/lib/ssl/test/ssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -1215,7 +1215,6 @@ early_data_basic(Config) when is_list(Config) ->
verify_active_session_resumption,
[false]}},
{from, self()}, {options, ClientOpts1}]),
- skip_keylogs(4), %% HS and two traffic secrets
ssl_test_lib:check_result(Server0, ok, Client0, ok),
@@ -1235,14 +1234,6 @@ early_data_basic(Config) when is_list(Config) ->
verify_active_session_resumption,
[true]}},
{from, self()}, {options, ClientOpts2}]),
- %% Check that we get the EARLY DATA keylog event
- receive
- {keylog, #{items := EarlyKeylog}} ->
- ["CLIENT_EARLY_TRAFFIC_SECRET" ++ _| _] = EarlyKeylog
- end,
- skip_keylogs(4), %% HS and two traffic secrets so they do not end up
- %% in check_result
-
ssl_test_lib:check_result(Server0, ok, Client1, ok),
diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl
index db708bb364..245c88397c 100644
--- a/lib/ssl/test/tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_version_SUITE.erl
@@ -29,6 +29,8 @@
groups/0,
init_per_suite/1,
init_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2,
end_per_suite/1,
end_per_group/2
]).
@@ -79,7 +81,9 @@
client_cert_fail_alert_active/0,
client_cert_fail_alert_active/1,
client_cert_fail_alert_passive/0,
- client_cert_fail_alert_passive/1
+ client_cert_fail_alert_passive/1,
+ keylog_on_alert/0,
+ keylog_on_alert/1
]).
@@ -89,7 +93,7 @@
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-all() ->
+all() ->
[
cert_groups()
].
@@ -117,7 +121,8 @@ tls_1_3_1_2_tests() ->
middle_box_client_tls_v2_session_reused,
renegotiate_error,
client_cert_fail_alert_active,
- client_cert_fail_alert_passive
+ client_cert_fail_alert_passive,
+ keylog_on_alert
].
legacy_tests() ->
[tls_client_tls10_server,
@@ -137,10 +142,10 @@ init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
ok ->
- case ssl_test_lib:sufficient_crypto_support('tlsv1.3') of
+ case ssl_test_lib:sufficient_crypto_support('tlsv1.3') of
true ->
ssl_test_lib:clean_start(),
- [{client_type, erlang}, {server_type, erlang} |
+ [{client_type, erlang}, {server_type, erlang} |
Config];
false ->
{skip, "Insufficient crypto support for TLS-1.3"}
@@ -158,18 +163,18 @@ init_per_group(rsa, Config0) ->
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
[{client_type, erlang},
- {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
+ {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
init_per_group(ecdsa, Config0) ->
PKAlg = crypto:supports(public_keys),
- case lists:member(ecdsa, PKAlg) andalso
+ case lists:member(ecdsa, PKAlg) andalso
(lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
COpts = proplists:get_value(client_ecdsa_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
[{client_type, erlang},
- {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
+ {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
false ->
{skip, "Missing EC crypto support"}
@@ -177,6 +182,15 @@ init_per_group(ecdsa, Config0) ->
end_per_group(GroupName, Config) ->
ssl_test_lib:end_per_group(GroupName, Config).
+
+init_per_testcase(_TestCase, Config) ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 20}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -193,9 +207,8 @@ tls13_client_tls12_server(Config) when is_list(Config) ->
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
-
tls13_client_with_ext_tls12_server() ->
- [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when "
+ [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when "
"client has TLS 1.3 specific extensions"}].
tls13_client_with_ext_tls12_server(Config) ->
@@ -215,11 +228,11 @@ tls13_client_with_ext_tls12_server(Config) ->
rsa_pkcs1_sha256,
ecdsa_sha1]}|ClientOpts0],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
-
+
tls12_client_tls13_server() ->
[{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}].
-tls12_client_tls13_server(Config) when is_list(Config) ->
+tls12_client_tls13_server(Config) when is_list(Config) ->
ClientOpts = [{versions, ['tlsv1.1', 'tlsv1.2']} |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions, ['tlsv1.3', 'tlsv1.2']},
@@ -234,7 +247,7 @@ tls_client_tls10_server(Config) when is_list(Config) ->
[{key_exchange, fun(srp_rsa) -> false;
(srp_anon) -> false;
(srp_dss) -> false;
- (_) -> true end}]),
+ (_) -> true end}]),
ClientOpts = [{versions, ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
{ciphers, CCiphers} |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
@@ -251,14 +264,14 @@ tls_client_tls11_server(Config) when is_list(Config) ->
[{key_exchange, fun(srp_rsa) -> false;
(srp_anon) -> false;
(srp_dss) -> false;
- (_) -> true end}]),
+ (_) -> true end}]),
ClientOpts = [{versions,
['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
{ciphers, CCiphers} |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,['tlsv1.1']},
{verify, verify_peer}, {fail_if_no_peer_cert, true},
- {ciphers, ssl:cipher_suites(all, 'tlsv1.1')}
+ {ciphers, ssl:cipher_suites(all, 'tlsv1.1')}
| ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
@@ -280,7 +293,7 @@ tls10_client_tls_server(Config) when is_list(Config) ->
[{key_exchange, fun(srp_rsa) -> false;
(srp_anon) -> false;
(srp_dss) -> false;
- (_) -> true end}]),
+ (_) -> true end}]),
ClientOpts = [{versions, ['tlsv1']},
{ciphers, ssl:cipher_suites(all, 'tlsv1')} |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
@@ -298,7 +311,6 @@ tls11_client_tls_server(Config) when is_list(Config) ->
(srp_anon) -> false;
(srp_dss) -> false;
(_) -> true end}]),
-
ClientOpts = [{versions, ['tlsv1.1']},
{ciphers, ssl:cipher_suites(all, 'tlsv1.1')} |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
@@ -339,7 +351,7 @@ legacy_tls12_server_tls_client(Config) when is_list(Config) ->
SHA = sha384,
Prop = proplists:get_value(tc_group_properties, Config),
Alg = proplists:get_value(name, Prop),
- #{client_config := ClientOpts0,
+ #{client_config := ClientOpts0,
server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_der(Alg, [{server_chain,
[[{digest, SHA}],
[{digest, SHA}],
@@ -580,9 +592,9 @@ client_cert_fail_alert_passive(Config) when is_list(Config) ->
{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts0)],
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}| ServerOpts0],
alert_passive(ServerOpts, ClientOpts, recv,
- ServerNode, Hostname),
+ ServerNode, Hostname, unknown_ca),
alert_passive(ServerOpts, ClientOpts, setopts,
- ServerNode, Hostname).
+ ServerNode, Hostname, unknown_ca).
tls13_client_tls11_server() ->
[{doc,"Test that a TLS 1.3 client gets old server alert from TLS 1.0 server."}].
@@ -591,6 +603,83 @@ tls13_client_tls11_server(Config) when is_list(Config) ->
ServerOpts = [{versions, ['tlsv1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security).
+keylog_on_alert() ->
+ [{doc,"Test that keep_secrets keylog_hs callback, if specified, "
+ "is called with keylog info when handshake alert is raised"}].
+
+keylog_on_alert(Config) when is_list(Config) ->
+ ssl:clear_pem_cache(),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"),
+ create_bad_client_certfile(NewClientCertFile, ClientOpts0),
+ ClientOpts = [{versions, ['tlsv1.3']}, {active, false}, {certfile, NewClientCertFile}|
+ proplists:delete(certfile, ClientOpts0)],
+ ServerOpts = [{versions, ['tlsv1.3']},
+ {verify, verify_peer}, {fail_if_no_peer_cert, true}
+ | ServerOpts0],
+
+ Me = self(),
+ Fun = fun(AlertInfo) ->
+ Me ! {alert_info, AlertInfo}
+ end,
+ keylog_alert_passive([{keep_secrets, {keylog_hs, Fun}} | ServerOpts], ClientOpts, recv,
+ ServerNode, Hostname),
+
+ receive_server_keylog_for_cert_alert(),
+
+ keylog_alert_passive(ServerOpts,
+ [{keep_secrets, {keylog_hs, Fun}} | ClientOpts], recv,
+ ServerNode, Hostname),
+
+ receive_client_keylog_for_cert_alert(),
+
+ ClientNoCert = proplists:delete(keyfile, proplists:delete(certfile, ClientOpts0)),
+ keylog_alert_passive([{keep_secrets, {keylog_hs, Fun}} | ServerOpts],
+ [{active, false} | ClientNoCert],
+ recv, ServerNode, Hostname),
+ receive_server_keylog_for_cert_alert().
+
+receive_server_keylog_for_cert_alert() ->
+ %% This alert will be decrypted with application secrets
+ %% as client is already in connection
+ receive
+ {alert_info, #{items := SKeyLog}} ->
+ case keylog_prefixes(["CLIENT_HANDSHAKE_TRAFFIC_SECRET",
+ "SERVER_HANDSHAKE_TRAFFIC_SECRET",
+ "SERVER_TRAFFIC_SECRET_0"], SKeyLog) of
+ true ->
+ ok;
+ false ->
+ ct:fail({server_received, SKeyLog})
+ end
+ end.
+
+receive_client_keylog_for_cert_alert() ->
+ receive
+ {alert_info, #{items := CKeyLog}} ->
+ case keylog_prefixes(["CLIENT_HANDSHAKE_TRAFFIC_SECRET",
+ "SERVER_HANDSHAKE_TRAFFIC_SECRET",
+ "CLIENT_TRAFFIC_SECRET_0",
+ "SERVER_TRAFFIC_SECRET_0"], CKeyLog) of
+ true ->
+ ok;
+ false ->
+ ct:fail({client_received, CKeyLog})
+ end
+ end.
+keylog_prefixes([], []) ->
+ true;
+keylog_prefixes([Prefix | Prefixes], [Secret | Secrets]) ->
+ case lists:prefix(Prefix, Secret) of
+ true ->
+ keylog_prefixes(Prefixes, Secrets);
+ false ->
+ false
+ end.
+
%%--------------------------------------------------------------------
%% Internal functions and callbacks -----------------------------------
%%--------------------------------------------------------------------
@@ -622,7 +711,7 @@ check_session_id(Socket, Expected) ->
end.
alert_passive(ServerOpts, ClientOpts, Function,
- ServerNode, Hostname) ->
+ ServerNode, Hostname, AlertAtom) ->
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
@@ -632,16 +721,36 @@ alert_passive(ServerOpts, ClientOpts, Function,
ct:sleep(500),
case Function of
recv ->
- {error, {tls_alert, {unknown_ca,_}}} = ssl:recv(Socket, 0);
+ {error, {tls_alert, {AlertAtom,_}}} = ssl:recv(Socket, 0);
setopts ->
{error, {tls_alert, {unknown_ca,_}}} = ssl:setopts(Socket, [{active, once}])
end.
-create_bad_client_certfile(NewClientCertFile, ClientOpts0) ->
- KeyFile = proplists:get_value(keyfile, ClientOpts0),
+keylog_alert_passive(ServerOpts, ClientOpts, Function,
+ ServerNode, Hostname) ->
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Fun = fun() ->
+ {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts),
+ case Function of
+ recv ->
+ ssl:recv(Socket, 0);
+ setopts ->
+ ssl:setopts(Socket, [{active, once}])
+ end
+ end,
+ %% Execute in other process and let test case detect key-log message.
+ spawn_link(Fun).
+
+create_bad_client_certfile(NewClientCertFile, ClientOpts) ->
+ KeyFile = proplists:get_value(keyfile, ClientOpts),
[KeyEntry] = ssl_test_lib:pem_to_der(KeyFile),
Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)),
- ClientCertFile = proplists:get_value(certfile, ClientOpts0),
+ ClientCertFile = proplists:get_value(certfile, ClientOpts),
[{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile),
ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp),
--
2.51.0