summaryrefslogtreecommitdiff
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/Makefile2
-rw-r--r--lib/ssl/src/dtls_connection.erl112
-rw-r--r--lib/ssl/src/dtls_gen_connection.erl8
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl148
-rw-r--r--lib/ssl/src/dtls_record.erl66
-rw-r--r--lib/ssl/src/inet_tls_dist.erl11
-rw-r--r--lib/ssl/src/ssl.app.src14
-rw-r--r--lib/ssl/src/ssl.erl163
-rw-r--r--lib/ssl/src/ssl_certificate.erl119
-rw-r--r--lib/ssl/src/ssl_cipher.erl16
-rw-r--r--lib/ssl/src/ssl_config.erl193
-rw-r--r--lib/ssl/src/ssl_connection.hrl21
-rw-r--r--lib/ssl/src/ssl_crl_cache.erl6
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl84
-rw-r--r--lib/ssl/src/ssl_handshake.erl221
-rw-r--r--lib/ssl/src/ssl_handshake.hrl1
-rw-r--r--lib/ssl/src/ssl_internal.hrl54
-rw-r--r--lib/ssl/src/ssl_logger.erl6
-rw-r--r--lib/ssl/src/ssl_manager.erl18
-rw-r--r--lib/ssl/src/ssl_pem_cache.erl5
-rw-r--r--lib/ssl/src/ssl_pkix_db.erl76
-rw-r--r--lib/ssl/src/ssl_record.erl13
-rw-r--r--lib/ssl/src/ssl_session.erl106
-rw-r--r--lib/ssl/src/tls_client_ticket_store.erl14
-rw-r--r--lib/ssl/src/tls_connection.erl81
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl301
-rw-r--r--lib/ssl/src/tls_dtls_connection.erl190
-rw-r--r--lib/ssl/src/tls_gen_connection.erl246
-rw-r--r--lib/ssl/src/tls_handshake.erl54
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl262
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl6
-rw-r--r--lib/ssl/src/tls_record.erl8
-rw-r--r--lib/ssl/src/tls_record.hrl11
-rw-r--r--lib/ssl/src/tls_record_1_3.erl163
-rw-r--r--lib/ssl/src/tls_record_1_3.hrl4
-rw-r--r--lib/ssl/src/tls_sender.erl153
-rw-r--r--lib/ssl/src/tls_server_session_ticket.erl6
-rw-r--r--lib/ssl/src/tls_socket.erl4
-rw-r--r--lib/ssl/src/tls_v1.erl64
39 files changed, 1972 insertions, 1058 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 1e55bb497d..789bed5c3f 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -173,7 +173,7 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
# Targets
# ----------------------------------------------------
-opt debug: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE)
+$(TYPES): $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE)
deps: $(DEP_FILE)
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 65076994f9..08229d8bb5 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -46,7 +46,8 @@
%% ClientKeyExchange \
%% CertificateVerify* Flight 5
%% [ChangeCipherSpec] /
-%% Finished --------> /
+%% NextProtocol* /
+%% Finished --------> /
%%
%% [ChangeCipherSpec] \ Flight 6
%% <-------- Finished /
@@ -64,7 +65,8 @@
%% <-------- Finished / part 2
%%
%% [ChangeCipherSpec] \ Abbrev Flight 3
-%% Finished --------> /
+%% NextProtocol* /
+%% Finished --------> /
%%
%%
%% Message Flights for Abbbriviated Handshake
@@ -142,6 +144,7 @@
user_hello/3,
wait_ocsp_stapling/3,
certify/3,
+ wait_cert_verify/3,
cipher/3,
abbreviated/3,
connection/3]).
@@ -160,16 +163,15 @@
%%====================================================================
init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
process_flag(trap_exit, true),
- State0 = #state{protocol_specific = Map} =
- initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
+ State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
try
State = ssl_gen_statem:ssl_config(State0#state.ssl_options,
Role, State0),
gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch
throw:Error ->
- EState = State0#state{protocol_specific =
- Map#{error => Error}},
+ #state{protocol_specific = Map} = State0,
+ EState = State0#state{protocol_specific = Map#{error => Error}},
gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
%%====================================================================
@@ -208,13 +210,14 @@ initial_hello({call, From}, {start, Timeout},
session_cache_cb = CacheCb},
protocol_specific = PS,
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv,
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv,
ssl_options = #{versions := Versions} = SslOpts,
session = Session0,
connection_states = ConnectionStates0
} = State0) ->
Packages = maps:get(active_n, PS),
dtls_socket:setopts(Transport, Socket, [{active,Packages}]),
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts),
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Session#session.session_id, Renegotiation),
@@ -324,14 +327,12 @@ hello(internal, #hello_verify_request{cookie = Cookie},
},
dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions);
hello(internal, #client_hello{extensions = Extensions} = Hello,
- #state{ssl_options = #{handshake := hello},
- handshake_env = HsEnv,
+ #state{handshake_env = #handshake_env{continue_status = pause},
start_or_recv_from = From} = State0) ->
try tls_dtls_connection:handle_sni_extension(State0, Hello) of
#state{} = State ->
- {next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{hello = Hello}},
- [{reply, From, {ok, Extensions}}]}
+ {next_state, user_hello, State#state{start_or_recv_from = undefined},
+ [{postpone, true}, {reply, From, {ok, Extensions}}]}
catch throw:#alert{} = Alert ->
alert_or_reset_connection(Alert, ?FUNCTION_NAME, State0)
end;
@@ -354,16 +355,11 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #sta
hello(internal, Hello#client_hello{cookie = <<>>}, State)
end
end;
-hello(internal, #server_hello{extensions = Extensions} = Hello,
- #state{ssl_options = #{
- handshake := hello},
- handshake_env = HsEnv,
+hello(internal, #server_hello{extensions = Extensions},
+ #state{handshake_env = #handshake_env{continue_status = pause},
start_or_recv_from = From} = State) ->
- {next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{
- hello = Hello}},
- [{reply, From, {ok, Extensions}}]};
-
+ {next_state, user_hello, State#state{start_or_recv_from = undefined},
+ [{postpone, true},{reply, From, {ok, Extensions}}]};
hello(internal, #server_hello{} = Hello,
#state{
static_env = #static_env{role = client},
@@ -469,6 +465,24 @@ certify(state_timeout, Event, State) ->
certify(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert_verify(enter, _Event, State0) ->
+ {State, Actions} = handle_flight_timer(State0),
+ {keep_state, State, Actions};
+wait_cert_verify(info, Event, State) ->
+ gen_info(Event, ?FUNCTION_NAME, State);
+wait_cert_verify(state_timeout, Event, State) ->
+ handle_state_timeout(Event, ?FUNCTION_NAME, State);
+wait_cert_verify(Type, Event, State) ->
+ try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State)
+ catch throw:#alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
+ end.
+
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
@@ -498,13 +512,20 @@ cipher(Type, Event, State) ->
#hello_request{} | #client_hello{}| term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-connection(enter, _, #state{connection_states = Cs0} = State0) ->
- State = case maps:is_key(previous_cs, Cs0) of
- false ->
- State0;
- true ->
- Cs = maps:remove(previous_cs, Cs0),
- State0#state{connection_states = Cs}
+connection(enter, _, #state{connection_states = Cs0,
+ static_env = Env} = State0) ->
+ State = case Env of
+ #static_env{socket = {Listener, {Client, _}}} ->
+ dtls_packet_demux:connection_setup(Listener, Client),
+ case maps:is_key(previous_cs, Cs0) of
+ false ->
+ State0;
+ true ->
+ Cs = maps:remove(previous_cs, Cs0),
+ State0#state{connection_states = Cs}
+ end;
+ _ -> %% client
+ State0
end,
{keep_state, State};
connection(info, Event, State) ->
@@ -516,13 +537,14 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho
session_cache_cb = CacheCb
},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv,
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv,
session = Session0,
ssl_options = #{versions := Versions} = SslOpts,
connection_states = ConnectionStates0,
protocol_specific = PS
} = State0) ->
#{current_cookie_secret := Cookie} = PS,
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts),
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs),
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts,
Session#session.session_id, Renegotiation, undefined),
@@ -557,14 +579,20 @@ connection(internal, #client_hello{}, #state{static_env = #static_env{role = ser
dtls_gen_connection:next_event(?FUNCTION_NAME, Record, State);
connection(internal, new_connection, #state{ssl_options=SSLOptions,
handshake_env=HsEnv,
+ static_env = #static_env{socket = {Listener, {Client, _}}},
connection_states = OldCs} = State) ->
case maps:get(previous_cs, OldCs, undefined) of
undefined ->
- BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
- ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation),
- ConnectionStates = ConnectionStates0#{previous_cs => OldCs},
- {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {false, first}},
- connection_states = ConnectionStates}};
+ case dtls_packet_demux:new_connection(Listener, Client) of
+ true ->
+ {keep_state, State};
+ false ->
+ BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
+ ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation),
+ ConnectionStates = ConnectionStates0#{previous_cs => OldCs},
+ {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {false, first}},
+ connection_states = ConnectionStates}}
+ end;
_ ->
%% Someone spamming new_connection, just drop them
{keep_state, State}
@@ -628,6 +656,7 @@ format_status(Type, Data) ->
initial_state(Role, Host, Port, Socket,
{#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ put(log_level, maps:get(log_level, SSLOptions)),
BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation),
#{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role),
@@ -681,14 +710,14 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State
handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
renegotiation = {Renegotiation, _},
negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv,
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv,
session = Session0,
ssl_options = SslOpts} =
tls_dtls_connection:handle_sni_extension(State0, Hello),
SessionTracker = proplists:get_value(session_id_tracker, Trackers),
{Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} =
dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0,
- ConnectionStates0, CertKeyPairs, KeyExAlg}, Renegotiation),
+ ConnectionStates0, CertKeyAlts, KeyExAlg}, Renegotiation),
Protocol = case Protocol0 of
undefined -> CurrentProtocol;
_ -> Protocol0
@@ -738,20 +767,23 @@ gen_handshake(StateName, Type, Event, State) ->
catch
throw:#alert{}=Alert ->
alert_or_reset_connection(Alert, StateName, State);
- error:_ ->
+ error:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data),
alert_or_reset_connection(Alert, StateName, State)
end.
gen_info(Event, connection = StateName, State) ->
try dtls_gen_connection:handle_info(Event, StateName, State)
- catch error:_ ->
+ catch error:Reason:ST ->
+ ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]),
Alert = ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data),
alert_or_reset_connection(Alert, StateName, State)
end;
gen_info(Event, StateName, State) ->
try dtls_gen_connection:handle_info(Event, StateName, State)
- catch error:_ ->
+ catch error:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,malformed_handshake_data),
alert_or_reset_connection(Alert, StateName, State)
end.
diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl
index 4964d3d21f..c075fa5879 100644
--- a/lib/ssl/src/dtls_gen_connection.erl
+++ b/lib/ssl/src/dtls_gen_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -572,6 +572,12 @@ handle_info(new_cookie_secret, StateName,
{next_state, StateName, State#state{protocol_specific =
CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(),
previous_cookie_secret => Secret}}};
+handle_info({socket_reused, Client}, StateName,
+ #state{static_env = #static_env{socket = {_, {Client, _}}}} = State) ->
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = server}, StateName, State),
+ {stop, {shutdown, transport_closed}, State};
+
handle_info(Msg, StateName, State) ->
ssl_gen_statem:handle_info(Msg, StateName, State).
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index 19f1d2359c..c4cdb2eb01 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2023. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -33,6 +33,8 @@
sockname/1,
close/1,
new_owner/1,
+ new_connection/2,
+ connection_setup/2,
get_all_opts/1,
set_all_opts/2,
get_sock_opts/2,
@@ -55,7 +57,6 @@
dtls_options,
emulated_options,
dtls_msq_queues = kv_new(),
- clients = set_new(),
dtls_processes = kv_new(),
accepters = queue:new(),
first,
@@ -85,6 +86,12 @@ close(PacketSocket) ->
new_owner(PacketSocket) ->
call(PacketSocket, new_owner).
+new_connection(PacketSocket, Client) ->
+ call(PacketSocket, {new_connection, Client, self()}).
+
+connection_setup(PacketSocket, Client) ->
+ gen_server:cast(PacketSocket, {connection_setup, Client}).
+
get_sock_opts(PacketSocket, SplitSockOpts) ->
call(PacketSocket, {get_sock_opts, SplitSockOpts}).
get_all_opts(PacketSocket) ->
@@ -146,6 +153,18 @@ handle_call(close, _, #state{dtls_processes = Processes,
end;
handle_call(new_owner, _, State) ->
{reply, ok, State#state{close = false, first = true}};
+handle_call({new_connection, Old, _Pid}, _,
+ #state{accepters = Accepters, dtls_msq_queues = MsgQs0} = State) ->
+ case queue:is_empty(Accepters) of
+ false ->
+ OldQueue = kv_get(Old, MsgQs0),
+ MsgQs1 = kv_delete(Old, MsgQs0),
+ MsgQs = kv_insert({old,Old}, OldQueue, MsgQs1),
+ {reply, true, State#state{dtls_msq_queues = MsgQs}};
+ true ->
+ {reply, false, State}
+ end;
+
handle_call({get_sock_opts, {SocketOptNames, EmOptNames}}, _, #state{listener = Socket,
emulated_options = EmOpts} = State) ->
case get_socket_opts(Socket, SocketOptNames) of
@@ -170,7 +189,16 @@ handle_call({getstat, Options}, _, #state{listener = Socket, transport = {Tran
handle_cast({active_once, Client, Pid}, State0) ->
State = handle_active_once(Client, Pid, State0),
- {noreply, State}.
+ {noreply, State};
+handle_cast({connection_setup, Client}, #state{dtls_msq_queues = MsgQueues} = State) ->
+ case kv_lookup({old, Client}, MsgQueues) of
+ none ->
+ {noreply, State};
+ {value, {Pid, _}} ->
+ Pid ! {socket_reused, Client},
+ %% Will be deleted when handling DOWN message
+ {noreply, State}
+ end.
handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket, transport = {_,Transport,_,_,_}} = State0) ->
State = handle_datagram({IP, InPortNo}, Msg, State0),
@@ -190,24 +218,40 @@ handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, t
?LOG_NOTICE(Report),
{noreply, State};
handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag,_}} = State) ->
- Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]),
+ Report = io_lib:format("SSL Packet muliplexer shutdown: Socket error: ~p ~n", [Error]),
?LOG_NOTICE(Report),
{noreply, State#state{close=true}};
-handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients,
- dtls_processes = Processes0,
- dtls_msq_queues = MsgQueues0,
- close = ListenClosed} = State) ->
+handle_info({'DOWN', _, process, Pid, _},
+ #state{dtls_processes = Processes0,
+ dtls_msq_queues = MsgQueues0,
+ close = ListenClosed} = State0) ->
Client = kv_get(Pid, Processes0),
Processes = kv_delete(Pid, Processes0),
- MsgQueues = kv_delete(Client, MsgQueues0),
+ State = case kv_lookup(Client, MsgQueues0) of
+ none ->
+ MsgQueues1 = kv_delete({old, Client}, MsgQueues0),
+ State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1};
+ {value, {Pid, _}} ->
+ MsgQueues1 = kv_delete(Client, MsgQueues0),
+ %% Restore old process if exists
+ case kv_lookup({old, Client}, MsgQueues1) of
+ none ->
+ State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1};
+ {value, Old} ->
+ MsgQueues2 = kv_delete({old, Client}, MsgQueues1),
+ MsgQueues = kv_insert(Client, Old, MsgQueues2),
+ State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues}
+ end;
+ {value, _} -> %% Old process died (just delete its queue)
+ MsgQueues1 = kv_delete({old, Client}, MsgQueues0),
+ State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1}
+ end,
case ListenClosed andalso kv_empty(Processes) of
true ->
{stop, normal, State};
false ->
- {noreply, State#state{clients = set_delete(Client, Clients),
- dtls_processes = Processes,
- dtls_msq_queues = MsgQueues}}
+ {noreply, State}
end.
terminate(_Reason, _State) ->
@@ -219,55 +263,57 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-handle_datagram(Client, Msg, #state{clients = Clients,
- accepters = AcceptorsQueue0} = State) ->
- case set_is_member(Client, Clients) of
- false ->
+handle_datagram(Client, Msg, #state{dtls_msq_queues = MsgQueues, accepters = AcceptorsQueue0} = State) ->
+ case kv_lookup(Client, MsgQueues) of
+ none ->
case queue:out(AcceptorsQueue0) of
- {{value, {UserPid, From}}, AcceptorsQueue} ->
- setup_new_connection(UserPid, From, Client, Msg,
+ {{value, {UserPid, From}}, AcceptorsQueue} ->
+ setup_new_connection(UserPid, From, Client, Msg,
State#state{accepters = AcceptorsQueue});
{empty, _} ->
%% Drop packet client will resend
State
end;
- true ->
- dispatch(Client, Msg, State)
+ {value, Queue} ->
+ dispatch(Queue, Client, Msg, State)
end.
-dispatch(Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) ->
- case kv_lookup(Client, MsgQueues) of
- {value, Queue0} ->
- case queue:out(Queue0) of
- {{value, Pid}, Queue} when is_pid(Pid) ->
- Pid ! Msg,
- State#state{dtls_msq_queues =
- kv_update(Client, Queue, MsgQueues)};
- {{value, _UDP}, _Queue} ->
- State#state{dtls_msq_queues =
- kv_update(Client, queue:in(Msg, Queue0), MsgQueues)};
- {empty, Queue} ->
- State#state{dtls_msq_queues =
- kv_update(Client, queue:in(Msg, Queue), MsgQueues)}
- end
+dispatch({Pid, Queue0}, Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) ->
+ case queue:out(Queue0) of
+ {{value, Pid}, Queue} when is_pid(Pid) ->
+ Pid ! Msg,
+ State#state{dtls_msq_queues =
+ kv_update(Client, {Pid, Queue}, MsgQueues)};
+ {{value, _UDP}, _Queue} ->
+ State#state{dtls_msq_queues =
+ kv_update(Client, {Pid, queue:in(Msg, Queue0)}, MsgQueues)};
+ {empty, Queue} ->
+ State#state{dtls_msq_queues =
+ kv_update(Client, {Pid, queue:in(Msg, Queue)}, MsgQueues)}
end.
+
next_datagram(Socket, N) ->
inet:setopts(Socket, [{active, N}]).
handle_active_once(Client, Pid, #state{dtls_msq_queues = MsgQueues} = State0) ->
- Queue0 = kv_get(Client, MsgQueues),
+ {Key, Queue0} = case kv_lookup(Client, MsgQueues) of
+ {value, {Pid, Q0}} -> {Client, Q0};
+ _ ->
+ OldKey = {old, Client},
+ {Pid, Q0} = kv_get(OldKey, MsgQueues),
+ {OldKey, Q0}
+ end,
case queue:out(Queue0) of
- {{value, Pid}, _} when is_pid(Pid) ->
- State0;
- {{value, Msg}, Queue} ->
- Pid ! Msg,
- State0#state{dtls_msq_queues = kv_update(Client, Queue, MsgQueues)};
- {empty, Queue0} ->
- State0#state{dtls_msq_queues = kv_update(Client, queue:in(Pid, Queue0), MsgQueues)}
+ {{value, Pid}, _} when is_pid(Pid) ->
+ State0;
+ {{value, Msg}, Queue} ->
+ Pid ! Msg,
+ State0#state{dtls_msq_queues = kv_update(Key, {Pid, Queue}, MsgQueues)};
+ {empty, Queue0} ->
+ State0#state{dtls_msq_queues = kv_update(Key, {Pid, queue:in(Pid, Queue0)}, MsgQueues)}
end.
setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes,
- clients = Clients,
dtls_msq_queues = MsgQueues,
dtls_options = DTLSOpts,
port = Port,
@@ -281,8 +327,7 @@ setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes,
erlang:monitor(process, Pid),
gen_server:reply(From, {ok, Pid, {Client, Socket}}),
Pid ! Msg,
- State#state{clients = set_insert(Client, Clients),
- dtls_msq_queues = kv_insert(Client, queue:new(), MsgQueues),
+ State#state{dtls_msq_queues = kv_insert(Client, {Pid, queue:new()}, MsgQueues),
dtls_processes = kv_insert(Pid, Client, Processes)};
{error, Reason} ->
gen_server:reply(From, {error, Reason}),
@@ -295,7 +340,7 @@ kv_lookup(Key, Store) ->
gb_trees:lookup(Key, Store).
kv_insert(Key, Value, Store) ->
gb_trees:insert(Key, Value, Store).
-kv_get(Key, Store) ->
+kv_get(Key, Store) ->
gb_trees:get(Key, Store).
kv_delete(Key, Store) ->
gb_trees:delete(Key, Store).
@@ -304,15 +349,6 @@ kv_new() ->
kv_empty(Store) ->
gb_trees:is_empty(Store).
-set_new() ->
- gb_sets:empty().
-set_insert(Item, Set) ->
- gb_sets:insert(Item, Set).
-set_delete(Item, Set) ->
- gb_sets:delete(Item, Set).
-set_is_member(Item, Set) ->
- gb_sets:is_member(Item, Set).
-
call(Server, Msg) ->
try
gen_server:call(Server, Msg, infinity)
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index 6efc2dc8be..dadb16d250 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -175,9 +175,9 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}},
%% and returns it as a list of tls_compressed binaries also returns leftover
%% data
%%--------------------------------------------------------------------
-get_dtls_records(Data, Vinfo, Buffer, SslOpts) ->
+get_dtls_records(Data, Vinfo, Buffer, #{log_level := LogLevel}) ->
BinData = list_to_binary([Buffer, Data]),
- get_dtls_records_aux(Vinfo, BinData, [], SslOpts).
+ get_dtls_records_aux(Vinfo, BinData, [], LogLevel).
%%====================================================================
%% Encoding DTLS records
@@ -423,40 +423,48 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
max_fragment_length => undefined
}.
-get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
- Acc, #{log_level := LogLevel} = SslOpts)
+get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo,
+ <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Epoch), ?UINT48(SequenceNumber),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
+ Acc0, LogLevel)
when ((StateName == hello)
orelse ((StateName == certify) andalso (DataTag == udp))
- orelse ((StateName == abbreviated) andalso (DataTag == udp))) andalso ((Type == ?HANDSHAKE)
- orelse
- (Type == ?ALERT)) ->
+ orelse ((StateName == abbreviated) andalso (DataTag == udp)))
+ andalso ((Type == ?HANDSHAKE) orelse (Type == ?ALERT)) ->
ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]),
+ Acc = [#ssl_tls{type = Type, version = {MajVer, MinVer},
+ epoch = Epoch, sequence_number = SequenceNumber,
+ fragment = Data} | Acc0],
case is_acceptable_version({MajVer, MinVer}, Versions) of
true ->
- get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc], SslOpts);
+ get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
- end;
-get_dtls_records_aux({_, _, Version, _} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+get_dtls_records_aux({_, _, Version, Versions} = Vinfo,
+ <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Epoch), ?UINT48(SequenceNumber),
?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
- Acc, #{log_level := LogLevel} = SslOpts) when (Type == ?APPLICATION_DATA) orelse
- (Type == ?HANDSHAKE) orelse
- (Type == ?ALERT) orelse
- (Type == ?CHANGE_CIPHER_SPEC) ->
+ Acc0, LogLevel)
+ when (Type == ?APPLICATION_DATA) orelse
+ (Type == ?HANDSHAKE) orelse
+ (Type == ?ALERT) orelse
+ (Type == ?CHANGE_CIPHER_SPEC) ->
ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]),
- case {MajVer, MinVer} of
- Version ->
- get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc], SslOpts);
- _ ->
+ Acc = [#ssl_tls{type = Type, version = {MajVer,MinVer},
+ epoch = Epoch, sequence_number = SequenceNumber,
+ fragment = Data} | Acc0],
+ if {MajVer, MinVer} =:= Version ->
+ get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
+ Type == ?HANDSHAKE ->
+ case is_acceptable_version({MajVer, MinVer}, Versions) of
+ true ->
+ get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+ true ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end;
get_dtls_records_aux(_, <<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer),
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index c234bf81be..9118fb59f6 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -232,7 +232,7 @@ gen_accept(Driver, Listen) ->
%% smaller than MaxPending
accept_loop(DLK, undefined, MaxPending, Pending) when map_size(Pending) < MaxPending ->
accept_loop(DLK, spawn_accept(DLK), MaxPending, Pending);
-accept_loop(DLK, HandshakePid, MaxPending, Pending) ->
+accept_loop({_, _, NetKernelPid} = DLK, HandshakePid, MaxPending, Pending) ->
receive
{continue, HandshakePid} when is_pid(HandshakePid) ->
accept_loop(DLK, undefined, MaxPending, Pending#{HandshakePid => true});
@@ -244,6 +244,9 @@ accept_loop(DLK, HandshakePid, MaxPending, Pending) ->
%% HandshakePid crashed before turning into Pending, which means
%% error happened in accept. Need to restart the listener.
exit(Reason);
+ {'EXIT', NetKernelPid, Reason} ->
+ %% Since we're trapping exits, need to manually propagate this signal
+ exit(Reason);
Unexpected ->
?LOG_WARNING("TLS distribution: unexpected message: ~p~n" ,[Unexpected]),
accept_loop(DLK, HandshakePid, MaxPending, Pending)
@@ -440,7 +443,7 @@ gen_accept_connection(
Driver, AcceptPid, DistCtrl,
MyNode, Allowed, SetupTime, Kernel)
end,
- [link, {priority, max}])).
+ dist_util:net_ticker_spawn_options())).
do_accept(
_Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) ->
@@ -539,7 +542,7 @@ gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
Kernel = self(),
monitor_pid(
spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime),
- [link, {priority, max}])).
+ dist_util:net_ticker_spawn_options())).
-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index a43a84f26d..b5cb6b5d91 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -2,7 +2,7 @@
[{description, "Erlang/OTP SSL application"},
{vsn, "%VSN%"},
{modules, [
- %% TLS/SSL
+ %% TLS/SSL
tls_connection,
tls_connection_1_3,
tls_handshake,
@@ -34,7 +34,7 @@
dtls_server_sup,
dtls_server_session_cache_sup,
%% API
- ssl, %% Main API
+ ssl, %% Main API
ssl_session_cache_api,
%% Both TLS/SSL and DTLS
tls_dtls_connection,
@@ -46,7 +46,7 @@
ssl_cipher_format,
ssl_srp_primes,
ssl_alert,
- ssl_listen_tracker_sup, %% may be used by DTLS over SCTP
+ ssl_listen_tracker_sup, %% may be used by DTLS over SCTP
tls_bloom_filter,
tls_client_ticket_store,
%% Erlang Distribution over SSL/TLS
@@ -55,8 +55,8 @@
ssl_dist_sup,
ssl_dist_connection_sup,
ssl_dist_admin_sup,
- tls_dist_sup,
- tls_dist_server_sup,
+ tls_dist_sup,
+ tls_dist_server_sup,
%% SSL/TLS session and cert handling
ssl_session,
ssl_client_session_cache_db,
@@ -70,7 +70,7 @@
ssl_certificate,
%% CRL handling
ssl_crl,
- ssl_crl_cache,
+ ssl_crl_cache,
ssl_crl_cache_api,
ssl_crl_hash_dir,
%% Logging
@@ -85,6 +85,6 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-3.12","public_key-1.11.3","kernel-8.0",
+ {runtime_dependencies, ["stdlib-4.1","public_key-1.11.3","kernel-8.4",
"erts-10.0","crypto-5.0", "inets-5.10.7",
"runtime_tools-1.15.1"]}]}.
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 2b88568927..ad5028655d 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -26,6 +26,7 @@
-module(ssl).
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
@@ -310,7 +311,8 @@
{certfile, cert_pem()} |
{key, key()} |
{keyfile, key_pem()} |
- {password, key_password()} |
+ {password, key_pem_password()} |
+ {certs_keys, certs_keys()} |
{ciphers, cipher_suites()} |
{eccs, [named_curve()]} |
{signature_algs, signature_algs()} |
@@ -334,7 +336,8 @@
{ssl_imp, ssl_imp()} |
{session_tickets, session_tickets()} |
{key_update_at, key_update_at()} |
- {middlebox_comp_mode, middlebox_comp_mode()}.
+ {receiver_spawn_opts, spawn_opts()} |
+ {sender_spawn_opts, spawn_opts()}.
-type protocol() :: tls | dtls.
-type handshake_completion() :: hello | full.
@@ -347,8 +350,14 @@
key_id := crypto:key_id(),
password => crypto:password()}. % exported
-type key_pem() :: file:filename().
--type key_password() :: string() | fun(() -> string()).
--type cipher_suites() :: ciphers().
+-type key_pem_password() :: iodata() | fun(() -> iodata()).
+-type certs_keys() :: [cert_key_conf()].
+-type cert_key_conf() :: #{cert => cert(),
+ key => key(),
+ certfile => cert_pem(),
+ keyfile => key_pem(),
+ password => key_pem_password()}.
+-type cipher_suites() :: ciphers().
-type ciphers() :: [erl_cipher_suite()] |
string(). % (according to old API) exported
-type cipher_filters() :: list({key_exchange | cipher | mac | prf,
@@ -391,6 +400,7 @@
-type middlebox_comp_mode() :: boolean().
-type client_early_data() :: binary().
-type server_early_data() :: disabled | enabled.
+-type spawn_opts() :: [erlang:spawn_opt_option()].
%% -------------------------------------------------------------------------------------------------------
@@ -407,7 +417,8 @@
{max_fragment_length, max_fragment_length()} |
{customize_hostname_check, customize_hostname_check()} |
{fallback, fallback()} |
- {certificate_authorities, certificate_authorities()} |
+ {middlebox_comp_mode, middlebox_comp_mode()} |
+ {certificate_authorities, client_certificate_authorities()} |
{session_tickets, client_session_tickets()} |
{use_ticket, use_ticket()} |
{early_data, client_early_data()}.
@@ -418,8 +429,8 @@
-type client_verify_type() :: verify_type().
-type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}.
-type client_reuse_sessions() :: boolean() | save.
--type certificate_authorities() :: boolean().
--type client_cacerts() :: [public_key:der_encoded()].
+-type client_certificate_authorities() :: boolean().
+-type client_cacerts() :: [public_key:der_encoded()] | [public_key:combined_cert()].
-type client_cafile() :: file:filename().
-type app_level_protocol() :: binary().
-type client_alpn() :: [app_level_protocol()].
@@ -447,6 +458,7 @@
{dhfile, dh_file()} |
{verify, server_verify_type()} |
{fail_if_no_peer_cert, fail_if_no_peer_cert()} |
+ {certificate_authorities, server_certificate_authorities()} |
{reuse_sessions, server_reuse_sessions()} |
{reuse_session, server_reuse_session()} |
{alpn_preferred_protocols, server_alpn()} |
@@ -462,7 +474,7 @@
{cookie, cookie()} |
{early_data, server_early_data()}.
--type server_cacerts() :: [public_key:der_encoded()].
+-type server_cacerts() :: [public_key:der_encoded()] | [public_key:combined_cert()].
-type server_cafile() :: file:filename().
-type server_alpn() :: [app_level_protocol()].
-type server_next_protocol() :: [app_level_protocol()].
@@ -479,6 +491,7 @@
-type honor_ecc_order() :: boolean().
-type client_renegotiation() :: boolean().
-type cookie() :: boolean().
+-type server_certificate_authorities() :: boolean().
%% -------------------------------------------------------------------------------------------------------
-type prf_random() :: client_random | server_random. % exported
-type protocol_extensions() :: #{renegotiation_info => binary(),
@@ -1395,39 +1408,15 @@ clear_pem_cache() ->
ssl_pem_cache:clear().
%%---------------------------------------------------------------
--spec format_error({error, Reason}) -> string() when
+-spec format_error(Reason | {error, Reason}) -> string() when
Reason :: any().
%%
%% Description: Creates error string.
%%--------------------------------------------------------------------
format_error({error, Reason}) ->
- format_error(Reason);
-format_error(Reason) when is_list(Reason) ->
- Reason;
-format_error(closed) ->
- "TLS connection is closed";
-format_error({tls_alert, {_, Description}}) ->
- Description;
-format_error({options,{FileType, File, Reason}}) when FileType == cacertfile;
- FileType == certfile;
- FileType == keyfile;
- FileType == dhfile ->
- Error = file_error_format(Reason),
- file_desc(FileType) ++ File ++ ": " ++ Error;
-format_error({options, {socket_options, Option, Error}}) ->
- lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, format_error(Error)]));
-format_error({options, {socket_options, Option}}) ->
- lists:flatten(io_lib:format("Invalid socket option: ~p", [Option]));
-format_error({options, Options}) ->
- lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options]));
-
-format_error(Error) ->
- case inet:format_error(Error) of
- "unknown POSIX" ++ _ ->
- unexpected_format(Error);
- Other ->
- Other
- end.
+ do_format_error(Reason);
+format_error(Reason) ->
+ do_format_error(Reason).
tls_version({3, _} = Version) ->
Version;
@@ -1673,11 +1662,11 @@ handle_option(fallback = Option, Value0, OptionsMap, #{role := Role}) ->
assert_role(client_only, Role, Option, Value0),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
-handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := Role}) ->
- Value = default_option_role(client, false, Role),
- OptionsMap#{Option => Value};
-handle_option(certificate_authorities = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) ->
- assert_role(client_only, Role, Option, Value0),
+handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := server}) ->
+ OptionsMap#{Option => true};
+handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := client}) ->
+ OptionsMap#{Option => false};
+handle_option(certificate_authorities = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
@@ -1713,6 +1702,13 @@ handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsM
assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
+handle_option(log_level = Option, unbound, OptionsMap, _Env) ->
+ DefaultLevel = case logger:get_module_level(?MODULE) of
+ [] -> notice;
+ [{ssl,Level}] -> Level
+ end,
+ Value = validate_option(Option, DefaultLevel),
+ OptionsMap#{Option => Value};
handle_option(next_protocols_advertised = Option, unbound, OptionsMap,
#{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
@@ -1746,6 +1742,11 @@ handle_option(password = Option, unbound, OptionsMap, #{rules := Rules}) ->
handle_option(password = Option, Value0, OptionsMap, _Env) ->
Value = validate_option(Option, Value0),
OptionsMap#{password => Value};
+handle_option(certs_keys, unbound, OptionsMap, _Env) ->
+ OptionsMap;
+handle_option(certs_keys = Option, Value0, OptionsMap, _Env) ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{certs_keys => Value};
handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
OptionsMap#{Option => Value};
@@ -2086,10 +2087,10 @@ validate_option(cacertfile, undefined, _) ->
<<>>;
validate_option(cacertfile, Value, _)
when is_binary(Value) ->
- Value;
+ unambiguous_path(Value);
validate_option(cacertfile, Value, _)
when is_list(Value), Value =/= ""->
- binary_filename(Value);
+ binary_filename(unambiguous_path(Value));
validate_option(cacerts, Value, _)
when Value == undefined;
is_list(Value) ->
@@ -2293,11 +2294,13 @@ validate_option(partial_chain, Value, _)
when is_function(Value) ->
Value;
validate_option(password, Value, _)
- when is_list(Value) ->
+ when is_list(Value); is_binary(Value) ->
Value;
validate_option(password, Value, _)
when is_function(Value, 0) ->
Value;
+validate_option(certs_keys, Value, _) when is_list(Value) ->
+ Value;
validate_option(protocol, Value = tls, _) ->
Value;
validate_option(protocol, Value = dtls, _) ->
@@ -2307,6 +2310,9 @@ validate_option(psk_identity, undefined, _) ->
validate_option(psk_identity, Identity, _)
when is_list(Identity), Identity =/= "", length(Identity) =< 65535 ->
binary_filename(Identity);
+validate_option(receiver_spawn_opts, Value, _)
+ when is_list(Value) ->
+ Value;
validate_option(renegotiate_at, Value, _) when is_integer(Value) ->
erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
validate_option(reuse_session, undefined, _) ->
@@ -2329,6 +2335,9 @@ validate_option(reuse_sessions, save = Value, _) ->
validate_option(secure_renegotiate, Value, _)
when is_boolean(Value) ->
Value;
+validate_option(sender_spawn_opts, Value, _)
+ when is_list(Value) ->
+ Value;
validate_option(server_name_indication, Value, _)
when is_list(Value) ->
%% RFC 6066, Section 3: Currently, the only server names supported are
@@ -2465,7 +2474,7 @@ handle_hashsigns_option(_, _Version) ->
undefined.
handle_signature_algorithms_option(Value, Version) when is_list(Value)
- andalso Version >= {3, 4} ->
+ andalso Version >= {3, 3} ->
case tls_v1:signature_schemes(Version, Value) of
[] ->
throw({error, {options,
@@ -2555,7 +2564,7 @@ dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
dtls_validate_versions([Ver| _], Versions) ->
throw({error, {options, {Ver, {versions, Versions}}}}).
-%% The option cacerts overrides cacertsfile
+%% The option cacerts overrides cacertfile
ca_cert_default(_,_, [_|_]) ->
undefined;
ca_cert_default(verify_none, _, _) ->
@@ -2679,6 +2688,47 @@ handle_supported_groups_option(Value, Version) when is_list(Value) ->
end.
+-spec do_format_error( string()
+ | closed
+ | {tls_alert, {_, Description :: string()}}
+ | {options, Options :: term()}
+ | {options, {socket_options, Option :: term()}}
+ | {options, {socket_options, Option :: term(), Error}}
+ | {options, {FileType, File :: string(), Error}}
+ | InetError
+ | OtherReason) -> string()
+ when
+ FileType :: cacertfile | certfile | keyfile | dhfile,
+ OtherReason :: term(),
+ InetError :: inet:posix() | system_limit.
+
+do_format_error(Reason) when is_list(Reason) ->
+ Reason;
+do_format_error(closed) ->
+ "TLS connection is closed";
+do_format_error({tls_alert, {_, Description}}) ->
+ Description;
+do_format_error({options,{FileType, File, Reason}}) when FileType == cacertfile;
+ FileType == certfile;
+ FileType == keyfile;
+ FileType == dhfile ->
+ Error = file_error_format(Reason),
+ file_desc(FileType) ++ File ++ ": " ++ Error;
+do_format_error ({options, {socket_options, Option, Error}}) ->
+ lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, do_format_error(Error)]));
+do_format_error({options, {socket_options, Option}}) ->
+ lists:flatten(io_lib:format("Invalid socket option: ~p", [Option]));
+do_format_error({options, Options}) ->
+ lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options]));
+
+do_format_error(Error) ->
+ case inet:format_error(Error) of
+ "unknown POSIX" ++ _ ->
+ unexpected_format(Error);
+ Other ->
+ Other
+ end.
+
unexpected_format(Error) ->
lists:flatten(io_lib:format("Unexpected error: ~p", [Error])).
@@ -2849,8 +2899,25 @@ add_filter(Filter, Filters) ->
maybe_client_warn_no_verify(#{verify := verify_none,
warn_verify_none := true,
log_level := LogLevel}, client) ->
- ssl_logger:log(warning, LogLevel, #{description => "Authenticity is not established by certificate path validation",
- reason => "Option {verify, verify_peer} and cacertfile/cacerts is missing"}, #{});
+ ssl_logger:log(warning, LogLevel,
+ #{description => "Server authenticity is not verified since certificate path validation is not enabled",
+ reason => "The option {verify, verify_peer} and one of the options 'cacertfile' or "
+ "'cacerts' are required to enable this."}, ?LOCATION);
maybe_client_warn_no_verify(_,_) ->
%% Warning not needed. Note client certificate validation is optional in TLS
ok.
+
+unambiguous_path(Value) ->
+ AbsName = filename:absname(Value),
+ case file:read_link(AbsName) of
+ {ok, PathWithNoLink} ->
+ case filename:pathtype(PathWithNoLink) of
+ relative ->
+ Dirname = filename:dirname(AbsName),
+ filename:join([Dirname, PathWithNoLink]);
+ _ ->
+ PathWithNoLink
+ end;
+ _ ->
+ AbsName
+ end.
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index eef6aa875a..2e2b43f564 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -80,7 +80,9 @@
public_key_type/1,
foldl_db/3,
find_cross_sign_root_paths/4,
- handle_cert_auths/4
+ handle_cert_auths/4,
+ available_cert_key_pairs/1,
+ available_cert_key_pairs/2
]).
%%====================================================================
@@ -307,25 +309,60 @@ find_cross_sign_root_paths([_ | Rest] = Path, CertDbHandle, CertDbRef, Invalidat
end.
handle_cert_auths(Chain, [], _, _) ->
- %% If we have no authorities extension to check we just accept
- %% first choice
+ %% If we have no authorities extension (or corresponding
+ %% 'certificate_authorities' in the certificate request message in
+ %% TLS-1.2 is empty) to check we just accept first choice.
{ok, Chain};
handle_cert_auths([Cert], CertAuths, CertDbHandle, CertDbRef) ->
- {ok, {_, [Cert | _] = EChain}, {_, [_ | DCerts]}} = certificate_chain(Cert, CertDbHandle, CertDbRef, [], both),
- case cert_auth_member(cert_subjects(DCerts), CertAuths) of
- true ->
- {ok, EChain};
- false ->
- {error, EChain, not_in_auth_domain}
+ case certificate_chain(Cert, CertDbHandle, CertDbRef, [], both) of
+ {ok, {_, [Cert | _] = EChain}, {_, [_ | DCerts]}} ->
+ case cert_auth_member(cert_issuers(DCerts), CertAuths) of
+ true ->
+ {ok, EChain};
+ false ->
+ {error, EChain, not_in_auth_domain}
+ end;
+ _ ->
+ {ok, [Cert]}
end;
handle_cert_auths([_ | Certs] = EChain, CertAuths, _, _) ->
- case cert_auth_member(cert_subjects(Certs), CertAuths) of
+ case cert_auth_member(cert_issuers(Certs), CertAuths) of
true ->
{ok, EChain};
false ->
{error, EChain, not_in_auth_domain}
end.
+available_cert_key_pairs(CertKeyGroups) ->
+ %% To be able to find possible TLS session pre TLS-1.3
+ %% that may be reused. At this point the version is
+ %% not negotiated.
+ RevAlgos = [dsa, rsa, rsa_pss_pss, ecdsa],
+ cert_key_group_to_list(RevAlgos, CertKeyGroups, []).
+
+%% Create the prioritized list of cert key pairs that
+%% are availble for use in the negotiated version
+available_cert_key_pairs(CertKeyGroups, {3, 4}) ->
+ RevAlgos = [rsa, rsa_pss_pss, ecdsa, eddsa],
+ cert_key_group_to_list(RevAlgos, CertKeyGroups, []);
+available_cert_key_pairs(CertKeyGroups, {3, 3}) ->
+ RevAlgos = [dsa, rsa, rsa_pss_pss, ecdsa],
+ cert_key_group_to_list(RevAlgos, CertKeyGroups, []);
+available_cert_key_pairs(CertKeyGroups, {3, N}) when N < 3->
+ RevAlgos = [dsa, rsa, ecdsa],
+ cert_key_group_to_list(RevAlgos, CertKeyGroups, []).
+
+cert_key_group_to_list([], _, Acc) ->
+ final_group_list(Acc);
+cert_key_group_to_list([Algo| Rest], CertKeyGroups, Acc) ->
+ CertKeyPairs = maps:get(Algo, CertKeyGroups, []),
+ cert_key_group_to_list(Rest, CertKeyGroups, CertKeyPairs ++ Acc).
+
+final_group_list([]) ->
+ [#{certs => [[]], private_key => #{}}];
+final_group_list(List) ->
+ List.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -411,7 +448,7 @@ find_alternative_root([Cert | _], CertDbHandle, CertDbRef, InvalidatedList) ->
end.
find_issuer(#cert{der=DerCert, otp=OtpCert}, CertDbHandle, CertsDbRef, ListDb, InvalidatedList) ->
- IsIssuerFun =
+ IsIssuerFun =
fun({_Key, #cert{otp=ErlCertCandidate}}, Acc) ->
case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of
true ->
@@ -431,12 +468,15 @@ find_issuer(#cert{der=DerCert, otp=OtpCert}, CertDbHandle, CertsDbRef, ListDb, I
end,
Result = case is_reference(CertsDbRef) of
- true ->
- do_find_issuer(IsIssuerFun, CertDbHandle, ListDb);
- false ->
+ true when ListDb == [] ->
+ CertEntryList = ssl_pkix_db:select_certentries_by_ref(CertsDbRef, CertDbHandle),
+ do_find_issuer(IsIssuerFun, CertDbHandle, CertEntryList);
+ false when ListDb == [] ->
{extracted, CertsData} = CertsDbRef,
- DB = [Entry || {decoded, Entry} <- CertsData],
- do_find_issuer(IsIssuerFun, CertDbHandle, DB)
+ CertEntryList = [Entry || {decoded, Entry} <- CertsData],
+ do_find_issuer(IsIssuerFun, CertDbHandle, CertEntryList);
+ _ ->
+ do_find_issuer(IsIssuerFun, CertDbHandle, ListDb)
end,
case Result of
issuer_not_found ->
@@ -445,6 +485,7 @@ find_issuer(#cert{der=DerCert, otp=OtpCert}, CertDbHandle, CertsDbRef, ListDb, I
Result
end.
+
do_find_issuer(IssuerFun, CertDbHandle, CertDb) ->
try
foldl_db(IssuerFun, CertDbHandle, CertDb)
@@ -667,18 +708,18 @@ maybe_shorten_path(Path, PartialChainHandler, Default) ->
DerCerts = [Der || #cert{der=Der} <- Path],
try PartialChainHandler(DerCerts) of
{trusted_ca, Root} ->
- new_trusteded_path(Root, Path, Default);
+ new_trusted_path(Root, Path, Default);
unknown_ca ->
Default
catch _:_ ->
Default
end.
-new_trusteded_path(DerCert, [#cert{der=DerCert}=Cert | Chain], _) ->
- {Cert, Chain};
-new_trusteded_path(DerCert, [_ | Rest], Default) ->
- new_trusteded_path(DerCert, Rest, Default);
-new_trusteded_path(_, [], Default) ->
+new_trusted_path(DerCert, [#cert{der=DerCert}=Cert | Path], _) ->
+ {Cert, Path};
+new_trusted_path(DerCert, [_ | Rest], Default) ->
+ new_trusted_path(DerCert, Rest, Default);
+new_trusted_path(_, [], Default) ->
%% User did not pick a cert present
%% in the cert chain so ignore
Default.
@@ -758,21 +799,29 @@ subject(Cert) ->
{_Serial,Subject} = public_key:pkix_subject_id(Cert),
Subject.
-cert_subjects([], Acc) ->
- Acc;
-cert_subjects([Cert | Rest], Acc) ->
- cert_subjects(Rest, [subject(Cert) | Acc]).
-
-cert_subjects(OTPCerts) ->
- cert_subjects(OTPCerts, []).
+issuer(Cert) ->
+ case public_key:pkix_is_self_signed(Cert) of
+ true ->
+ subject(Cert);
+ false ->
+ case is_binary(Cert) of
+ true ->
+ #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
+ public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.issuer);
+ false ->
+ #'OTPCertificate'{tbsCertificate = TBSCert} = Cert,
+ public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.issuer)
+ end
+ end.
-decode_cert_auths(<<>>, Acc) ->
+cert_issuers([], Acc) ->
Acc;
-decode_cert_auths(<<?UINT16(Len), Auth:Len/binary, Rest/binary>>, Acc) ->
- NormAut = public_key:pkix_normalize_name(Auth),
- decode_cert_auths(Rest, [NormAut | Acc]).
+cert_issuers([Cert | Rest], Acc) ->
+ cert_issuers(Rest, [issuer(Cert) | Acc]).
+
+cert_issuers(OTPCerts) ->
+ cert_issuers(OTPCerts, []).
-cert_auth_member(ChainSubjects, EncCertAuths) ->
- CertAuths = decode_cert_auths(EncCertAuths, []),
+cert_auth_member(ChainSubjects, CertAuths) ->
CommonAuthorities = sets:intersection(sets:from_list(ChainSubjects), sets:from_list(CertAuths)),
not sets:is_empty(CommonAuthorities).
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index f567bd6c53..87dd624306 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -183,10 +183,10 @@ cipher(?AES_CBC, CipherState, Mac, Fragment, Version) ->
end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version).
aead_encrypt(Type, Key, Nonce, Fragment, AdditionalData, TagLen) ->
- crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, Fragment, AdditionalData, TagLen, true).
+ crypto:crypto_one_time_aead(aead_type(Type,byte_size(Key)), Key, Nonce, Fragment, AdditionalData, TagLen, true).
aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AdditionalData) ->
- crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, CipherText, AdditionalData, CipherTag, false).
+ crypto:crypto_one_time_aead(aead_type(Type,byte_size(Key)), Key, Nonce, CipherText, AdditionalData, CipherTag, false).
aead_type(?AES_GCM, 16) ->
aes_128_gcm;
@@ -260,12 +260,13 @@ decipher(?RC4, HashSz, CipherState = #cipher_state{state = State}, Fragment, _,
#generic_stream_cipher{content = Content, mac = Mac} = GSC,
{Content, Mac, CipherState}
catch
- _:_ ->
+ _:Reason:ST ->
%% This is a DECRYPTION_FAILED but
%% "differentiating between bad_record_mac and decryption_failed
%% alerts may permit certain attacks against CBC mode as used in
%% TLS [CBCATT]. It is preferable to uniformly use the
%% bad_record_mac alert to hide the specific type of the error."
+ ?SSL_LOG(debug, decrypt_error, [{reason,Reason}, {stacktrace, ST}]),
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end;
@@ -305,12 +306,13 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
{<<16#F0, Content/binary>>, Mac, CipherState1}
end
catch
- _:_ ->
+ _:Reason:ST ->
%% This is a DECRYPTION_FAILED but
%% "differentiating between bad_record_mac and decryption_failed
%% alerts may permit certain attacks against CBC mode as used in
%% TLS [CBCATT]. It is preferable to uniformly use the
%% bad_record_mac alert to hide the specific type of the error."
+ ?SSL_LOG(debug, decrypt_error, [{reason,Reason}, {stacktrace, ST}]),
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
@@ -674,8 +676,8 @@ scheme_to_components(eddsa_ed448) -> {none, eddsa, ed448};
scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined};
scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined};
scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined};
-scheme_to_components(rsa_pkcs1_sha1) -> {sha1, rsa_pkcs1, undefined};
-scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined};
+scheme_to_components(rsa_pkcs1_sha1) -> {sha, rsa_pkcs1, undefined};
+scheme_to_components(ecdsa_sha1) -> {sha, ecdsa, undefined};
%% Handling legacy signature algorithms
scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}.
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index 91bd03decf..46578c05a5 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -41,20 +41,162 @@
%% Internal application API
%%====================================================================
init(#{erl_dist := ErlDist,
- key := Key,
- keyfile := KeyFile,
- password := Password, %% Can be fun() or string()
dh := DH,
dhfile := DHFile} = SslOpts, Role) ->
init_manager_name(ErlDist),
+ #{pem_cache := PemCache} = Config = init_cacerts(SslOpts, Role),
+ DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role),
+
+ CertKeyAlts = init_certs_keys(SslOpts, Role, PemCache),
+
+ {ok, Config#{cert_key_alts => CertKeyAlts, dh_params => DHParams}}.
- {ok, #{pem_cache := PemCache} = Config, Certs}
- = init_certificates(SslOpts, Role),
+init_certs_keys(#{certs_keys := CertsKeys}, Role, PemCache) ->
+ Pairs = lists:map(fun(CertKey) -> cert_key_pair(CertKey, Role, PemCache) end, CertsKeys),
+ CertKeyGroups = group_pairs(Pairs),
+ prioritize_groups(CertKeyGroups);
+init_certs_keys(SslOpts, Role, PemCache) ->
+ KeyPair = init_cert_key_pair(SslOpts, Role, PemCache),
+ group_pairs([KeyPair]).
+
+init_cert_key_pair(#{key := Key,
+ keyfile := KeyFile,
+ password := Password} = Opts, Role, PemCache) ->
+ {ok, Certs} = init_certificates(Opts, PemCache, Role),
PrivateKey =
init_private_key(PemCache, Key, KeyFile, Password, Role),
- DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role),
- {ok, Config#{cert_key_pairs => [#{private_key => PrivateKey, certs => Certs}], dh_params => DHParams}}.
+ #{private_key => PrivateKey, certs => Certs}.
+
+cert_key_pair(CertKey, Role, PemCache) ->
+ CertKeyPairConf = cert_conf(key_conf(CertKey)),
+ init_cert_key_pair(CertKeyPairConf, Role, PemCache).
+
+
+group_pairs([#{certs := [[]]}]) ->
+ #{eddsa => [],
+ ecdsa => [],
+ rsa_pss_pss => [],
+ rsa => [],
+ dsa => []
+ };
+group_pairs(Pairs) ->
+ group_pairs(Pairs, #{eddsa => [],
+ ecdsa => [],
+ rsa_pss_pss => [],
+ rsa => [],
+ dsa => []
+ }).
+group_pairs([], Group) ->
+ Group;
+group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed25519'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) ->
+ group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]});
+group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed448'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) ->
+ group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]});
+group_pairs([#{private_key := #'ECPrivateKey'{}} = Pair | Rest], #{ecdsa := ECDSA} = Group) ->
+ group_pairs(Rest, Group#{ecdsa => [Pair | ECDSA]});
+group_pairs([#{private_key := {#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}} = Pair | Rest], #{rsa_pss_pss := RSAPSS} = Group) ->
+ group_pairs(Rest, Group#{rsa_pss_pss => [Pair | RSAPSS]});
+group_pairs([#{private_key := #'RSAPrivateKey'{}} = Pair | Rest], #{rsa := RSA} = Group) ->
+ group_pairs(Rest, Group#{rsa => [Pair | RSA]});
+group_pairs([#{private_key := #'DSAPrivateKey'{}} = Pair | Rest], #{dsa := DSA} = Group) ->
+ group_pairs(Rest, Group#{dsa => [Pair | DSA]});
+group_pairs([#{private_key := #{algorithm := dss, engine := _}} = Pair | Rest], Group) ->
+ Pairs = maps:get(dsa, Group),
+ group_pairs(Rest, Group#{dsa => [Pair | Pairs]});
+group_pairs([#{private_key := #{algorithm := Alg, engine := _}} = Pair | Rest], Group) ->
+ Pairs = maps:get(Alg, Group),
+ group_pairs(Rest, Group#{Alg => [Pair | Pairs]}).
+
+prioritize_groups(#{eddsa := EDDSA,
+ ecdsa := ECDSA,
+ rsa_pss_pss := RSAPSS,
+ rsa := RSA,
+ dsa := DSA} = CertKeyGroups) ->
+ CertKeyGroups#{eddsa => prio_eddsa(EDDSA),
+ ecdsa => prio_ecdsa(ECDSA),
+ rsa_pss_pss => prio_rsa_pss(RSAPSS),
+ rsa => prio_rsa(RSA),
+ dsa => prio_dsa(DSA)}.
+
+prio_eddsa(EDDSA) ->
+ %% Engine not supported yet
+ using_curve({namedCurve, ?'id-Ed25519'}, EDDSA, []) ++ using_curve({namedCurve, ?'id-Ed448'}, EDDSA, []).
+
+prio_ecdsa(ECDSA) ->
+ EnginePairs = [Pair || Pair = #{private_key := #{engine := _}} <- ECDSA],
+ Curves = tls_v1:ecc_curves(all),
+ EnginePairs ++ lists:foldr(fun(Curve, AccIn) ->
+ CurveOid = pubkey_cert_records:namedCurves(Curve),
+ Pairs = using_curve({namedCurve, CurveOid}, ECDSA -- EnginePairs, []),
+ Pairs ++ AccIn
+ end, [], Curves).
+using_curve(_, [], Acc) ->
+ lists:reverse(Acc);
+using_curve(Curve, [#{private_key := #'ECPrivateKey'{parameters = Curve}} = Pair | Rest], Acc) ->
+ using_curve(Curve, Rest, [Pair | Acc]);
+using_curve(Curve, [_ | Rest], Acc) ->
+ using_curve(Curve, Rest, Acc).
+
+prio_rsa_pss(RSAPSS) ->
+ Order = fun(#{privat_key := {#'RSAPrivateKey'{modulus = N}, Params1}},
+ #{private_key := {#'RSAPrivateKey'{modulus = N}, Params2}}) ->
+ prio_params_1(Params1, Params2);
+ (#{private_key := {#'RSAPrivateKey'{modulus = N}, _}},
+ #{private_key := {#'RSAPrivateKey'{modulus = M}, _}}) when M > N ->
+ true;
+ (#{private_key := #{engine := _}}, _) ->
+ true;
+ (_,_) ->
+ false
+ end,
+ lists:sort(Order, RSAPSS).
+
+prio_params_1(#'RSASSA-PSS-params'{hashAlgorithm = #'HashAlgorithm'{algorithm = Oid1}},
+ #'RSASSA-PSS-params'{hashAlgorithm = #'HashAlgorithm'{algorithm = Oid2}}) ->
+ public_key:pkix_hash_type(Oid1) > public_key:pkix_hash_type(Oid2).
+
+prio_rsa(RSA) ->
+ Order = fun(#{key := #'RSAPrivateKey'{modulus = N}},
+ #{key := #'RSAPrivateKey'{modulus = M}}) when M > N ->
+ true;
+ (#{private_key := #{engine := _}}, _) ->
+ true;
+ (_,_) ->
+ false
+ end,
+ lists:sort(Order, RSA).
+
+prio_dsa(DSA) ->
+ Order = fun(#{key := #'DSAPrivateKey'{q = N}},
+ #{key := #'DSAPrivateKey'{q = M}}) when M > N ->
+ true;
+ (#{private_key := #{engine := _}}, _) ->
+ true;
+ (_,_) ->
+ false
+ end,
+ lists:sort(Order, DSA).
+
+key_conf(#{key := _} = Conf) ->
+ Conf#{certfile => <<>>,
+ keyfile => <<>>,
+ password => undefined};
+key_conf(#{keyfile := _} = Conf) ->
+ case maps:get(password, Conf, undefined) of
+ undefined ->
+ Conf#{key => undefined,
+ password => undefined};
+ _ ->
+ Conf#{key => undefined}
+ end.
+
+cert_conf(#{cert := Bin} = Conf) when is_binary(Bin)->
+ Conf#{cert => [Bin]};
+cert_conf(#{cert := _} = Conf) ->
+ Conf#{certfile => <<>>};
+cert_conf(#{certfile := _} = Conf) ->
+ Conf#{cert => undefined}.
pre_1_3_session_opts(Role) ->
{Cb, InitArgs} = session_cb_opts(Role),
@@ -119,12 +261,10 @@ init_manager_name(true) ->
put(ssl_manager, ssl_manager:name(dist)),
put(ssl_pem_cache, ssl_pem_cache:name(dist)).
-init_certificates(#{cacerts := CaCerts,
- cacertfile := CACertFile,
- certfile := CertFile,
- cert := OwnCerts,
- crl_cache := CRLCache
- }, Role) ->
+init_cacerts(#{cacerts := CaCerts,
+ cacertfile := CACertFile,
+ crl_cache := CRLCache
+ }, Role) ->
{ok, Config} =
try
Certs = case CaCerts of
@@ -138,31 +278,36 @@ init_certificates(#{cacerts := CaCerts,
_:Reason ->
file_error(CACertFile, {cacertfile, Reason})
end,
- init_certificates(OwnCerts, Config, CertFile, Role).
+ Config.
-init_certificates(undefined, Config, <<>>, _) ->
- {ok, Config, [[]]};
+init_certificates(#{certfile := CertFile,
+ cert := OwnCerts}, PemCache, Role) ->
+ init_certificates(OwnCerts, PemCache, CertFile, Role).
-init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) ->
+init_certificates(undefined, _, <<>>, _) ->
+ {ok, [[]]};
+init_certificates(undefined, PemCache, CertFile, client) ->
try
%% OwnCert | [OwnCert | Chain]
OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, Config, OwnCerts}
+ {ok, OwnCerts}
catch _Error:_Reason ->
- {ok, Config, [[]]}
+ {ok, [[]]}
end;
-
-init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) ->
+init_certificates(undefined, PemCache, CertFile, server) ->
try
%% OwnCert | [OwnCert | Chain]
OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, Config, OwnCerts}
+ {ok, OwnCerts}
catch
_:Reason ->
file_error(CertFile, {certfile, Reason})
end;
-init_certificates(OwnCerts, Config, _, _) ->
- {ok, Config, OwnCerts}.
+init_certificates(OwnCerts, _, _, _) when is_binary(OwnCerts)->
+ {ok, [OwnCerts]};
+init_certificates(OwnCerts, _, _, _) ->
+ {ok, OwnCerts}.
+
init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa;
Alg == rsa;
Alg == dss ->
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 86ee57d7fa..c8295f339f 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -55,7 +55,7 @@
-record(handshake_env, {
- client_hello_version :: ssl_record:ssl_version() | 'undefined',
+ client_hello_version :: ssl_record:ssl_version() | 'undefined', %% Legacy client hello
unprocessed_handshake_events = 0 :: integer(),
tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
| 'undefined',
@@ -67,7 +67,9 @@
early_data_accepted = false :: boolean(), %% TLS 1.3
allow_renegotiate = true ::boolean(),
%% Ext handling
- hello, %%:: #client_hello{} | #server_hello{}
+ %% continue_status reflects handling of the option handshake that is either full or
+ %% hello (will pause at hello message to allow user to act on hello extensions)
+ continue_status, %% full | pause | {pause, ClientVersionsExt} | continue
sni_hostname = undefined,
max_frag_enum :: undefined | {max_frag_enum, integer()},
expecting_next_protocol_negotiation = false ::boolean(),
@@ -97,9 +99,12 @@
socket_tls_closed = false ::boolean(),
negotiated_version :: ssl_record:ssl_version() | 'undefined',
erl_dist_handle = undefined :: erlang:dist_handle() | 'undefined',
- cert_key_pairs = undefined :: [#{private_key => public_key:private_key(),
- certs => [public_key:der_encoded()]}]
- | secret_printout() | 'undefined'
+ cert_key_alts = undefined :: #{eddsa => list(),
+ ecdsa => list(),
+ rsa_pss_pss => list(),
+ rsa => list(),
+ dsa => list()
+ } | secret_printout() | 'undefined'
}).
-record(state, {
@@ -118,7 +123,7 @@
%% need to worry about packet loss in TLS. In DTLS we
%% need to track DTLS handshake seqnr
flight_buffer = [] :: list() | map(),
- client_certificate_requested = false :: boolean(),
+ client_certificate_status = not_requested :: not_requested | requested | empty | needs_verifying | verified,
protocol_specific = #{} :: map(),
session :: #session{} | secret_printout(),
key_share,
@@ -150,8 +155,8 @@
%% session_cache_cb - not implemented
%% crl_db - not implemented
%% client_hello_version - Bleichenbacher mitigation in TLS 1.2
-%% client_certificate_requested - Built into TLS 1.3 state machine
-%% key_algorithm - not used
+%% client_certificate_status - only uses non_requested| requested
+%% key_algorithm - only uses not_requested and requested
%% diffie_hellman_params - used in TLS 1.2 ECDH key exchange
%% diffie_hellman_keys - used in TLS 1.2 ECDH key exchange
%% psk_identity - not used
diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl
index 095e3e8b44..c12d829470 100644
--- a/lib/ssl/src/ssl_crl_cache.erl
+++ b/lib/ssl/src/ssl_crl_cache.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@ fresh_crl(#'DistributionPoint'{distributionPoint = {fullName, Names}}, CRL) ->
case get_crls(Names, undefined) of
not_available ->
CRL;
- [NewCRL] ->
+ NewCRL ->
NewCRL
end.
@@ -175,7 +175,7 @@ cache_lookup(URL, {{Cache, _}, _}) ->
case ssl_pkix_db:lookup(string:trim(Path, leading, "/"), Cache) of
undefined ->
[];
- CRLs ->
+ [CRLs] ->
CRLs
end.
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
index caa6c6742d..36768ab6c7 100644
--- a/lib/ssl/src/ssl_gen_statem.erl
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -25,8 +25,6 @@
-module(ssl_gen_statem).
--include_lib("kernel/include/logger.hrl").
-
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_connection.hrl").
@@ -102,24 +100,28 @@
%%% Initial Erlang process setup
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+-spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), tuple(), pid(), tuple()) ->
{ok, pid()} | ignore | {error, reason()}.
%%
%% Description: Creates a process which calls Module:init/1 to
%% choose appropriat gen_statem and initialize.
%%--------------------------------------------------------------------
-start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}.
+start_link(Role, Sender, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) ->
+ Opts = [link | proplists:delete(link, ReceiverOpts)],
+ Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]], Opts),
+ {ok, Pid}.
%%--------------------------------------------------------------------
--spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+-spec start_link(atom(), ssl:host(), inet:port_number(), port(), tuple(), pid(), tuple()) ->
{ok, pid()} | ignore | {error, reason()}.
%%
%% Description: Creates a gen_statem process which calls Module:init/1 to
%% initialize.
%%--------------------------------------------------------------------
-start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
+start_link(Role, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) ->
+ Opts = [link | proplists:delete(link, ReceiverOpts)],
+ Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]], Opts),
+ {ok, Pid}.
%%--------------------------------------------------------------------
@@ -149,6 +151,7 @@ init([_Role, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs
-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
%%--------------------------------------------------------------------
ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
+ ssl_options = #{handshake := Handshake},
handshake_env = HsEnv,
connection_env = CEnv} = State0) ->
{ok, #{cert_db_ref := Ref,
@@ -156,12 +159,21 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
fileref_db_handle := FileRefHandle,
session_cache := CacheHandle,
crl_db_info := CRLDbHandle,
- cert_key_pairs := CertKeyPairs,
+ cert_key_alts := CertKeyAlts,
dh_params := DHParams}} =
ssl_config:init(Opts, Role),
TimeStamp = erlang:monotonic_time(),
Session = State0#state.session,
+ ContinueStatus = case Handshake of
+ hello ->
+ %% Will pause handshake after hello message to
+ %% enable user to react to hello extensions
+ pause;
+ full ->
+ Handshake
+ end,
+
State0#state{session = Session#session{time_stamp = TimeStamp},
static_env = InitStatEnv0#static_env{
file_ref_db = FileRefHandle,
@@ -170,8 +182,9 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
crl_db = CRLDbHandle,
session_cache = CacheHandle
},
- handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
- connection_env = CEnv#connection_env{cert_key_pairs = CertKeyPairs},
+ handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams,
+ continue_status = ContinueStatus},
+ connection_env = CEnv#connection_env{cert_key_alts = CertKeyAlts},
ssl_options = Opts}.
%%--------------------------------------------------------------------
@@ -709,8 +722,6 @@ handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName,
Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
handle_common_event(timeout, hibernate, _, _) ->
{keep_state_and_data, [hibernate]};
-handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, State) ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), StateName, State);
handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) ->
{stop_and_reply,
{shutdown, user_timeout},
@@ -840,10 +851,9 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName,
handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{
role = Role,
socket = Socket,
- error_tag = ErrorTag},
- ssl_options = #{log_level := Level}} = State) ->
- ssl_logger:log(info, Level, #{description => "Socket error",
- reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION),
+ error_tag = ErrorTag}
+ } = State) ->
+ ?SSL_LOG(info, "Socket error", [{error_tag, ErrorTag}, {description, Reason}]),
Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}),
handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
{stop, {shutdown,normal}, State};
@@ -870,11 +880,9 @@ handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_en
{stop,{shutdown, Reason}, State};
handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> %% PRE TLS-1.3
{next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}};
-handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag},
- ssl_options = #{log_level := Level}} = State) ->
- ssl_logger:log(notice, Level, #{description => "Unexpected INFO message",
- reason => [{message, Msg}, {socket, Socket},
- {error_tag, ErrorTag}]}, ?LOCATION),
+handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}} = State) ->
+ ?SSL_LOG(notice, "Unexpected INFO message",
+ [{message, Msg}, {socket, Socket}, {error_tag, ErrorTag}]),
{next_state, StateName, State}.
%%====================================================================
@@ -896,9 +904,25 @@ read_application_data(Data,
try read_application_dist_data(DHandle, Front, BufferSize, Rear) of
Buffer ->
{no_record, State#state{user_data_buffer = Buffer}}
- catch error:_ ->
- {stop,disconnect,
- State#state{user_data_buffer = {Front,BufferSize,Rear}}}
+ catch
+ error:notsup ->
+ %% Distribution controller has shut down
+ %% so we are no longer input handler and therefore
+ %% erlang:dist_ctrl_put_data/2 raises this exception
+ {stop, {shutdown, dist_closed},
+ %% This buffers known data, but we might have delivered
+ %% some of it to the VM, which makes buffering all
+ %% incorrect, as would be wasting all.
+ %% But we are stopping the server so
+ %% user_data_buffer is not important at all...
+ State#state{
+ user_data_buffer = {Front,BufferSize,Rear}}};
+ error:Reason:Stacktrace ->
+ %% Unforeseen exception in parsing application data
+ {stop,
+ {disconnect,{error,Reason,Stacktrace}},
+ State#state{
+ user_data_buffer = {Front,BufferSize,Rear}}}
end
end.
passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
@@ -1151,7 +1175,7 @@ terminate(Reason, connection, #state{static_env = #static_env{
handle_trusted_certs_db(State),
Alert = terminate_alert(Reason),
%% Send the termination ALERT if possible
- catch (ok = Connection:send_alert_in_connection(Alert, State)),
+ catch Connection:send_alert_in_connection(Alert, State),
Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates);
terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
protocol_cb = Connection,
@@ -1278,7 +1302,7 @@ handle_sni_hostname(Hostname,
fileref_db_handle := FileRefHandle,
session_cache := CacheHandle,
crl_db_info := CRLDbHandle,
- cert_key_pairs := CertKeyPairs,
+ cert_key_alts := CertKeyAlts,
dh_params := DHParams}} =
ssl_config:init(NewOptions, Role),
State0#state{
@@ -1289,7 +1313,7 @@ handle_sni_hostname(Hostname,
crl_db = CRLDbHandle,
session_cache = CacheHandle
},
- connection_env = CEnv#connection_env{cert_key_pairs = CertKeyPairs},
+ connection_env = CEnv#connection_env{cert_key_alts = CertKeyAlts},
ssl_options = NewOptions,
handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
diffie_hellman_params = DHParams}
@@ -1333,7 +1357,9 @@ filter_for_versions(['tlsv1.1'], OrigSSLOptions) ->
maps:without(Opts, OrigSSLOptions);
filter_for_versions(['tlsv1.1'| Rest], OrigSSLOptions) ->
Opts = ?'TLS-1_3_ONLY_OPTIONS' ++ ?'FROM_TLS-1_2_ONLY_OPTIONS',
- maybe_exclude_tlsv1(Rest, maps:without(Opts, OrigSSLOptions)).
+ maybe_exclude_tlsv1(Rest, maps:without(Opts, OrigSSLOptions));
+filter_for_versions(['tlsv1'], OrigSSLOptions) ->
+ OrigSSLOptions.
maybe_exclude_tlsv1(Versions, Options) ->
case lists:member('tlsv1', Versions) of
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index ee102077af..56a1ca81b9 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -358,7 +358,8 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
error:{_,{error, {asn1, Asn1Reason}}} ->
%% ASN-1 decode of certificate somehow failed
?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason});
- error:OtherReason ->
+ error:OtherReason:ST ->
+ ?SSL_LOG(info, internal_error, [{error, OtherReason}, {stacktrace, ST}]),
?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})
end.
%%--------------------------------------------------------------------
@@ -427,7 +428,8 @@ master_secret(Version, #session{master_secret = Mastersecret},
try master_secret(Version, Mastersecret, SecParams,
ConnectionStates, Role)
catch
- exit:_ ->
+ exit:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure)
end;
@@ -443,7 +445,8 @@ master_secret(Version, PremasterSecret, ConnectionStates, Role) ->
ClientRandom, ServerRandom),
SecParams, ConnectionStates, Role)
catch
- exit:_ ->
+ exit:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure)
end.
@@ -561,25 +564,27 @@ encode_handshake(#server_key_params{params_bin = Keys, hashsign = HashSign,
encode_handshake(#certificate_request{certificate_types = CertTypes,
hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos},
certificate_authorities = CertAuths},
- {Major, Minor}) when Major == 3, Minor >= 3 ->
+ {3,3}) ->
HashSigns = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> ||
SignatureScheme <- HashSignAlgos >>,
+ EncCertAuths = encode_cert_auths(CertAuths),
CertTypesLen = byte_size(CertTypes),
HashSignsLen = byte_size(HashSigns),
- CertAuthsLen = byte_size(CertAuths),
+ CertAuthsLen = byte_size(EncCertAuths),
{?CERTIFICATE_REQUEST,
- <<?BYTE(CertTypesLen), CertTypes/binary,
- ?UINT16(HashSignsLen), HashSigns/binary,
- ?UINT16(CertAuthsLen), CertAuths/binary>>
+ <<?BYTE(CertTypesLen), CertTypes/binary,
+ ?UINT16(HashSignsLen), HashSigns/binary,
+ ?UINT16(CertAuthsLen), EncCertAuths/binary>>
};
encode_handshake(#certificate_request{certificate_types = CertTypes,
certificate_authorities = CertAuths},
_Version) ->
+ EncCertAuths = encode_cert_auths(CertAuths),
CertTypesLen = byte_size(CertTypes),
- CertAuthsLen = byte_size(CertAuths),
+ CertAuthsLen = byte_size(EncCertAuths),
{?CERTIFICATE_REQUEST,
<<?BYTE(CertTypesLen), CertTypes/binary,
- ?UINT16(CertAuthsLen), CertAuths/binary>>
+ ?UINT16(CertAuthsLen), EncCertAuths/binary>>
};
encode_handshake(#server_hello_done{}, _Version) ->
{?SERVER_HELLO_DONE, <<>>};
@@ -764,10 +769,11 @@ encode_extensions([#early_data_indication_nst{indication = MaxSize} | Rest], Acc
encode_extensions(Rest, <<?UINT16(?EARLY_DATA_EXT),
?UINT16(4), ?UINT32(MaxSize), Acc/binary>>);
encode_extensions([#certificate_authorities{authorities = CertAuths}| Rest], Acc) ->
- CertAuthsLen = byte_size(CertAuths),
+ EncCertAuths = encode_cert_auths(CertAuths),
+ CertAuthsLen = byte_size(EncCertAuths),
Len = CertAuthsLen + 2,
encode_extensions(Rest, <<?UINT16(?CERTIFICATE_AUTHORITIES_EXT), ?UINT16(Len),
- ?UINT16(CertAuthsLen), CertAuths/binary, Acc/binary>>).
+ ?UINT16(CertAuthsLen), EncCertAuths/binary, Acc/binary>>).
encode_cert_status_req(
StatusType,
@@ -816,6 +822,16 @@ encode_protocols_advertised_on_server(Protocols) ->
#next_protocol_negotiation{
extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
+encode_cert_auths(Auths) ->
+ encode_cert_auths(Auths, []).
+
+encode_cert_auths([], Acc) ->
+ list_to_binary(lists:reverse(Acc));
+encode_cert_auths([Auth | Auths], Acc) ->
+ DNEncodedBin = public_key:pkix_encode('Name', Auth, otp),
+ DNEncodedLen = byte_size(DNEncodedBin),
+ encode_cert_auths(Auths, [<<?UINT16(DNEncodedLen), DNEncodedBin/binary>> | Acc]).
+
%%====================================================================
%% Decode handshake
%%====================================================================
@@ -866,16 +882,16 @@ decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) ->
decode_handshake({3, 3} = Version, ?CERTIFICATE_REQUEST,
<<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary,
?UINT16(HashSignsLen), HashSigns:HashSignsLen/binary,
- ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) ->
+ ?UINT16(CertAuthsLen), EncCertAuths:CertAuthsLen/binary>>) ->
HashSignAlgos = decode_sign_alg(Version, HashSigns),
#certificate_request{certificate_types = CertTypes,
hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos},
- certificate_authorities = CertAuths};
+ certificate_authorities = decode_cert_auths(EncCertAuths, [])};
decode_handshake(_Version, ?CERTIFICATE_REQUEST,
<<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary,
- ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) ->
+ ?UINT16(CertAuthsLen), EncCertAuths:CertAuthsLen/binary>>) ->
#certificate_request{certificate_types = CertTypes,
- certificate_authorities = CertAuths};
+ certificate_authorities = decode_cert_auths(EncCertAuths, [])};
decode_handshake(_Version, ?SERVER_HELLO_DONE, <<>>) ->
#server_hello_done{};
decode_handshake({Major, Minor}, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen),
@@ -888,8 +904,8 @@ decode_handshake(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) ->
#client_key_exchange{exchange_keys = PKEPMS};
decode_handshake(_Version, ?FINISHED, VerifyData) ->
#finished{verify_data = VerifyData};
-decode_handshake(_, Message, _) ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_handshake, Message})).
+decode_handshake(_, MessageType, _) ->
+ throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, {unknown_or_malformed_handshake, MessageType})).
%%--------------------------------------------------------------------
@@ -970,6 +986,7 @@ decode_suites('2_bytes', Dec) ->
decode_suites('3_bytes', Dec) ->
from_3bytes(Dec).
+
%%====================================================================
%% Cipher suite handling
%%====================================================================
@@ -1045,7 +1062,8 @@ cipher_suites(Suites, true) ->
prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) ->
{ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}.
-select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyPairs) ->
+select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyAlts) ->
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version),
{SessionId, Resumed} = ssl_session:server_select_session(Version, SessIdTracker, SuggestedSessionId,
SslOpts, CertKeyPairs),
case Resumed of
@@ -1063,7 +1081,7 @@ select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessId
new_session_parameters(SessionId, #session{ecc = ECCCurve0} = Session, CipherSuites, SslOpts,
Version, Compressions, HashSigns, CertKeyPairs) ->
Compression = select_compression(Compressions),
- {Certs, Key, {ECCCurve, CipherSuite}} = select_cert_key_pair_and_params(CipherSuites, CertKeyPairs, HashSigns,
+ {Certs, Key, {ECCCurve, CipherSuite}} = server_select_cert_key_pair_and_params(CipherSuites, CertKeyPairs, HashSigns,
ECCCurve0, SslOpts, Version),
Session#session{session_id = SessionId,
ecc = ECCCurve,
@@ -1074,30 +1092,44 @@ new_session_parameters(SessionId, #session{ecc = ECCCurve0} = Session, CipherSui
%% Possibly support part of "trusted_ca_keys" extension that corresponds to TLS-1.3 certificate_authorities?!
-select_cert_key_pair_and_params(CipherSuites, [#{private_key := NoKey, certs := [[]] = NoCerts}], HashSigns, ECCCurve0,
+server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := NoKey, certs := [[]] = NoCerts}], HashSigns, ECCCurve0,
#{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) ->
%% This can happen if anonymous cipher suites are enabled
Suites = available_suites(undefined, UserSuites, Version, HashSigns, ECCCurve0),
CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder),
CurveAndSuite = cert_curve(undefined, ECCCurve0, CipherSuite0),
{NoCerts, NoKey, CurveAndSuite};
-select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs}], HashSigns, ECCCurve0,
+server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs}], HashSigns, ECCCurve0,
#{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) ->
Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0),
CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder),
CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0),
{Certs, Key, CurveAndSuite};
-select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs} | Rest], HashSigns, ECCCurve0,
+server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs} | Rest], HashSigns, ECCCurve0,
#{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = Opts, Version) ->
Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0),
case select_cipher_suite(CipherSuites, Suites, HonorCipherOrder) of
no_suite ->
- select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version);
+ server_select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version);
CipherSuite0 ->
- CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0),
- {Certs, Key, CurveAndSuite}
+ case is_acceptable_cert(Cert, HashSigns, ssl:tls_version(Version)) of
+ true ->
+ CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0),
+ {Certs, Key, CurveAndSuite};
+ false ->
+ server_select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version)
+ end
end.
+is_acceptable_cert(Cert, HashSigns, {Major, Minor}) when Major == 3,
+ Minor >= 3 ->
+ {SignAlgo0, Param, _, _, _} = get_cert_params(Cert),
+ SignAlgo = sign_algo(SignAlgo0, Param),
+ is_acceptable_hash_sign(SignAlgo, HashSigns);
+is_acceptable_cert(_,_,_) ->
+ %% Not negotiable pre TLS-1.2. So if cert is available for version it is acceptable
+ true.
+
supported_ecc({Major, Minor}) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) ->
Curves = tls_v1:ecc_curves(Minor),
#elliptic_curves{elliptic_curve_list = Curves};
@@ -1105,26 +1137,27 @@ supported_ecc(_) ->
#elliptic_curves{elliptic_curve_list = []}.
premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) ->
- try
- public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params)
- catch
- error:computation_failed ->
+ try
+ public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params)
+ catch
+ error:Reason:ST ->
+ ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
- end;
+ end;
premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) ->
- try
+ try
crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base])
- catch
- error:computation_failed ->
+ catch
+ error:Reason:ST ->
+ ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
end;
premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime,
verifier = Verifier}) ->
- try crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of
- PremasterSecret ->
- PremasterSecret
+ try crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']})
catch
- error:_ ->
+ error:Reason:ST ->
+ ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
end;
premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public},
@@ -1132,14 +1165,13 @@ premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Sa
case ssl_srp_primes:check_srp_params(Generator, Prime) of
ok ->
DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]),
- try crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of
- PremasterSecret ->
- PremasterSecret
+ try crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']})
catch
- error ->
+ error:Reason:ST ->
+ ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
end;
- _ ->
+ not_accepted ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
end;
premaster_secret(#client_rsa_psk_identity{
@@ -1185,14 +1217,16 @@ premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) ->
try public_key:decrypt_private(EncSecret, RSAPrivateKey,
[{rsa_pad, rsa_pkcs1_padding}])
catch
- _:_ ->
+ _:Reason:ST ->
+ ?SSL_LOG(debug, decrypt_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
end;
premaster_secret(EncSecret, #{algorithm := rsa} = Engine) ->
try crypto:private_decrypt(rsa, EncSecret, maps:remove(algorithm, Engine),
[{rsa_pad, rsa_pkcs1_padding}])
catch
- _:_ ->
+ _:Reason:ST ->
+ ?SSL_LOG(debug, decrypt_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
end.
%%====================================================================
@@ -1923,14 +1957,10 @@ handle_ocsp_extension(false = Stapling, Extensions) ->
end.
certificate_authorities(CertDbHandle, CertDbRef) ->
- Authorities = [ Cert || #cert{otp = Cert} <- certificate_authorities_from_db(CertDbHandle, CertDbRef)],
- Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) ->
- OTPSubj = TBSCert#'OTPTBSCertificate'.subject,
- DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp),
- DNEncodedLen = byte_size(DNEncodedBin),
- <<?UINT16(DNEncodedLen), DNEncodedBin/binary>>
- end,
- list_to_binary([Enc(Cert) || Cert <- Authorities]).
+ Auths = fun(#'OTPCertificate'{tbsCertificate = TBSCert}) ->
+ TBSCert#'OTPTBSCertificate'.subject
+ end,
+ [Auths(Cert) || #cert{otp = Cert} <- certificate_authorities_from_db(CertDbHandle, CertDbRef)].
%%--------------------------------------------------------------------
%%% Internal functions
@@ -2020,8 +2050,8 @@ validation_fun_and_state(undefined, VerifyState, CertPath, LogLevel) ->
apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogLevel) when
(VerifyResult0 == valid) or (VerifyResult0 == valid_peer) ->
VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState),
- case Fun(OtpCert, VerifyResult, UserState0) of
- {Valid, UserState} when (Valid == valid) or (Valid == valid_peer) ->
+ case apply_fun(Fun, OtpCert, VerifyResult, UserState0, CertPath) of
+ {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer) ->
case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of
valid ->
{Valid, {SslState, UserState}};
@@ -2031,9 +2061,9 @@ apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogL
{fail, _} = Fail ->
Fail
end;
-apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath, _LogLevel) ->
- case Fun(OtpCert, ExtensionOrError, UserState0) of
- {Valid, UserState} when (Valid == valid) or (Valid == valid_peer)->
+apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, CertPath, _LogLevel) ->
+ case apply_fun(Fun, OtpCert, ExtensionOrError, UserState0, CertPath) of
+ {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer)->
{Valid, {SslState, UserState}};
{fail, _} = Fail ->
Fail;
@@ -2041,6 +2071,14 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath,
{unknown, {SslState, UserState}}
end.
+apply_fun(Fun, OtpCert, ExtensionOrError, UserState, CertPath) ->
+ if is_function(Fun, 4) ->
+ #cert{der=DerCert} = lists:keyfind(OtpCert, #cert.otp, CertPath),
+ Fun(OtpCert, DerCert, ExtensionOrError, UserState);
+ is_function(Fun, 3) ->
+ Fun(OtpCert, ExtensionOrError, UserState)
+ end.
+
maybe_check_hostname(OtpCert, valid_peer, SslState) ->
case ssl_certificate:validate(OtpCert, valid_peer, SslState) of
{valid, _} ->
@@ -2076,11 +2114,10 @@ path_validation_alert(Reason) ->
digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) ->
- try do_digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) of
- Signature ->
- Signature
+ try do_digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo)
catch
- error:badkey->
+ error:Reason:ST ->
+ ?SSL_LOG(info, sign_error, [{error, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey)))
end.
@@ -2263,7 +2300,8 @@ encrypted_premaster_secret(Secret, RSAPublicKey) ->
rsa_pkcs1_padding}]),
#encrypted_premaster_secret{premaster_secret = PreMasterSecret}
catch
- _:_->
+ _:Reason:ST->
+ ?SSL_LOG(debug, encrypt_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed))
end.
@@ -3060,13 +3098,13 @@ decode_extensions(<<?UINT16(?EARLY_DATA_EXT), ?UINT16(4), ?UINT32(MaxSize),
Acc#{early_data =>
#early_data_indication_nst{indication = MaxSize}});
decode_extensions(<<?UINT16(?CERTIFICATE_AUTHORITIES_EXT), ?UINT16(Len),
- CertAuts0:Len/binary, Rest/binary>>,
+ CertAutsExt:Len/binary, Rest/binary>>,
Version, MessageType, Acc) ->
CertAutsLen = Len - 2,
- <<?UINT16(CertAutsLen), CertAuts/binary>> = CertAuts0,
+ <<?UINT16(CertAutsLen), EncCertAuts/binary>> = CertAutsExt,
decode_extensions(Rest, Version, MessageType,
Acc#{certificate_authorities =>
- #certificate_authorities{authorities = CertAuts}});
+ #certificate_authorities{authorities = decode_cert_auths(EncCertAuts, [])}});
%% Ignore data following the ClientHello (i.e.,
%% extensions) if not understood.
decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
@@ -3087,18 +3125,10 @@ decode_sign_alg({3,3}, SignSchemeList) ->
{true, {Hash, Sign}};
{Hash, rsa_pss_pss = Sign, _} ->
{true,{Hash, Sign}};
- {sha1, rsa_pkcs1, _} ->
- {true,{sha, rsa}};
{Hash, rsa_pkcs1, _} ->
{true,{Hash, rsa}};
- {sha1, ecdsa, _} ->
- {true,{sha, ecdsa}};
- {sha512,ecdsa, _} ->
- {true,{sha512, ecdsa}};
- {sha384,ecdsa, _} ->
- {true,{sha384, ecdsa}};
- {sha256,ecdsa, _}->
- {true,{sha256, ecdsa}};
+ {Hash, ecdsa, _} ->
+ {true,{Hash, ecdsa}};
_ ->
false
end;
@@ -3213,6 +3243,11 @@ decode_psk_binders(<<>>, Acc) ->
decode_psk_binders(<<?BYTE(Len), Binder:Len/binary, Rest/binary>>, Acc) ->
decode_psk_binders(Rest, [Binder|Acc]).
+decode_cert_auths(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_cert_auths(<<?UINT16(Len), Auth:Len/binary, Rest/binary>>, Acc) ->
+ decode_cert_auths(Rest, [public_key:pkix_normalize_name(Auth) | Acc]).
+
%% encode/decode stream of certificate data to/from list of certificate data
certs_to_list(ASN1Certs) ->
certs_to_list(ASN1Certs, []).
@@ -3477,21 +3512,6 @@ is_acceptable_cert_type(Sign, Types) ->
is_supported_sign(SignAlgo, _, HashSigns, []) ->
ssl_cipher:is_supported_sign(SignAlgo, HashSigns);
%% {'SignatureAlgorithm',{1,2,840,113549,1,1,11},'NULL'}
-is_supported_sign({Hash, Sign}, 'NULL', _, SignatureSchemes) ->
- Fun = fun (Scheme, Acc) ->
- {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme),
- S1 = case S0 of
- rsa_pkcs1 -> rsa;
- S -> S
- end,
- H1 = case H0 of
- sha1 -> sha;
- H -> H
- end,
- Acc orelse (Sign =:= S1 andalso
- Hash =:= H1)
- end,
- lists:foldl(Fun, false, SignatureSchemes);
%% TODO: Implement validation for the curve used in the signature
%% RFC 3279 - 2.2.3 ECDSA Signature Algorithm
%% When the ecdsa-with-SHA1 algorithm identifier appears as the
@@ -3503,20 +3523,15 @@ is_supported_sign({Hash, Sign}, 'NULL', _, SignatureSchemes) ->
%% the certificate of the issuer SHALL apply to the verification of the
%% signature.
is_supported_sign({Hash, Sign}, _Param, _, SignatureSchemes) ->
- Fun = fun (Scheme, Acc) ->
- {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme),
+ Fun = fun (Scheme) ->
+ {H, S0, _} = ssl_cipher:scheme_to_components(Scheme),
S1 = case S0 of
rsa_pkcs1 -> rsa;
S -> S
end,
- H1 = case H0 of
- sha1 -> sha;
- H -> H
- end,
- Acc orelse (Sign =:= S1 andalso
- Hash =:= H1)
+ (Sign =:= S1) andalso (Hash =:= H)
end,
- lists:foldl(Fun, false, SignatureSchemes).
+ lists:any(Fun, SignatureSchemes).
%% SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
@@ -3843,12 +3858,12 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR
customize_hostname_check := CustomizeHostnameCheck,
crl_check := CrlCheck,
log_level := Level,
- signature_algs := SignAlgos,
- signature_algs_cert := SignAlgosCert,
- depth := Depth},
+ depth := Depth} = Opts,
#{cert_ext := CertExt,
ocsp_responder_certs := OcspResponderCerts,
ocsp_state := OcspState}) ->
+ SignAlgos = maps:get(signature_algs, Opts, undefined),
+ SignAlgosCert = maps:get(signature_algs_cert, Opts, undefined),
ValidationFunAndState =
validation_fun_and_state(VerifyFun, #{role => Role,
certdb => CertDbHandle,
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 3c770f4e25..6dd47019f4 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -54,6 +54,7 @@
dh_public_value %% TLS 1.3 DH Public Value from peer
}).
+-define(EMPTY_ID, <<>>).
-define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3
-define(NUM_OF_PREMASTERSECRET_BYTES, 48).
-define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, ssl_dh_groups:modp2048_generator()).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index a611f237d0..cdb3154cb6 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -23,7 +23,8 @@
-ifndef(ssl_internal).
-define(ssl_internal, true).
--include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+-include_lib("public_key/include/public_key.hrl").
-define(SECRET_PRINTOUT, "***").
@@ -132,6 +133,7 @@
cacerts]},
cacerts => {undefined, [versions]},
cert => {undefined, [versions]},
+ certs_keys => {undefined, [versions]},
certfile => {<<>>, [versions]},
certificate_authorities => {false, [versions]},
ciphers => {[], [versions]},
@@ -154,6 +156,7 @@
hibernate_after => {infinity, [versions]},
honor_cipher_order => {false, [versions]},
honor_ecc_order => {undefined, [versions]},
+ keep_secrets => {false, [versions]},
key => {undefined, [versions]},
keyfile => {undefined, [versions,
certfile]},
@@ -177,11 +180,12 @@
password => {"", [versions]},
protocol => {tls, []},
psk_identity => {undefined, [versions]},
+ receiver_spawn_opts => {[], [versions]},
renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]},
reuse_session => {undefined, [versions]},
reuse_sessions => {true, [versions]},
secure_renegotiate => {true, [versions]},
- keep_secrets => {false, [versions]},
+ sender_spawn_opts => {[], [versions]},
server_name_indication => {undefined, [versions]},
session_tickets => {disabled, [versions]},
signature_algs => {undefined, [versions]},
@@ -217,10 +221,30 @@
versions => {[], [protocol]}
}).
--define('TLS-1_3_ONLY_OPTIONS', [anti_replay, cookie, early_data, key_update_at, middlebox_comp_mode, session_tickets, supported_groups, use_ticket]).
--define('FROM_TLS-1_2_ONLY_OPTIONS', [signature_algs, signature_algs_cert]).
--define('PRE_TLS-1_3_ONLY_OPTIONS', [client_renegotiation, secure_renegotiate]).
--define('TLS-1_0_ONLY_OPTIONS', [padding_check, beast_mitigation]).
+-define('TLS-1_3_ONLY_OPTIONS', [anti_replay,
+ certificate_authorities,
+ cookie,
+ early_data,
+ key_update_at,
+ middlebox_comp_mode,
+ session_tickets,
+ supported_groups,
+ use_ticket]).
+-define('FROM_TLS-1_2_ONLY_OPTIONS', [signature_algs,
+ signature_algs_cert]).
+-define('PRE_TLS-1_3_ONLY_OPTIONS', [client_renegotiation,
+ dh_file,
+ eccs,
+ fallback,
+ secure_renegotiate,
+ psk_identity,
+ reuse_session,
+ reuse_sessions,
+ srp_identity,
+ user_lookup_fun
+ ]).
+-define('TLS-1_0_ONLY_OPTIONS', [padding_check,
+ beast_mitigation]).
-record(socket_options,
{
@@ -247,6 +271,24 @@
{stop, any(), any()}.
-type ssl_options() :: map().
+
+-define(SSL_LOG(Level, Descr, Reason),
+ fun() ->
+ case get(log_level) of
+ undefined ->
+ %% Use debug here, i.e. log everything and let loggers
+ %% log_level decide if it should be logged
+ ssl_logger:log(Level, debug,
+ #{description => Descr, reason => Reason},
+ ?LOCATION);
+ __LogLevel__ ->
+ ssl_logger:log(Level, __LogLevel__,
+ #{description => Descr, reason => Reason},
+ ?LOCATION)
+ end
+ end()).
+
+
%% Internal ticket data record holding pre-processed ticket data.
-record(ticket_data,
{key, %% key in client ticket store
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index 9e872587e2..8777233b81 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -57,10 +57,10 @@ log(Level, LogLevel, ReportMap, Meta) ->
ok
end.
-debug(Level, Direction, Protocol, Message)
+debug(LogLevel, Direction, Protocol, Message)
when (Direction =:= inbound orelse Direction =:= outbound) andalso
(Protocol =:= 'record' orelse Protocol =:= 'handshake') ->
- case logger:compare_levels(Level, debug) of
+ case logger:compare_levels(LogLevel, debug) of
lt ->
?LOG_DEBUG(#{direction => Direction,
protocol => Protocol,
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index a4fb07c795..2e96624a2b 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -20,6 +20,8 @@
%%----------------------------------------------------------------------
%% Purpose: Manages ssl sessions and trusted certifacates
+%% (Note: See the document internal_doc/pem_and_cert_cache.md for additional
+%% information)
%%----------------------------------------------------------------------
-module(ssl_manager).
@@ -274,23 +276,23 @@ init([ManagerName, PemCacheName, Opts]) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({{connection_init, <<>>, Role, {CRLCb, UserCRLDb}}, _Pid}, _From,
- #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) ->
+ #state{certificate_db = [CertDb, FileRefDb, PemCache | _] = Db} = State) ->
Ref = make_ref(),
{reply, {ok, #{cert_db_ref => Ref,
cert_db_handle => CertDb,
fileref_db_handle => FileRefDb,
- pem_cache => PemChace,
+ pem_cache => PemCache,
session_cache => session_cache(Role, State),
crl_db_info => {CRLCb, crl_db_info(Db, UserCRLDb)}}}, State};
handle_call({{connection_init, Trustedcerts, Role, {CRLCb, UserCRLDb}}, Pid}, _From,
- #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) ->
+ #state{certificate_db = [CertDb, FileRefDb, PemCache | _] = Db} = State) ->
case add_trusted_certs(Pid, Trustedcerts, Db) of
{ok, Ref} ->
{reply, {ok, #{cert_db_ref => Ref,
cert_db_handle => CertDb,
fileref_db_handle => FileRefDb,
- pem_cache => PemChace,
+ pem_cache => PemCache,
session_cache => session_cache(Role, State),
crl_db_info => {CRLCb, crl_db_info(Db, UserCRLDb)}}}, State};
{error, _} = Error ->
@@ -310,10 +312,12 @@ handle_call({{register_session, Host, Port, Session},_}, _, State0) ->
State = client_register_session(Host, Port, Session, State0),
{reply, ok, State};
handle_call({refresh_trusted_db, _}, _, #state{certificate_db = Db} = State) ->
- ssl_pkix_db:refresh_trusted_certs(Db),
+ PemCache = get(ssl_pem_cache),
+ ssl_pkix_db:refresh_trusted_certs(Db, PemCache),
{reply, ok, State};
handle_call({{refresh_trusted_db, File}, _}, _, #state{certificate_db = Db} = State) ->
- ssl_pkix_db:refresh_trusted_certs(File, Db),
+ PemCache = get(ssl_pem_cache),
+ ssl_pkix_db:refresh_trusted_certs(File, Db, PemCache),
{reply, ok, State}.
%%--------------------------------------------------------------------
@@ -461,7 +465,7 @@ invalidate_session(Cache, CacheCb, Key, _Session,
clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) ->
case ssl_pkix_db:ref_count(Ref, RefDb, 0) of
- 0 ->
+ 0 ->
ssl_pkix_db:remove(Ref, RefDb),
ssl_pkix_db:remove(File, FileMapDb),
ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
diff --git a/lib/ssl/src/ssl_pem_cache.erl b/lib/ssl/src/ssl_pem_cache.erl
index 2c24351714..cc1c04da9a 100644
--- a/lib/ssl/src/ssl_pem_cache.erl
+++ b/lib/ssl/src/ssl_pem_cache.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 20016-2021. All Rights Reserved.
+%% Copyright Ericsson AB 20016-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@
%%----------------------------------------------------------------------
%% Purpose: Manages ssl sessions and trusted certifacates
+%% (Note: See the document internal_doc/pem_and_cert_cache.md for additional
+%% information)
%%----------------------------------------------------------------------
-module(ssl_pem_cache).
@@ -50,7 +52,6 @@
}).
-define(CLEAR_PEM_CACHE, 120000).
--define(DEFAULT_MAX_SESSION_CACHE, 1000).
%%====================================================================
%% API
diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl
index dbdae5307c..eac9e2a8b3 100644
--- a/lib/ssl/src/ssl_pkix_db.erl
+++ b/lib/ssl/src/ssl_pkix_db.erl
@@ -31,11 +31,11 @@
-export([create/1, create_pem_cache/1,
add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3,
- refresh_trusted_certs/1,
refresh_trusted_certs/2,
+ refresh_trusted_certs/3,
extract_trusted_certs/1,
remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1,
- ref_count/3, lookup_trusted_cert/4, foldl/3, select_cert_by_issuer/2,
+ ref_count/3, lookup_trusted_cert/4, foldl/3, select_certentries_by_ref/2,
decode_pem_file/1, lookup/2]).
%%====================================================================
@@ -117,7 +117,7 @@ lookup_trusted_cert(_DbHandle, {extracted,Certs}, SerialNumber, Issuer) ->
CertSerial =:= SerialNumber, CertIssuer =:= Issuer],
undefined
catch
- Cert ->
+ throw:Cert ->
{ok, Cert}
end.
@@ -132,12 +132,12 @@ lookup_trusted_cert(_DbHandle, {extracted,Certs}, SerialNumber, Issuer) ->
add_trusted_certs(_Pid, {extracted, _} = Certs, _) ->
{ok, Certs};
-add_trusted_certs(_Pid, {der, DerList}, [CertDb, _,_ | _]) ->
+add_trusted_certs(_Pid, {der, DerList}, [CertDb, _, _ | _]) ->
NewRef = make_ref(),
add_certs_from_der(DerList, NewRef, CertDb),
{ok, NewRef};
-add_trusted_certs(_Pid, File, [ _, {RefDb, FileMapDb} | _] = Db) ->
+add_trusted_certs(_Pid, File, [_, {RefDb, FileMapDb} | _] = Db) ->
case lookup(File, FileMapDb) of
[Ref] ->
ref_count(Ref, RefDb, 1),
@@ -146,18 +146,18 @@ add_trusted_certs(_Pid, File, [ _, {RefDb, FileMapDb} | _] = Db) ->
new_trusted_cert_entry(File, Db)
end.
-refresh_trusted_certs(File, [CertsDb, {_, FileMapDb} | _]) ->
+refresh_trusted_certs(File, [CertsDb, {_, FileMapDb} | _], PemCache) ->
case lookup(File, FileMapDb) of
[Ref] ->
- {ok, Content} = decode_pem_file(File),
- remove_trusted_certs(Ref, CertsDb),
- add_certs_from_pem(Content, Ref, CertsDb);
+ Certs = ssl_certificate:file_to_certificats(File, PemCache),
+ KeyList = select_certentries_by_ref(Ref,CertsDb),
+ update_certs(Ref, Certs, KeyList, CertsDb);
undefined ->
ok
end.
-refresh_trusted_certs([_, {_, FileMapDb} | _] = Db) ->
+refresh_trusted_certs([_, {_, FileMapDb} | _] = Db, PemCache) ->
Refresh = fun({File, _}, Acc) ->
- refresh_trusted_certs(File, Db),
+ refresh_trusted_certs(File, Db, PemCache),
Acc
end,
foldl(Refresh, refresh, FileMapDb).
@@ -243,9 +243,13 @@ lookup(Key, Db) ->
foldl(Fun, Acc0, Cache) ->
ets:foldl(Fun, Acc0, Cache).
-
-select_cert_by_issuer(Cache, Issuer) ->
- ets:select(Cache, [{{{'_','_', Issuer},{'_', '$1'}},[],['$$']}]).
+%%--------------------------------------------------------------------
+-spec select_certentries_by_ref(reference(), db_handle()) -> term().
+%%
+%% Description: Select certs entries originating from same source
+%%--------------------------------------------------------------------
+select_certentries_by_ref(Ref, Cache) ->
+ ets:select(Cache, [{{{Ref,'_', '_'}, '_'},[],['$_']}]).
%%--------------------------------------------------------------------
-spec ref_count(term(), db_handle(), integer()) -> integer().
@@ -297,40 +301,40 @@ remove_certs(Ref, CertsDb) ->
ok.
add_certs_from_der(DerList, Ref, CertsDb) ->
- Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end,
+ Add = fun(Cert) -> add_cert(Cert, Ref, CertsDb) end,
[Add(Cert) || Cert <- DerList],
ok.
certs_from_der(DerList) ->
Ref = make_ref(),
[Decoded || Cert <- DerList,
- Decoded <- [decode_certs(Ref, Cert)],
+ Decoded <- [decode_cert(Ref, Cert)],
Decoded =/= undefined].
add_certs_from_pem(PemEntries, Ref, CertsDb) ->
- Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end,
+ Add = fun(Cert) -> add_cert(Cert, Ref, CertsDb) end,
[Add(Cert) || {'Certificate', Cert, not_encrypted} <- PemEntries],
ok.
-add_certs(Cert, Ref, CertsDb) ->
+add_cert(Cert, Ref, CertsDb) ->
try
- {decoded, {Key, Val}} = decode_certs(Ref, Cert),
+ {decoded, {Key, Val}} = decode_cert(Ref, Cert),
insert(Key, Val, CertsDb)
catch
error:_ ->
ok
end.
-decode_certs(Ref, #cert{otp=ErlCert} = Cert) ->
+decode_cert(Ref, #cert{otp=ErlCert} = Cert) ->
TBSCertificate = ErlCert#'OTPCertificate'.tbsCertificate,
SerialNumber = TBSCertificate#'OTPTBSCertificate'.serialNumber,
Issuer = public_key:pkix_normalize_name(
TBSCertificate#'OTPTBSCertificate'.issuer),
{decoded, {{Ref, SerialNumber, Issuer}, Cert}};
-decode_certs(Ref, Der) ->
+decode_cert(Ref, Der) ->
try public_key:pkix_decode_cert(Der, otp) of
ErlCert ->
- decode_certs(Ref, #cert{der=Der, otp=ErlCert})
+ decode_cert(Ref, #cert{der=Der, otp=ErlCert})
catch error:_ ->
?LOG_NOTICE("SSL WARNING: Ignoring a CA cert as "
"it could not be correctly decoded.~n"),
@@ -352,7 +356,7 @@ new_trusted_cert_entry(File, [CertsDb, RefsDb, _ | _]) ->
add_crls([_,_,_, {_, Mapping} | _], ?NO_DIST_POINT, CRLs) ->
[add_crls(CRL, Mapping) || CRL <- CRLs];
add_crls([_,_,_, {Cache, Mapping} | _], Path, CRLs) ->
- insert(Path, CRLs, Cache),
+ insert(Path, CRLs, Cache),
[add_crls(CRL, Mapping) || CRL <- CRLs].
add_crls(CRL, Mapping) ->
@@ -365,7 +369,7 @@ remove_crls([_,_,_, {Cache, Mapping} | _], Path) ->
case lookup(Path, Cache) of
undefined ->
ok;
- CRLs ->
+ [CRLs] ->
remove(Path, Cache),
[rm_crls(CRL, Mapping) || CRL <- CRLs]
end.
@@ -378,3 +382,27 @@ crl_issuer(DerCRL) ->
TBSCRL = CRL#'CertificateList'.tbsCertList,
TBSCRL#'TBSCertList'.issuer.
+update_certs(Ref, CertList, KeyList, CertsDb) ->
+ {Insert, Delete} = insert_delete_lists(Ref, CertList, CertsDb, [], KeyList),
+ insert_cert_entries(Insert, CertsDb),
+ remove_cert_entries(Delete, CertsDb).
+
+insert_delete_lists(_, [], _, Insert, Delete) ->
+ {Insert, Delete};
+insert_delete_lists(Ref, [Cert | Rest], CertsDb, Insert, Delete) ->
+ case decode_cert(Ref, Cert) of
+ {decoded, {Key, Value} = Entry} ->
+ case lookup(Key, CertsDb) of
+ [Value] -> %% Entry already exists and is unchanged
+ insert_delete_lists(Ref, Rest, CertsDb, Insert, lists:keydelete(Key, 1, Delete));
+ _ ->
+ insert_delete_lists(Ref, Rest, CertsDb, [Entry | Insert], lists:keydelete(Key, 1, Delete))
+ end;
+ undefined ->
+ insert_delete_lists(Ref, Rest, CertsDb, Insert, Delete)
+ end.
+
+insert_cert_entries(EntryList, CertsDb) ->
+ ets:insert(CertsDb, EntryList).
+remove_cert_entries(EntryList, CertsDb) ->
+ lists:foreach(fun({Key, Value}) -> remove(Key, Value, CertsDb) end, EntryList).
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index c3c31e8767..b7b68edd82 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -447,11 +447,14 @@ decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment
case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of
Content when is_binary(Content) ->
Content;
- _ ->
+ Reason ->
+ ?SSL_LOG(debug, decrypt_error, [{reason,Reason},
+ {stacktrace, process_info(self(), current_stacktrace)}]),
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end
catch
- _:_ ->
+ _:Reason2:ST ->
+ ?SSL_LOG(debug, decrypt_error, [{reason,Reason2}, {stacktrace, ST}]),
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
@@ -480,10 +483,10 @@ empty_connection_state(ConnectionEnd, Version,
secure_renegotiation => undefined,
client_verify_data => undefined,
server_verify_data => undefined,
- max_early_data_size => MaxEarlyDataSize,
+ pending_early_data_size => MaxEarlyDataSize,
max_fragment_length => undefined,
trial_decryption => false,
- early_data_limit => false
+ early_data_accepted => false
}.
init_security_parameters(?CLIENT, Version) ->
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index 2f35c54c64..3999b2fc0e 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
+%%
%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
-%%
+%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
@@ -14,7 +14,7 @@
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -30,9 +30,13 @@
-include("ssl_api.hrl").
%% Internal application API
--export([is_new/2, client_select_session/5, server_select_session/5, valid_session/2, legacy_session_id/0]).
+-export([is_new/2,
+ client_select_session/5,
+ server_select_session/5,
+ valid_session/2,
+ legacy_session_id/1]).
--type seconds() :: integer().
+-type seconds() :: integer().
%%--------------------------------------------------------------------
-spec legacy_session_id() -> ssl:session_id().
@@ -42,51 +46,71 @@
%% If now lower versions are configured this function can be called
%% for a dummy value.
%%--------------------------------------------------------------------
-legacy_session_id() ->
- crypto:strong_rand_bytes(32).
-
+legacy_session_id(#{middlebox_comp_mode := true}) ->
+ legacy_session_id();
+legacy_session_id(_) ->
+ ?EMPTY_ID.
%%--------------------------------------------------------------------
--spec is_new(ssl:session_id(), ssl:session_id()) -> boolean().
-%%
-%% Description: Checks if the session id decided by the server is a
-%% new or resumed sesion id.
+-spec is_new(ssl:session_id() | #session{}, ssl:session_id()) -> boolean().
+ %%
+ %% Description: Checks if the session id decided by the server is a
+%% new or resumed sesion id. TLS-1.3 middlebox negotiation
+%% requies that client also needs to check "is_resumable" in
+%% its current session data when pre TLS-1.3 version is
+%% negotiated.
%%--------------------------------------------------------------------
-is_new(<<>>, _) ->
+is_new(?EMPTY_ID, _) ->
true;
is_new(SessionId, SessionId) ->
false;
+is_new(#session{session_id = ?EMPTY_ID}, _) ->
+ true;
+is_new(#session{session_id = SessionId,
+ is_resumable = true}, SessionId) ->
+ false;
is_new(_ClientSuggestion, _ServerDecision) ->
true.
%%--------------------------------------------------------------------
--spec client_select_session({ssl:host(), inet:port_number(), map()}, db_handle(), atom(),
- #session{}, list()) -> #session{}.
+-spec client_select_session({ssl:host(), inet:port_number(), map()},
+ db_handle(), atom(), #session{}, list()) -> #session{}.
%%
%% Description: Should be called by the client side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
client_select_session({_, _, #{versions := Versions,
- protocol := Protocol}} = ClientInfo,
+ protocol := Protocol} = Opts} = ClientInfo,
Cache, CacheCb, NewSession, CertKeyPairs) ->
-
+
RecordCb = record_cb(Protocol),
- Version = RecordCb:lowest_protocol_version(Versions),
-
- case Version of
- {3, N} when N >= 4 ->
- NewSession#session{session_id = legacy_session_id()};
+ LVersion = RecordCb:lowest_protocol_version(Versions),
+ HVersion = RecordCb:highest_protocol_version(Versions),
+
+ case LVersion of
+ {3, 4} ->
+ %% Session reuse is not supported, do pure legacy
+ %% middlebox comp mode negotiation, by providing either
+ %% empty session id (no middle box) or random id (middle
+ %% box mode).
+ NewSession#session{session_id = legacy_session_id(Opts)};
_ ->
- do_client_select_session(ClientInfo, Cache, CacheCb, NewSession, CertKeyPairs)
- end.
+ Session = do_client_select_session(ClientInfo, Cache, CacheCb,
+ NewSession, CertKeyPairs),
+ %% If TLS-1.3 is highest version and there was no previous
+ %% session id that could be reused, if TLS-1.3 is not
+ %% negotiated, possibly use random id for middle box mode
+ %% negotiation.
+ maybe_handle_middlebox(HVersion, Session, Opts)
+ end.
%%--------------------------------------------------------------------
--spec server_select_session(ssl_record:ssl_version(), pid(), binary(), map(),
+-spec server_select_session(ssl_record:ssl_version(), pid(), ssl:session_id(), map(),
list()) -> {binary(), #session{} | undefined}.
%%
%% Description: Should be called by the server side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
-server_select_session(_, SessIdTracker, <<>>, _SslOpts, _CertKeyPairs) ->
+server_select_session(_, SessIdTracker, ?EMPTY_ID, _SslOpts, _CertKeyPairs) ->
{ssl_server_session_cache:new_session_id(SessIdTracker), undefined};
server_select_session(_, SessIdTracker, SuggestedId, Options, CertKeyPairs) ->
case is_resumable(SuggestedId, SessIdTracker, Options, CertKeyPairs)
@@ -111,26 +135,28 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession, _) when is_binary(SessionId) andalso
- is_binary(SessionData) ->
+do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}},
+ _, _, NewSession, _) when is_binary(SessionId) andalso
+ is_binary(SessionData) ->
try binary_to_term(SessionData, [safe]) of
Session ->
- Session
+ Session#session{is_resumable = true}
catch
_:_ ->
- NewSession#session{session_id = <<>>}
+ NewSession#session{session_id = ?EMPTY_ID}
end;
-do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession, _) when is_binary(SessionId)->
+do_client_select_session({Host, Port, #{reuse_session := SessionId}},
+ Cache, CacheCb, NewSession, _) when is_binary(SessionId)->
case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
undefined ->
- NewSession#session{session_id = <<>>};
+ NewSession#session{session_id = ?EMPTY_ID};
#session{} = Session->
Session
end;
do_client_select_session(ClientInfo, Cache, CacheCb, NewSession, CertKeyPairs) ->
case select_session(ClientInfo, Cache, CacheCb, CertKeyPairs) of
no_session ->
- NewSession#session{session_id = <<>>};
+ NewSession#session{session_id = ?EMPTY_ID};
Session ->
Session
end.
@@ -156,7 +182,8 @@ select_session(Sessions, #{ciphers := Ciphers}, CertKeyPairs) ->
end,
not (resumable(Session#session.is_resumable) andalso
lists:member(Session#session.cipher_suite, Ciphers)
- andalso (is_owncert(SessionOwnCert, CertKeyPairs) orelse (SessionOwnCert == undefined)))
+ andalso (is_owncert(SessionOwnCert, CertKeyPairs)
+ orelse (SessionOwnCert == undefined)))
end,
case lists:dropwhile(IsNotResumable, Sessions) of
[] -> no_session;
@@ -165,7 +192,8 @@ select_session(Sessions, #{ciphers := Ciphers}, CertKeyPairs) ->
is_resumable(_, _, #{reuse_sessions := false}, _) ->
{false, undefined};
-is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCertKeyPairs) ->
+is_resumable(SuggestedSessionId, SessIdTracker,
+ #{reuse_session := ReuseFun} = Options, OwnCertKeyPairs) ->
case ssl_server_session_cache:reuse_session(SessIdTracker, SuggestedSessionId) of
#session{cipher_suite = CipherSuite,
own_certificates = [SessionOwnCert | _],
@@ -207,3 +235,11 @@ record_cb(tls) ->
tls_record;
record_cb(dtls) ->
dtls_record.
+
+legacy_session_id() ->
+ crypto:strong_rand_bytes(32).
+
+maybe_handle_middlebox({3, 4}, #session{session_id = ?EMPTY_ID} = Session, #{middlebox_comp_mode := true})->
+ Session#session{session_id = legacy_session_id()};
+maybe_handle_middlebox(_, Session, _) ->
+ Session.
diff --git a/lib/ssl/src/tls_client_ticket_store.erl b/lib/ssl/src/tls_client_ticket_store.erl
index eb10adc9f1..146fd4c8cc 100644
--- a/lib/ssl/src/tls_client_ticket_store.erl
+++ b/lib/ssl/src/tls_client_ticket_store.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -202,8 +202,8 @@ iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc) ->
lock = Lock}, Iter} when Lock =:= undefined orelse
Lock =:= Pid ->
MaxEarlyData = tls_handshake_1_3:get_max_early_data(Extensions),
- Age = erlang:system_time(seconds) - Timestamp,
- if Age < Lifetime ->
+ Age = erlang:monotonic_time(millisecond) - Timestamp,
+ if Age < Lifetime * 1000 ->
case verify_ticket_sni(SNI, TicketSNI) of
match ->
case lists:member(Cipher, Ciphers) of
@@ -274,7 +274,7 @@ get_tickets(#state{db = Db} = State, Pid, [Key|T], Acc) ->
ticket = Ticket,
extensions = Extensions
} = NewSessionTicket,
- TicketAge = erlang:system_time(seconds) - Timestamp,
+ TicketAge = erlang:monotonic_time(millisecond) - Timestamp,
ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
Identity = #psk_identity{
identity = Ticket,
@@ -329,8 +329,8 @@ collect_invalid_tickets(Iter0, Lifetime, Acc) ->
case gb_trees:next(Iter0) of
{Key, #data{timestamp = Timestamp,
lock = undefined}, Iter} ->
- Age = erlang:system_time(seconds) - Timestamp,
- if Age < Lifetime ->
+ Age = erlang:monotonic_time(millisecond) - Timestamp,
+ if Age < Lifetime * 1000 ->
collect_invalid_tickets(Iter, Lifetime, Acc);
true ->
collect_invalid_tickets(Iter, Lifetime, [Key|Acc])
@@ -343,7 +343,7 @@ collect_invalid_tickets(Iter0, Lifetime, Acc) ->
store_ticket(#state{db = Db0, max = Max} = State, Ticket, CipherSuite, SNI, PSK) ->
- Timestamp = erlang:system_time(seconds),
+ Timestamp = erlang:monotonic_time(millisecond),
Size = gb_trees:size(Db0),
Db1 = if Size =:= Max ->
delete_oldest(Db0);
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 05cf5bb6c3..6cc9e21cfb 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -34,6 +34,7 @@
%% ClientKeyExchange \
%% CertificateVerify* Flight 3 part 1
%% [ChangeCipherSpec] /
+%% NextProtocol*
%% Finished --------> / Flight 3 part 2
%% [ChangeCipherSpec]
%% <-------- Finished Flight 4
@@ -48,6 +49,7 @@
%% [ChangeCipherSpec]
%% <-------- Finished Abbrev Flight 2 part 2
%% [ChangeCipherSpec]
+%% NextProtocol*
%% Finished --------> Abbrev Flight 3
%% Application Data <-------> Application Data
%%
@@ -70,13 +72,14 @@
%% |
%% New session | Resumed session
%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
-%%
+%% WAIT_CERT_VERIFY
%% <- Possibly Receive -- | |
-%% OCSP Stapel ------> | Flight 3 part 1 |
+%% OCSP Stapel/CertVerify -> | Flight 3 part 1 |
%% | |
%% V | Abbrev Flight 2 part 2 to Abbrev Flight 3
%% CIPHER |
%% | |
+%% | |
%% | Fligth 3 part 2 to Flight 4 |
%% | |
%% V V
@@ -111,7 +114,8 @@
%% Setup
-export([init/1]).
--export([renegotiate/2]).
+-export([renegotiate/2,
+ choose_tls_fsm/2]).
%% gen_statem state functions
-export([initial_hello/3,
@@ -121,6 +125,7 @@
user_hello/3,
wait_ocsp_stapling/3,
certify/3,
+ wait_cert_verify/3,
cipher/3,
abbreviated/3,
connection/3]).
@@ -135,17 +140,17 @@
%% Internal application API
%%====================================================================
init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
- State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
- Host, Port, Socket, Options, User, CbInfo),
- try
- State1 = #state{static_env = #static_env{session_cache = Cache,
+ State0 = initial_state(Role, Sender, Host, Port, Socket, Options, User, CbInfo),
+ try
+ State1 = #state{static_env = #static_env{session_cache = Cache,
session_cache_cb = CacheCb
},
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs},
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts},
ssl_options = SslOptions,
session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
State = case Role of
client ->
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts),
Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0, CertKeyPairs),
State1#state{session = Session};
server ->
@@ -154,6 +159,7 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
tls_gen_connection:initialize_tls_sender(State),
gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch throw:Error ->
+ #state{protocol_specific = Map} = State0,
EState = State0#state{protocol_specific = Map#{error => Error}},
gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
@@ -207,31 +213,26 @@ config_error(Type, Event, State) ->
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-hello(internal, #client_hello{extensions = Extensions} = Hello,
- #state{ssl_options = #{handshake := hello},
- static_env = #static_env{role = server},
- handshake_env = HsEnv,
+hello(internal, #client_hello{extensions = Extensions},
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{continue_status = pause},
start_or_recv_from = From} = State) ->
- {next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{hello = Hello}},
- [{reply, From, {ok, Extensions}}]};
-hello(internal, #server_hello{extensions = Extensions} = Hello,
- #state{ssl_options = #{handshake := hello},
+ {next_state, user_hello, State#state{start_or_recv_from = undefined},
+ [{postpone, true}, {reply, From, {ok, Extensions}}]};
+hello(internal, #server_hello{extensions = Extensions},
+ #state{handshake_env = #handshake_env{continue_status = pause},
static_env = #static_env{role = client},
- handshake_env = HsEnv,
- start_or_recv_from = From} = State) ->
+ start_or_recv_from = From} = State) ->
{next_state, user_hello,
- State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{
- hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+ State#state{start_or_recv_from = undefined}, [{postpone, true}, {reply, From, {ok, Extensions}}]};
hello(internal, #client_hello{client_version = ClientVersion} = Hello,
#state{static_env = #static_env{role = server}, connection_env = CEnv} = State0) ->
try
#state{ssl_options = SslOpts} = State1 = tls_dtls_connection:handle_sni_extension(State0, Hello),
case choose_tls_fsm(SslOpts, Hello) of
tls_1_3_fsm ->
- %% Continue in TLS 1.3 'start' state
- {next_state, start, State1, [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]};
+ {next_state, start, State1,
+ [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]};
tls_1_0_to_1_2_fsm ->
{ServerHelloExt, Type, State} = handle_client_hello(Hello, State1),
{next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}
@@ -319,6 +320,19 @@ certify(Type, Event, State) ->
ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
end.
+
+%%--------------------------------------------------------------------
+-spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert_verify(info, Event, State) ->
+ gen_info(Event, ?FUNCTION_NAME, State);
+wait_cert_verify(Type, Event, State) ->
+ try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State)
+ catch throw:#alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
+ end.
+
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
@@ -353,13 +367,14 @@ connection(internal, #hello_request{},
handshake_env = #handshake_env{
renegotiation = {Renegotiation, peer},
ocsp_stapling_state = OcspState},
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs},
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts},
session = Session0,
ssl_options = SslOpts,
protocol_specific = #{sender := Pid},
connection_states = ConnectionStates} = State0) ->
try tls_sender:peer_renegotiate(Pid) of
{ok, Write} ->
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts),
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
Session#session.session_id,
@@ -371,8 +386,9 @@ connection(internal, #hello_request{},
ConnectionStates#{current_write => Write},
session = Session}),
tls_gen_connection:next_event(hello, no_record, State, Actions)
- catch
- _:_ ->
+ catch
+ _:Reason:ST ->
+ ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]),
{stop, {shutdown, sender_blocked}, State0}
end;
connection(internal, #hello_request{},
@@ -459,6 +475,7 @@ code_change(_OldVsn, StateName, State, _) ->
%%--------------------------------------------------------------------
initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ put(log_level, maps:get(log_level, SSLOptions)),
#{erl_dist := IsErlDist,
%% Use highest supported version for client/server random nonce generation
versions := [Version|_],
@@ -513,7 +530,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State
renegotiation = {Renegotiation, _},
negotiated_protocol = CurrentProtocol,
sni_guided_cert_selection = SNICertSelection} = HsEnv,
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv,
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv,
session = Session0,
ssl_options = SslOpts} = State,
SessionTracker = proplists:get_value(session_id_tracker, Trackers),
@@ -522,7 +539,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State
tls_handshake:hello(Hello,
SslOpts,
{SessionTracker, Session0,
- ConnectionStates0, CertKeyPairs, KeyExAlg},
+ ConnectionStates0, CertKeyAlts, KeyExAlg},
Renegotiation),
Protocol = case Protocol0 of
undefined -> CurrentProtocol;
@@ -549,7 +566,8 @@ gen_info(Event, connection = StateName, State) ->
try
tls_gen_connection: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)
@@ -559,7 +577,8 @@ gen_info(Event, StateName, State) ->
try
tls_gen_connection: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)
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 19c9a05a2f..8041e48eb0 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -125,6 +125,8 @@
config_error/3,
user_hello/3,
start/3,
+ hello_middlebox_assert/3,
+ hello_retry_middlebox_assert/3,
negotiated/3,
wait_cert/3,
wait_cv/3,
@@ -183,7 +185,7 @@ update_cipher_key(ConnStateName, CS0) ->
%% gen_statem callbacks
%%--------------------------------------------------------------------
callback_mode() ->
- state_functions.
+ [state_functions, state_enter].
init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
@@ -220,6 +222,8 @@ code_change(_OldVsn, StateName, State, _) ->
{start, timeout()} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+initial_hello(enter, _, State) ->
+ {keep_state, State};
initial_hello(Type, Event, State) ->
ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
@@ -228,77 +232,126 @@ initial_hello(Type, Event, State) ->
{start, timeout()} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+config_error(enter, _, State) ->
+ {keep_state, State};
config_error(Type, Event, State) ->
ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
+user_hello(enter, _, State) ->
+ {keep_state, State};
user_hello({call, From}, cancel, State) ->
gen_statem:reply(From, ok),
ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
?FUNCTION_NAME, State);
user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{static_env = #static_env{role = Role},
- handshake_env = #handshake_env{hello = Hello},
+ #state{static_env = #static_env{role = client = Role},
+ handshake_env = HSEnv,
+ ssl_options = Options0} = State0) ->
+ Options = ssl:handle_options(NewOptions, Role, Options0),
+ State = ssl_gen_statem:ssl_config(Options, Role, State0),
+ {next_state, wait_sh, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}},
+ [{{timeout, handshake}, Timeout, close}]};
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{static_env = #static_env{role = server = Role},
+ handshake_env = #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv,
ssl_options = Options0} = State0) ->
- Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
+ Options = #{versions := Versions} = ssl:handle_options(NewOptions, Role, Options0),
State = ssl_gen_statem:ssl_config(Options, Role, State0),
- Next = case Role of
- client ->
- wait_sh;
- server ->
- start
- end,
- {next_state, Next, State#state{start_or_recv_from = From},
- [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
+ case ssl_handshake:select_supported_version(ClientVersions, Versions) of
+ {3,4} ->
+ {next_state, start, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}},
+ [{{timeout, handshake}, Timeout, close}]};
+ undefined ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State);
+ _Else ->
+ {next_state, hello, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}},
+ [{change_callback_module, tls_connection},
+ {{timeout, handshake}, Timeout, close}]}
+ end;
user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
user_hello(_, _, _) ->
{keep_state_and_data, [postpone]}.
-start(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
-start(internal, #client_hello{extensions = Extensions} = Hello,
- #state{ssl_options = #{handshake := hello},
- start_or_recv_from = From,
- handshake_env = HsEnv} = State) ->
+start(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+start(internal = Type, #change_cipher_spec{} = Msg,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) ->
+ case ssl_handshake:init_handshake_history() of
+ Hist -> %% First message must always be client hello
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State);
+ _ ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State)
+ end;
+start(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+start(internal, #client_hello{extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }} = Hello,
+ #state{ssl_options = #{handshake := full}} = State) ->
+ case tls_record:is_acceptable_version({3,4}, ClientVersions) of
+ true ->
+ do_server_start(Hello, State);
+ false ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
+ end;
+start(internal, #client_hello{extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }= Extensions},
+ #state{start_or_recv_from = From,
+ handshake_env = #handshake_env{continue_status = pause} = HSEnv} = State) ->
{next_state, user_hello,
- State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{
- hello = Hello}}, [{reply, From, {ok, Extensions}}]};
-start(internal, #client_hello{} = Hello, State0) ->
- case tls_handshake_1_3:do_start(Hello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, start, State0);
- {State, start} ->
- {next_state, start, State, []};
- {State, negotiated} ->
- {next_state, negotiated, State, [{next_event, internal, {start_handshake, undefined}}]};
- {State, negotiated, PSK} -> %% Session Resumption with PSK
- {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
+ State#state{start_or_recv_from = undefined, handshake_env = HSEnv#handshake_env{continue_status = {pause, ClientVersions}}},
+ [{postpone, true}, {reply, From, {ok, Extensions}}]};
+start(internal, #client_hello{} = Hello,
+ #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
+ do_server_start(Hello, State);
+start(internal, #client_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello.
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
+start(internal, #server_hello{extensions = #{server_hello_selected_version :=
+ #server_hello_selected_version{selected_version = Version}}} = ServerHello,
+ #state{ssl_options = #{handshake := full,
+ versions := SupportedVersions}} = State) ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ do_client_start(ServerHello, State);
+ false ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
end;
-start(internal, #server_hello{extensions = Extensions} = ServerHello,
- #state{ssl_options = #{handshake := hello},
- handshake_env = HsEnv,
- start_or_recv_from = From}
+start(internal, #server_hello{extensions = #{server_hello_selected_version :=
+ #server_hello_selected_version{selected_version = Version}}
+ = Extensions},
+ #state{ssl_options = #{versions := SupportedVersions},
+ start_or_recv_from = From,
+ handshake_env = #handshake_env{continue_status = pause}}
= State) ->
- {next_state, user_hello,
- State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{
- hello = ServerHello}}, [{reply, From, {ok, Extensions}}]};
-start(internal, #server_hello{} = ServerHello, State0) ->
- case tls_handshake_1_3:do_start(ServerHello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, start, State0);
- {State, NextState} ->
- {next_state, NextState, State, []}
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined}, [{postpone, true}, {reply, From, {ok, Extensions}}]};
+ false ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
end;
+start(internal, #server_hello{} = ServerHello,
+ #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
+ do_client_start(ServerHello, State);
+start(internal, #server_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello.
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
start(info, Msg, State) ->
tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
start(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-negotiated(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+negotiated(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+negotiated(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
negotiated(internal, Message, State0) ->
case tls_handshake_1_3:do_negotiated(Message, State0) of
#alert{} = Alert ->
@@ -309,8 +362,11 @@ negotiated(internal, Message, State0) ->
negotiated(info, Msg, State) ->
tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State).
-wait_cert(internal, #change_cipher_spec{}, State0) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0);
+wait_cert(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cert(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
wait_cert(internal,
#certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
@@ -324,8 +380,11 @@ wait_cert(info, Msg, State) ->
wait_cert(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_cv(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cv(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cv(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
wait_cv(internal,
#certificate_verify_1_3{} = CertificateVerify, State0) ->
case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
@@ -339,8 +398,11 @@ wait_cv(info, Msg, State) ->
wait_cv(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_finished(internal, #change_cipher_spec{}, State0) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0);
+wait_finished(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
wait_finished(internal,
#finished{} = Finished, State0) ->
case tls_handshake_1_3:do_wait_finished(Finished, State0) of
@@ -356,34 +418,71 @@ wait_finished(info, Msg, State) ->
wait_finished(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_sh(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
-wait_sh(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello},
- start_or_recv_from = From,
- handshake_env = HsEnv} = State) ->
+wait_sh(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_sh(internal, #server_hello{extensions = Extensions},
+ #state{handshake_env = #handshake_env{continue_status = pause},
+ start_or_recv_from = From} = State) ->
{next_state, user_hello,
- State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{
- hello = Hello}}, [{reply, From, {ok, Extensions}}]};
-wait_sh(internal, #server_hello{} = Hello, State0) ->
+ State#state{start_or_recv_from = undefined}, [{postpone, true},{reply, From, {ok, Extensions}}]};
+wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello, #state{session = #session{session_id = ?EMPTY_ID},
+ ssl_options = #{middlebox_comp_mode := false}} = State0) ->
case tls_handshake_1_3:do_wait_sh(Hello, State0) of
- #alert{} = Alert ->
+ #alert{} = Alert ->
ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
{State1, start, ServerHello} ->
%% hello_retry_request: go to start
- {next_state, start, State1, [{next_event, internal, ServerHello}]};
+ {next_state, start, State1, [{next_event, internal, ServerHello}]};
{State1, wait_ee} ->
- tls_gen_connection:next_event(wait_ee, no_record, State1)
+ tls_gen_connection:next_event(wait_ee, no_record, State1)
+ end;
+wait_sh(internal, #server_hello{} = Hello,
+ #state{protocol_specific = PS, ssl_options = #{middlebox_comp_mode := true}} = State0) ->
+ IsRetry = maps:get(hello_retry, PS, false),
+ case tls_handshake_1_3:do_wait_sh(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
+ {State1 = #state{}, start, ServerHello} ->
+ %% hello_retry_request: go to start
+ {next_state, start, State1, [{next_event, internal, ServerHello}]};
+ {State1, wait_ee} when IsRetry == true ->
+ tls_gen_connection:next_event(wait_ee, no_record, State1);
+ {State1, wait_ee} when IsRetry == false ->
+ tls_gen_connection:next_event(hello_middlebox_assert, no_record, State1)
end;
wait_sh(info, Msg, State) ->
tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
wait_sh(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+hello_middlebox_assert(enter, _, State) ->
+ {keep_state, State};
+hello_middlebox_assert(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(wait_ee, no_record, State);
+hello_middlebox_assert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+hello_middlebox_assert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_ee(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+hello_retry_middlebox_assert(enter, _, State) ->
+ {keep_state, State};
+hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(wait_sh, no_record, State);
+hello_retry_middlebox_assert(internal, #server_hello{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, [postpone]);
+hello_retry_middlebox_assert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+hello_retry_middlebox_assert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+wait_ee(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
wait_ee(internal, #encrypted_extensions{} = EE, State0) ->
case tls_handshake_1_3:do_wait_ee(EE, State0) of
#alert{} = Alert ->
@@ -396,9 +495,12 @@ wait_ee(info, Msg, State) ->
wait_ee(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_cert_cr(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert_cr(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg,
+ State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of
{#alert{} = Alert, State} ->
@@ -418,8 +520,11 @@ wait_cert_cr(info, Msg, State) ->
wait_cert_cr(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_eoed(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_eoed(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_eoed(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
wait_eoed(internal, #end_of_early_data{} = EOED, State0) ->
case tls_handshake_1_3:do_wait_eoed(EOED, State0) of
{#alert{} = Alert, State} ->
@@ -432,6 +537,8 @@ wait_eoed(info, Msg, State) ->
wait_eoed(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+connection(enter, _, State) ->
+ {keep_state, State};
connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
handle_new_session_ticket(NewSessionTicket, State),
tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
@@ -455,6 +562,8 @@ connection({call, From}, negotiated_protocol,
connection(Type, Event, State) ->
ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+downgrade(enter, _, State) ->
+ {keep_state, State};
downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) ->
_ = handle_new_session_ticket(NewSessionTicket, State),
{next_state, ?FUNCTION_NAME, State};
@@ -464,8 +573,30 @@ downgrade(Type, Event, State) ->
%--------------------------------------------------------------------
%% internal functions
%%--------------------------------------------------------------------
+
+do_server_start(ClientHello, State0) ->
+ case tls_handshake_1_3:do_start(ClientHello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, start, State0);
+ {State, start} ->
+ {next_state, start, State, []};
+ {State, negotiated} ->
+ {next_state, negotiated, State, [{next_event, internal, {start_handshake, undefined}}]};
+ {State, negotiated, PSK} -> %% Session Resumption with PSK
+ {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
+ end.
+
+do_client_start(ServerHello, State0) ->
+ case tls_handshake_1_3:do_start(ServerHello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, start, State0);
+ {State, NextState} ->
+ {next_state, NextState, State, []}
+ end.
+
initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ put(log_level, maps:get(log_level, SSLOptions)),
#{erl_dist := IsErlDist,
%% Use highest supported version for client/server random nonce generation
versions := [Version|_],
@@ -500,7 +631,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
socket_options = SocketOptions,
ssl_options = SSLOptions,
session = #session{is_resumable = false,
- session_id = ssl_session:legacy_session_id()},
+ session_id = ssl_session:legacy_session_id(SSLOptions)},
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
user_data_buffer = {[],0,[]},
@@ -560,7 +691,7 @@ handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSession
tls_client_ticket_store:store_ticket(NewSessionTicket, {Cipher, HKDF}, SNI, PSK).
send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) ->
- Timestamp = erlang:system_time(seconds),
+ Timestamp = erlang:system_time(millisecond),
TicketData = #{cipher_suite => CipherSuite,
sni => SNI,
psk => PSK,
@@ -591,3 +722,19 @@ init_max_early_data_size(client) ->
init_max_early_data_size(server) ->
ssl_config:get_max_early_data_size().
+handle_middlebox(#state{protocol_specific = PS} = State0) ->
+ %% Always be prepared to ignore one change cipher spec
+ %% for maximum interopablility, even if middlebox mode
+ %% is not enabled.
+ State0#state{protocol_specific = PS#{change_cipher_spec => ignore}}.
+
+
+handle_change_cipher_spec(Type, Msg, StateName, #state{protocol_specific = PS0} = State) ->
+ case maps:get(change_cipher_spec, PS0) of
+ ignore ->
+ PS = PS0#{change_cipher_spec => fail},
+ tls_gen_connection:next_event(StateName, no_record,
+ State#state{protocol_specific = PS});
+ fail ->
+ ssl_gen_statem:handle_common_event(Type, Msg, StateName, State)
+ end.
diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl
index 9f0bf8132e..6882e8af34 100644
--- a/lib/ssl/src/tls_dtls_connection.erl
+++ b/lib/ssl/src/tls_dtls_connection.erl
@@ -56,6 +56,7 @@
user_hello/3,
abbreviated/3,
certify/3,
+ wait_cert_verify/3,
wait_ocsp_stapling/3,
cipher/3,
connection/3,
@@ -107,7 +108,7 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
handle_session(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression},
Version, NewId, ConnectionStates, ProtoExt, Protocol0,
- #state{session = #session{session_id = OldId},
+ #state{session = Session,
handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv,
connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) ->
#{key_exchange := KeyAlgorithm} =
@@ -128,7 +129,7 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
negotiated_protocol = Protocol},
connection_env = CEnv#connection_env{negotiated_version = Version}},
- case ssl_session:is_new(OldId, NewId) of
+ case ssl_session:is_new(Session, NewId) of
true ->
handle_new_session(NewId, CipherSuite, Compression,
State#state{connection_states = ConnectionStates});
@@ -168,12 +169,14 @@ user_hello({call, From}, cancel, _State) ->
throw(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled));
user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
#state{static_env = #static_env{role = Role},
- handshake_env = #handshake_env{hello = Hello},
+ handshake_env = HSEnv,
ssl_options = Options0} = State0) ->
- Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
+ Options = ssl:handle_options(NewOptions, Role, Options0),
State = ssl_gen_statem:ssl_config(Options, Role, State0),
- {next_state, hello, State#state{start_or_recv_from = From},
- [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
+ {next_state, hello, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}
+ },
+ [{{timeout, handshake}, Timeout, close}]};
user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
user_hello(_, _, _) ->
@@ -316,7 +319,7 @@ certify(internal, #certificate{asn1_certificates = []},
ssl_options = #{verify := verify_peer,
fail_if_no_peer_cert := false}} =
State0) ->
- Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
+ Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_status = empty});
certify(internal, #certificate{},
#state{static_env = #static_env{role = server},
ssl_options = #{verify := verify_none}}) ->
@@ -338,14 +341,19 @@ certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert,
ocsp_stapling_state = #{ocsp_expect := Status} = OcspState},
connection_env = #connection_env{
negotiated_version = Version},
- ssl_options = Opts} = State) when Status =/= staple ->
+ ssl_options = Opts} = State0) when Status =/= staple ->
OcspInfo = ocsp_info(OcspState, Opts, Peer),
case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
Opts, CRLDbInfo, Role, Host,
ensure_tls(Version), OcspInfo) of
{PeerCert, PublicKeyInfo} ->
- handle_peer_cert(Role, PeerCert, PublicKeyInfo,
- State#state{client_certificate_requested = false}, Connection, []);
+ State = case Role of
+ server ->
+ State0#state{client_certificate_status = needs_verifying};
+ client ->
+ State0
+ end,
+ handle_peer_cert(Role, PeerCert, PublicKeyInfo, State, Connection, []);
#alert{} = Alert ->
throw(Alert)
end;
@@ -409,11 +417,11 @@ certify(internal, #certificate_request{},
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
session = Session0,
- connection_env = #connection_env{cert_key_pairs = [#{certs := [[]]}]}} = State) ->
+ connection_env = #connection_env{cert_key_alts = [#{certs := [[]]}]}} = State) ->
%% The client does not have a certificate and will send an empty reply, the server may fail
%% or accept the connection by its own preference. No signature algorithms needed as there is
%% no certificate to verify.
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true,
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_status = requested,
session = Session0#session{own_certificates = [[]],
private_key = #{}}});
certify(internal, #certificate_request{} = CertRequest,
@@ -422,16 +430,17 @@ certify(internal, #certificate_request{} = CertRequest,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
connection_env = #connection_env{negotiated_version = Version,
- cert_key_pairs = CertKeyPairs
+ cert_key_alts = CertKeyAlts
},
session = Session0,
ssl_options = #{signature_algs := SupportedHashSigns}} = State) ->
TLSVersion = ssl:tls_version(Version),
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, ssl:tls_version(Version)),
Session = select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs,
SupportedHashSigns, TLSVersion,
CertDbHandle, CertDbRef),
Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{client_certificate_requested = true,
+ State#state{client_certificate_status = requested,
session = Session});
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(internal, #server_hello_done{},
@@ -510,7 +519,7 @@ certify(internal, #server_hello_done{},
end;
certify(internal = Type, #client_key_exchange{} = Msg,
#state{static_env = #static_env{role = server},
- client_certificate_requested = true,
+ client_certificate_status = requested,
ssl_options = #{fail_if_no_peer_cert := true}}) ->
%% We expect a certificate here
throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}));
@@ -531,25 +540,22 @@ certify(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
--spec cipher(gen_statem:event_type(),
- #hello_request{} | #certificate_verify{} | #finished{} | term(),
+-spec wait_cert_verify(gen_statem:event_type(),
+ #hello_request{} | #certificate_verify{} | term(),
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-cipher({call, From}, Msg, State) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State);
-cipher(info, Msg, State) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-cipher(internal, #certificate_verify{signature = Signature,
- hashsign_algorithm = CertHashSign},
- #state{static_env = #static_env{role = server,
- protocol_cb = Connection},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- kex_algorithm = KexAlg,
+wait_cert_verify(internal, #certificate_verify{signature = Signature,
+ hashsign_algorithm = CertHashSign},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ client_certificate_status = needs_verifying,
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ kex_algorithm = KexAlg,
public_key_info = PubKeyInfo},
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret} = Session0
- } = State) ->
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret} = Session0
+ } = State) ->
TLSVersion = ssl:tls_version(Version),
%% Use negotiated value if TLS-1.2 otherwise return default
@@ -557,17 +563,27 @@ cipher(internal, #certificate_verify{signature = Signature,
case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
TLSVersion, HashSign, MasterSecret, Hist) of
valid ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{session = Session0#session{sign_alg = HashSign}});
+ Connection:next_event(cipher, no_record,
+ State#state{client_certificate_status = verified,
+ session = Session0#session{sign_alg = HashSign}});
#alert{} = Alert ->
throw(Alert)
end;
-%% client must send a next protocol message if we are expecting it
-cipher(internal, #finished{},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_next_protocol_negotiation = true,
- negotiated_protocol = undefined}}) ->
- throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE));
+wait_cert_verify(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+wait_cert_verify(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec cipher(gen_statem:event_type(),
+ #hello_request{} | #finished{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+cipher({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+cipher(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
cipher(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = Role,
host = Host,
@@ -670,7 +686,8 @@ downgrade(Type, Event, State) ->
gen_handshake(StateName, Type, Event, State) ->
try
tls_dtls_connection:StateName(Type, Event, State)
- catch error:_ ->
+ catch error:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data))
end.
@@ -702,8 +719,12 @@ handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
end, [], Seed)),
ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
catch
- exit:_ -> {error, badarg};
- error:Reason -> {error, Reason}
+ exit:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
+ {error, badarg};
+ error:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
+ {error, Reason}
end,
{keep_state_and_data, [{reply, From, Reply}]};
handle_call(Msg, From, StateName, State) ->
@@ -731,6 +752,7 @@ do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
ServerHello =
ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
ConnectionStates1, ServerHelloExt),
+
State = server_hello(ServerHello,
State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
NextProtocols =/= undefined}}, Connection),
@@ -862,18 +884,18 @@ handle_peer_cert_key(_, _, _, _, State) ->
certify_client(#state{static_env = #static_env{role = client,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
- client_certificate_requested = true,
+ client_certificate_status = requested,
session = #session{own_certificates = OwnCerts}}
= State, Connection) ->
Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client),
Connection:queue_handshake(Certificate, State);
-certify_client(#state{client_certificate_requested = false} = State, _) ->
+certify_client(#state{client_certificate_status = not_requested} = State, _) ->
State.
verify_client_cert(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{tls_handshake_history = Hist},
connection_env = #connection_env{negotiated_version = Version},
- client_certificate_requested = true,
+ client_certificate_status = requested,
session = #session{sign_alg = HashSign,
master_secret = MasterSecret,
private_key = PrivateKey,
@@ -887,13 +909,13 @@ verify_client_cert(#state{static_env = #static_env{role = client},
#alert{} = Alert ->
throw(Alert)
end;
-verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
+verify_client_cert(#state{client_certificate_status = not_requested} = State, _) ->
State.
client_certify_and_key_exchange(State0, Connection) ->
State1 = do_client_certify_and_key_exchange(State0, Connection),
{State2, Actions} = finalize_handshake(State1, certify, Connection),
- State = State2#state{client_certificate_requested = false}, %% Reinitialize
+ State = State2#state{client_certificate_status = not_requested}, %% Reinitialize
Connection:next_event(cipher, no_record, State, Actions).
do_client_certify_and_key_exchange(State0, Connection) ->
@@ -908,7 +930,8 @@ server_certify_and_key_exchange(State0, Connection) ->
certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
#state{session = #session{private_key = PrivateKey},
- handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
+ handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version},
+ client_certificate_status = CCStatus}
= State, Connection) ->
FakeSecret = make_premaster_secret(Version, rsa),
%% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
@@ -928,55 +951,74 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS
#alert{description = ?DECRYPT_ERROR} ->
FakeSecret
end,
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+ calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
#state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
- kex_keys = {_, ServerDhPrivateKey}}
+ kex_keys = {_, ServerDhPrivateKey}},
+ client_certificate_status = CCStatus
} = State,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+ calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
- #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) ->
+ #state{handshake_env = #handshake_env{kex_keys = ECDHKey},
+ client_certificate_status = CCStatus
+ } = State, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+ calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_psk_identity{} = ClientKey,
#state{ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
+ #{user_lookup_fun := PSKLookup},
+ client_certificate_status = CCStatus
+ } = State0,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
#state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
kex_keys = {_, ServerDhPrivateKey}},
ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
+ #{user_lookup_fun := PSKLookup},
+ client_certificate_status = CCStatus
+ } = State0,
Connection) ->
PremasterSecret =
ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
#state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey},
ssl_options =
- #{user_lookup_fun := PSKLookup}} = State,
+ #{user_lookup_fun := PSKLookup},
+ client_certificate_status = CCStatus
+ } = State,
Connection) ->
PremasterSecret =
ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+ calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
#state{session = #session{private_key = PrivateKey},
ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
+ #{user_lookup_fun := PSKLookup},
+ client_certificate_status = CCStatus
+ } = State0,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PrivateKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_srp_public{} = ClientKey,
#state{handshake_env = #handshake_env{srp_params = Params,
- kex_keys = Key}
+ kex_keys = Key},
+ client_certificate_status = CCStatus
} = State0, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)).
+
+client_kex_next_state(needs_verifying) ->
+ wait_cert_verify;
+client_kex_next_state(empty) ->
+ cipher;
+client_kex_next_state(not_requested) ->
+ cipher.
certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} =
State, _) when KexAlg == dh_anon;
@@ -1285,15 +1327,15 @@ request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}}
request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{verify := verify_peer,
- signature_algs := SupportedHashSigns}} = State0, Connection) ->
+ ssl_options = #{verify := verify_peer} = Opts} = State0, Connection) ->
+ SupportedHashSigns = maps:get(signature_algs, Opts, undefined),
TLSVersion = ssl:tls_version(Version),
HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns,
TLSVersion),
Msg = ssl_handshake:certificate_request(CertDbHandle, CertDbRef,
HashSigns, TLSVersion),
State = Connection:queue_handshake(Msg, State0),
- State#state{client_certificate_requested = true};
+ State#state{client_certificate_status = requested};
request_client_cert(#state{ssl_options = #{verify := verify_none}} =
State, _) ->
@@ -1447,22 +1489,20 @@ generate_srp_server_keys(_SrpParams, 10) ->
generate_srp_server_keys(SrpParams =
#srp_user{generator = Generator, prime = Prime,
verifier = Verifier}, N) ->
- try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
- Keys ->
- Keys
+ try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']})
catch
- error:_ ->
+ error:Reason:ST ->
+ ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]),
generate_srp_server_keys(SrpParams, N+1)
end.
generate_srp_client_keys(_Generator, _Prime, 10) ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
generate_srp_client_keys(Generator, Prime, N) ->
- try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
- Keys ->
- Keys
+ try crypto:generate_key(srp, {user, [Generator, Prime, '6a']})
catch
- error:_ ->
+ error:Reason:ST ->
+ ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]),
generate_srp_client_keys(Generator, Prime, N+1)
end.
@@ -1651,7 +1691,7 @@ select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSi
select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, undefined).
select_client_cert_key_pair(Session0,_,[], _, _,_,_, undefined) ->
- %% No certificate compliant with signing algorithms found: empty certificate will be sent
+ %% No certificate compliant with supported algorithms: empty certificate will be sent
Session0#session{own_certificates = [[]],
private_key = #{}};
select_client_cert_key_pair(_,_,[], _, _,_,_,#session{}=Session) ->
@@ -1661,7 +1701,7 @@ select_client_cert_key_pair(Session0, #certificate_request{certificate_authoriti
[#{private_key := PrivateKey, certs := [Cert| _] = Certs} | Rest],
SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default) ->
case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, TLSVersion) of
- #alert {} ->
+ #alert{} ->
select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default);
SelectedHashSign ->
case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
index cff0abdcb3..940666f104 100644
--- a/lib/ssl/src/tls_gen_connection.erl
+++ b/lib/ssl/src/tls_gen_connection.erl
@@ -70,42 +70,70 @@
close/4,
protocol_name/0]).
--define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]).
-
%%====================================================================
%% Internal application API
-%%====================================================================
+%%====================================================================
%%====================================================================
%% Setup
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts,
- User, {CbModule, _,_, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, DynSup} = tls_connection_sup:start_child([]),
- {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, []),
- {ok, Pid} = tls_dyn_connection_sup:start_child(DynSup, receiver, [Role, Sender, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_gen_statem:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end;
+start_fsm(Role, Host, Port, Socket,
+ {#{erl_dist := ErlDist, sender_spawn_opts := SenderSpawnOpts}, _, Trackers} = Opts,
+ User, {CbModule, _, _, _, _} = CbInfo,
+ Timeout) ->
+ SenderOptions = handle_sender_options(ErlDist, SenderSpawnOpts),
+ Starter = start_connection_tree(User, ErlDist, SenderOptions,
+ Role, [Host, Port, Socket, Opts, User, CbInfo]),
+ receive
+ {Starter, SockReceiver, SockSender} ->
+ socket_control(Socket, SockReceiver, SockSender, CbModule, Trackers, Timeout);
+ {Starter, Error} ->
+ Error
+ end.
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts,
- User, {CbModule, _,_, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, DynSup} = tls_connection_sup:start_child_dist([]),
- {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, [[{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]]),
- {ok, Pid} = tls_dyn_connection_sup:start_child(DynSup, receiver, [Role, Sender, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_gen_statem:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
+handle_sender_options(ErlDist, SpawnOpts) ->
+ case ErlDist of
+ true ->
+ [[{spawn_opt, [{priority, max} | proplists:delete(priority, SpawnOpts)]}]];
+ false ->
+ [[{spawn_opt, SpawnOpts}]]
+ end.
+
+start_connection_tree(User, IsErlDist, SenderOpts, Role, ReceiverOpts) ->
+ StartConnectionTree =
+ fun() ->
+ case start_dyn_connection_sup(IsErlDist) of
+ {ok, DynSup} ->
+ case tls_dyn_connection_sup:start_child(DynSup, sender, SenderOpts) of
+ {ok, Sender} ->
+ case tls_dyn_connection_sup:start_child(DynSup, receiver,
+ [Role, Sender | ReceiverOpts]) of
+ {ok, Receiver} ->
+ User ! {self(), Receiver, Sender};
+ {error, Error} ->
+ User ! {self(), Error},
+ exit(DynSup, shutdown)
+ end;
+ {error, Error} ->
+ User ! {self(), Error},
+ exit(DynSup, shutdown)
+ end;
+ {error, Error} ->
+ User ! {self(), Error}
+ end
+ end,
+ spawn(StartConnectionTree).
+
+start_dyn_connection_sup(true) ->
+ tls_connection_sup:start_child_dist([]);
+start_dyn_connection_sup(false) ->
+ tls_connection_sup:start_child([]).
+
+socket_control(Socket, SockReceiver, SockSender, CbModule, Trackers, Timeout) ->
+ case ssl_gen_statem:socket_control(?MODULE, Socket, [SockReceiver, SockSender], CbModule, Trackers) of
+ {ok, SslSocket} ->
+ ssl_gen_statem:handshake(SslSocket, Timeout);
+ Error ->
+ Error
end.
pids(#state{protocol_specific = #{sender := Sender}}) ->
@@ -118,24 +146,26 @@ initialize_tls_sender(#state{static_env = #static_env{
trackers = Trackers
},
connection_env = #connection_env{negotiated_version = Version},
- socket_options = SockOpts,
+ socket_options = SockOpts,
ssl_options = #{renegotiate_at := RenegotiateAt,
key_update_at := KeyUpdateAt,
erl_dist := ErlDist,
- log_level := LogLevel},
+ log_level := LogLevel,
+ hibernate_after := HibernateAfter},
connection_states = #{current_write := ConnectionWriteState},
protocol_specific = #{sender := Sender}}) ->
Init = #{current_write => ConnectionWriteState,
role => Role,
socket => Socket,
socket_options => SockOpts,
- erl_dist => ErlDist,
+ erl_dist => ErlDist,
trackers => Trackers,
transport_cb => Transport,
negotiated_version => Version,
renegotiate_at => RenegotiateAt,
key_update_at => KeyUpdateAt,
- log_level => LogLevel},
+ log_level => LogLevel,
+ hibernate_after => HibernateAfter},
tls_sender:initialize(Sender, Init).
%%====================================================================
@@ -355,16 +385,28 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName,
} = State) when StateName == initial_hello;
StateName == hello;
StateName == certify;
+ StateName == wait_cert_verify;
+ StateName == wait_ocsp_stapling;
StateName == abbreviated;
StateName == cipher
->
%% Application data can not be sent before initial handshake pre TLS-1.3.
Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_initial_handshake),
ssl_gen_statem:handle_own_alert(Alert, StateName, State);
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, start = StateName,
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, early_data = false}, StateName,
#state{static_env = #static_env{role = server}
- } = State) ->
- Alert = ?ALERT_REC(?FATAL, ?DECODE_ERROR, invalid_tls_13_message),
+ } = State) when StateName == start;
+ StateName == recvd_ch;
+ StateName == negotiated;
+ StateName == wait_eoed ->
+ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, none_early_application_data_before_handshake),
+ ssl_gen_statem:handle_own_alert(Alert, StateName, State);
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName,
+ #state{static_env = #static_env{role = server}
+ } = State) when StateName == wait_cert;
+ StateName == wait_cv;
+ StateName == wait_finished->
+ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_handshake_or_intervened_in_post_handshake_auth),
ssl_gen_statem:handle_own_alert(Alert, StateName, State);
handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
#state{start_or_recv_from = From,
@@ -389,26 +431,17 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat
next_event(StateName, Record, State)
end;
%%% TLS record protocol level handshake messages
-handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
- StateName, #state{protocol_buffers =
- #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
- connection_env = #connection_env{negotiated_version = Version},
- static_env = #static_env{role = Role},
- ssl_options = Options} = State0) ->
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName,
+ #state{ssl_options = Options, protocol_buffers = Buffers} = State0) ->
try
- %% Calculate the effective version that should be used when decoding an incoming handshake
- %% message.
- EffectiveVersion = effective_version(Version, Options, Role, StateName),
- {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
- State =
- State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
- case Packets of
+ {HSPackets, NewHSBuffer, RecordRest} = get_tls_handshakes(Data, StateName, State0),
+ State = State0#state{protocol_buffers = Buffers#protocol_buffers{tls_handshake_buffer = NewHSBuffer}},
+ case HSPackets of
[] ->
- assert_buffer_sanity(Buf, Options),
+ assert_buffer_sanity(NewHSBuffer, Options),
next_event(StateName, no_record, State);
_ ->
- Events = tls_handshake_events(Packets),
+ Events = tls_handshake_events(HSPackets, RecordRest),
case StateName of
connection ->
ssl_gen_statem:hibernate_after(StateName, State, Events);
@@ -437,7 +470,8 @@ handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
#alert{} = Alert ->
ssl_gen_statem:handle_own_alert(Alert, 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, alert_decode_error),
StateName, State)
@@ -494,7 +528,8 @@ send_sync_alert(
Alert, #state{protocol_specific = #{sender := Sender}} = State) ->
try tls_sender:send_and_ack_alert(Sender, Alert)
catch
- _:_ ->
+ _:Reason:ST ->
+ ?SSL_LOG(info, "Send failed", [{error, Reason}, {stacktrace, ST}]),
throw({stop, {shutdown, own_alert}, State})
end.
@@ -528,10 +563,30 @@ protocol_name() ->
%%====================================================================
%% Internal functions
%%====================================================================
-tls_handshake_events(Packets) ->
- lists:map(fun(Packet) ->
- {next_event, internal, {handshake, Packet}}
- end, Packets).
+get_tls_handshakes(Data, StateName, #state{protocol_buffers = #protocol_buffers{tls_handshake_buffer = HSBuffer},
+ connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{role = Role},
+ ssl_options = Options}) ->
+ case handle_unnegotiated_version(Version, Options, Data, HSBuffer, Role, StateName) of
+ {HSPackets, NewHSBuffer} ->
+ %% Common case
+ NoRecordRest = <<>>,
+ {HSPackets, NewHSBuffer, NoRecordRest};
+ {_Packets, _HSBuffer, _RecordRest} = Result ->
+ %% Possible coalesced TLS record data from pre TLS-1.3 server
+ Result
+ end.
+
+tls_handshake_events(HSPackets, <<>>) ->
+ lists:map(fun(HSPacket) ->
+ {next_event, internal, {handshake, HSPacket}}
+ end, HSPackets);
+
+tls_handshake_events(HSPackets, RecordRest) ->
+ %% Coalesced TLS record data to be handled after first handshake message has been handled
+ RestEvent = {next_event, internal, {protocol_record, #ssl_tls{type = ?HANDSHAKE, fragment = RecordRest}}},
+ FirstHS = tls_handshake_events(HSPackets, <<>>),
+ FirstHS ++ [RestEvent].
unprocessed_events(Events) ->
%% The first handshake event will be processed immediately
@@ -675,32 +730,29 @@ activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n :
%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one
%%
next_record(State, CipherTexts, ConnectionStates, Check) ->
- next_record(State, CipherTexts, ConnectionStates, Check, []).
+ next_record(State, CipherTexts, ConnectionStates, Check, [], false).
%%
next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State,
- [CT|CipherTexts], ConnectionStates0, Check, Acc) ->
+ [CT|CipherTexts], ConnectionStates0, Check, Acc, IsEarlyData) ->
case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
+ {Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
case CipherTexts of
[] ->
%% End of cipher texts - build and deliver an ?APPLICATION_DATA record
%% from the accumulated fragments
next_record_done(State, [], ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
+ Record#ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
[_|_] ->
- next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
+ next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc], Record#ssl_tls.early_data)
end;
- {trial_decryption_failed, ConnectionStates} ->
+ {no_record, ConnectionStates} ->
case CipherTexts of
[] ->
- %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
- %% from the accumulated fragments
- next_record_done(State, [], ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc))});
+ Record = accumulated_app_record(Acc, IsEarlyData),
+ next_record_done(State, [], ConnectionStates, Record);
[_|_] ->
- next_record(State, CipherTexts, ConnectionStates, Check, Acc)
+ next_record(State, CipherTexts, ConnectionStates, Check, Acc, IsEarlyData)
end;
{Record, ConnectionStates} when Acc =:= [] ->
%% Singleton non-?APPLICATION_DATA record - deliver
@@ -711,33 +763,36 @@ next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} =
%% and forget about decrypting this record - we'll decrypt it again next time
%% Will not work for stream ciphers
next_record_done(State, [CT|CipherTexts], ConnectionStates0,
- #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))});
+ #ssl_tls{type = ?APPLICATION_DATA,
+ early_data = IsEarlyData,
+ fragment = iolist_to_binary(lists:reverse(Acc))});
#alert{} = Alert ->
Alert
end;
next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
- [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) ->
+ [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc, NotRelevant) ->
case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
+ {Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
case CipherTexts of
[] ->
%% End of cipher texts - build and deliver an ?APPLICATION_DATA record
%% from the accumulated fragments
next_record_done(State, [], ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
+ Record#ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
[_|_] ->
- next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
+ next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc], NotRelevant)
end;
#alert{} = Alert ->
Alert
end;
-next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) ->
+next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc, IsEarlyData) ->
next_record_done(State, CipherTexts, ConnectionStates,
#ssl_tls{type = ?APPLICATION_DATA,
+ early_data = IsEarlyData,
fragment = iolist_to_binary(lists:reverse(Acc))});
next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
- [CT|CipherTexts], ConnectionStates0, Check, []) ->
+ [CT|CipherTexts], ConnectionStates0, Check, [], _) ->
case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
{Record, ConnectionStates} ->
%% Singleton non-?APPLICATION_DATA record - deliver
@@ -746,6 +801,13 @@ next_record(#state{connection_env = #connection_env{negotiated_version = Version
Alert
end.
+accumulated_app_record([], _) ->
+ no_record;
+accumulated_app_record([_|_] = Acc, IsEarlyData) ->
+ #ssl_tls{type = ?APPLICATION_DATA,
+ early_data = IsEarlyData,
+ fragment = iolist_to_binary(lists:reverse(Acc))}.
+
next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) ->
{Record,
State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
@@ -755,21 +817,27 @@ next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, Connec
%% Pre TLS-1.3, on the client side, the connection state variable `negotiated_version` will initially be
%% the requested version. On the server side the same variable is initially undefined.
%% When the client can support TLS-1.3 and one or more prior versions and we are waiting
-%% for the server hello (with or without a RetryRequest, that is in state hello or in state wait_sh),
-%% the "initial requested version" kept in the connection state variable `negotiated_version`
+%% for the server hello the "initial requested version" kept in the connection state variable `negotiated_version`
%% (before the versions is actually negotiated) will always be the value of TLS-1.2 (which is a legacy
%% field in TLS-1.3 client hello). The versions are instead negotiated with an hello extension. When
%% decoding the server_hello messages we want to go through TLS-1.3 decode functions to be able
%% to handle TLS-1.3 extensions if TLS-1.3 will be the negotiated version.
-effective_version({3,3} , #{versions := [{3,4} = Version |_]}, client, StateName) when StateName == hello;
- StateName == wait_sh ->
- Version;
+handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, hello) ->
+ %% The effective version for decoding the server hello message should be the TLS-1.3. Possible coalesced TLS-1.2
+ %% server handshake messages should be decoded with the negotiated version in later state.
+ <<_:8, ?UINT24(Length), _/binary>> = Data,
+ <<FirstPacket:(Length+4)/binary, RecordRest/binary>> = Data,
+ {HSPacket, <<>> = NewHsBuffer} = tls_handshake:get_tls_handshakes(Version, FirstPacket, Buffer, Options),
+ {HSPacket, NewHsBuffer, RecordRest};
+%% TLS-1.3 RetryRequest
+handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, wait_sh) ->
+ tls_handshake:get_tls_handshakes(Version, Data, Buffer, Options);
%% When the `negotiated_version` variable is not yet set use the highest supported version.
-effective_version(undefined, #{versions := [Version|_]}, _, _) ->
- Version;
+handle_unnegotiated_version(undefined, #{versions := [Version|_]} = Options, Data, Buff, _, _) ->
+ tls_handshake:get_tls_handshakes(Version, Data, Buff, Options);
%% In all other cases use the version saved in the connection state variable `negotiated_version`
-effective_version(Version, _, _, _) ->
- Version.
+handle_unnegotiated_version(Version, Options, Data, Buff, _, _) ->
+ tls_handshake:get_tls_handshakes(Version, Data, Buff, Options).
assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>,
#{max_handshake_size := Max}) when
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 7a3d9712aa..7704ff7b6b 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -42,7 +42,7 @@
-export([encode_handshake/2]).
%% Handshake decoding
--export([get_tls_handshake/4, decode_handshake/3]).
+-export([get_tls_handshakes/4, decode_handshake/3]).
%% Handshake helper
-export([ocsp_nonce/2]).
@@ -245,7 +245,8 @@ hello(#client_hello{client_version = _ClientVersion,
Version = ssl_handshake:select_supported_version(ClientVersions, Versions),
do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation)
catch
- error:_ ->
+ error:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data))
end;
@@ -260,7 +261,8 @@ hello(#client_hello{client_version = ClientVersion,
error:{case_clause,{asn1, Asn1Reason}} ->
%% ASN-1 decode of certificate somehow failed
throw(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {failed_to_decode_own_certificate, Asn1Reason}));
- error:_ ->
+ error:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data))
end.
@@ -285,7 +287,7 @@ encode_handshake(Package, Version) ->
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist(),
+-spec get_tls_handshakes(tls_record:tls_version(), binary(), binary() | iolist(),
ssl_options()) ->
{[{tls_handshake(), binary()}], binary()}.
%%
@@ -293,10 +295,10 @@ encode_handshake(Package, Version) ->
%% and returns it as a list of handshake messages, also returns leftover
%% data.
%%--------------------------------------------------------------------
-get_tls_handshake(Version, Data, <<>>, Options) ->
- get_tls_handshake_aux(Version, Data, Options, []);
-get_tls_handshake(Version, Data, Buffer, Options) ->
- get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []).
+get_tls_handshakes(Version, Data, <<>>, Options) ->
+ get_tls_handshakes_aux(Version, Data, Options, []);
+get_tls_handshakes(Version, Data, Buffer, Options) ->
+ get_tls_handshakes_aux(Version, list_to_binary([Buffer, Data]), Options, []).
%%--------------------------------------------------------------------
%%% Handshake helper
@@ -330,8 +332,8 @@ handle_client_hello(Version,
true ->
SupportedHashSigns = maps:get(signature_algs, SslOpts, undefined),
Curves = maps:get(elliptic_curves, HelloExt, undefined),
- ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
- ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined),
+ ClientHashSigns = get_signature_ext(signature_algs, HelloExt, Version),
+ ClientSignatureSchemes = get_signature_ext(signature_algs_cert, HelloExt, Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
ClientHashSigns, SupportedHashSigns, Version),
ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, Version, ECCOrder),
@@ -357,7 +359,7 @@ handle_client_hello(Version,
CipherSuites, HelloExt,
SslOpts, Session1,
ConnectionStates0,
- Renegotiation, HashSign)
+ Renegotiation, HashSign)
end
end;
false ->
@@ -426,19 +428,20 @@ enc_handshake(HandshakeMsg, Version) ->
ssl_handshake:encode_handshake(HandshakeMsg, Version).
%%--------------------------------------------------------------------
-get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
+get_tls_handshakes_aux(Version, <<?BYTE(Type), ?UINT24(Length),
Body:Length/binary,Rest/binary>>,
#{log_level := LogLevel} = Opts, Acc) ->
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
try decode_handshake(Version, Type, Body) of
Handshake ->
ssl_logger:debug(LogLevel, inbound, 'handshake', Handshake),
- get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
+ get_tls_handshakes_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
catch
- error:_ ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, handshake_decode_error))
+ error:Reason:ST ->
+ ?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]),
+ throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, handshake_decode_error))
end;
-get_tls_handshake_aux(_Version, Data, _, Acc) ->
+get_tls_handshakes_aux(_Version, Data, _, Acc) ->
{lists:reverse(Acc), Data}.
decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 ->
@@ -470,3 +473,22 @@ ocsp_expect(true) ->
staple;
ocsp_expect(_) ->
no_staple.
+
+get_signature_ext(Ext, HelloExt, {3,3}) ->
+ case maps:get(Ext, HelloExt, undefined) of
+ %% Signature algorithms was not sent
+ undefined ->
+ undefined;
+ %% Can happen when connection is upgraded and sni_fun changes
+ %% the versions option from default
+ #signature_algorithms{signature_scheme_list = Schemes} ->
+ #hash_sign_algos{hash_sign_algos = ssl_cipher:signature_schemes_1_2(Schemes)};
+ #signature_algorithms_cert{} = Algos ->
+ Algos;
+ #hash_sign_algos{} = Algos ->
+ Algos
+ end;
+get_signature_ext(Ext, HelloExt, _) ->
+ maps:get(Ext, HelloExt, undefined).
+
+
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 84ee036a46..750a79887f 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -58,8 +58,7 @@
maybe_add_binders/4,
maybe_add_early_data_indication/3,
maybe_automatic_session_resumption/1,
- maybe_send_early_data/1,
- update_current_read/3]).
+ maybe_send_early_data/1]).
-export([get_max_early_data/1,
is_valid_binder/4,
@@ -205,18 +204,23 @@ encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
}.
-certificate_request(SignAlgs0, SignAlgsCert0, CertDbHandle, CertDbRef) ->
+certificate_request(SignAlgs0, SignAlgsCert0, CertDbHandle, CertDbRef, CertAuthBool) ->
%% Input arguments contain TLS 1.2 algorithms due to backward compatibility
%% reasons. These {Hash, Algo} tuples must be filtered before creating the
%% the extensions.
SignAlgs = filter_tls13_algs(SignAlgs0),
SignAlgsCert = filter_tls13_algs(SignAlgsCert0),
Extensions0 = add_signature_algorithms(#{}, SignAlgs),
- Extensions = add_signature_algorithms_cert(Extensions0, SignAlgsCert),
- Auths = ssl_handshake:certificate_authorities(CertDbHandle, CertDbRef),
+ Extensions1 = add_signature_algorithms_cert(Extensions0, SignAlgsCert),
+ Extensions = if CertAuthBool =:= true ->
+ Auths = ssl_handshake:certificate_authorities(CertDbHandle, CertDbRef),
+ Extensions1#{certificate_authorities => #certificate_authorities{authorities = Auths}};
+ true ->
+ Extensions1
+ end,
#certificate_request_1_3{
certificate_request_context = <<>>,
- extensions = Extensions#{certificate_authorities => #certificate_authorities{authorities = Auths}}}.
+ extensions = Extensions}.
add_signature_algorithms(Extensions, SignAlgs) ->
@@ -587,21 +591,21 @@ certificate_entry(DER) ->
%% 0101010101010101010101010101010101010101010101010101010101010101
sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) ->
Content = build_content(Context, THash),
- try ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo) of
- Signature ->
- {ok, Signature}
- catch
- error:badarg ->
- {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
+ try
+ {ok, ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo)}
+ catch throw:Alert ->
+ {error, Alert}
end.
+
verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) ->
Content = build_content(Context, THash),
try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of
Result ->
{ok, Result}
catch
- error:badarg ->
+ error:Reason:ST ->
+ ?SSL_LOG(debug, handshake_error, [{reason, Reason}, {stacktrace, ST}]),
{error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
end.
@@ -646,13 +650,13 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
maps:get(signature_algs, Extensions, undefined)),
ClientSignAlgsCert = get_signature_scheme_list(
maps:get(signature_algs_cert, Extensions, undefined)),
- CertAuths = get_certificate_authorites(maps:get(certificate_authorities, Extensions, undefined)),
+ CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
CookieExt = maps:get(cookie, Extensions, undefined),
Cookie = get_cookie(CookieExt),
#state{connection_states = ConnectionStates0,
session = Session0,
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs}} = State1 =
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts}} = State1 =
Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
Maybe(validate_cookie(Cookie, State1)),
@@ -667,6 +671,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
Maybe(validate_client_key_share(ClientGroups, ClientShares)),
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, {3,4}),
#session{own_certificates = [Cert|_]} = Session =
Maybe(select_server_cert_key_pair(Session0, CertKeyPairs, ClientSignAlgs,
ClientSignAlgsCert, CertAuths, State0,
@@ -746,6 +751,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
ocsp_stapling_state = OcspState},
connection_env = #connection_env{negotiated_version = NegotiatedVersion},
+ protocol_specific = PS,
ssl_options = #{ciphers := ClientCiphers,
supported_groups := ClientGroups0,
use_ticket := UseTicket,
@@ -819,8 +825,17 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory},
key_share = ClientKeyShare},
- {State, wait_sh}
-
+ %% If it is a hello_retry and middlebox mode is
+ %% used assert the change_cipher_spec message
+ %% that the server should send next
+ case (maps:get(hello_retry, PS, false)) andalso
+ (maps:get(middlebox_comp_mode, SslOpts, true))
+ of
+ true ->
+ {State, hello_retry_middlebox_assert};
+ false ->
+ {State, wait_sh}
+ end
catch
{Ref, #alert{} = Alert} ->
Alert
@@ -864,11 +879,11 @@ do_negotiated({start_handshake, PSK0},
ssl_record:step_encryption_state_write(State3);
false ->
%% Read state is overwritten when handshake secrets are set.
- %% Trial_decryption and early_data_limit must be set here!
+ %% 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 limit
+ false %% early_data_accepted
)
end,
@@ -902,7 +917,8 @@ do_negotiated({start_handshake, PSK0},
catch
{Ref, #alert{} = Alert} ->
Alert;
- error:badarg ->
+ error:badarg=Reason:ST ->
+ ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]),
?ALERT_REC(?ILLEGAL_PARAMETER, illegal_parameter_to_compute_key)
end.
@@ -1128,8 +1144,9 @@ do_wait_eoed(#end_of_early_data{}, State0) ->
%% Upon receiving a message with type server_hello, implementations MUST
%% first examine the Random value and, if it matches this value, process
%% it as described in Section 4.1.4).
-maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, State0) ->
- {error, {State0, start, ServerHello}};
+maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello,
+ #state{protocol_specific = PS} = State0) ->
+ {error, {State0#state{protocol_specific = PS#{hello_retry => true}}, start, ServerHello}};
maybe_hello_retry_request(_, _) ->
ok.
@@ -1180,11 +1197,10 @@ maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, l
%% first ClientHello.
%% @end
maybe_prepend_change_cipher_spec(#state{
- ssl_options =
- #{middlebox_comp_mode := true},
+ session = #session{session_id = Id},
handshake_env =
#handshake_env{
- change_cipher_spec_sent = false} = HSEnv} = State, Bin) ->
+ change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID ->
CCSBin = create_change_cipher_spec(State),
{State#state{handshake_env =
HSEnv#handshake_env{change_cipher_spec_sent = true}},
@@ -1195,11 +1211,10 @@ maybe_prepend_change_cipher_spec(State, Bin) ->
%% @doc Appends a change_cipher_spec record to the input binary
%% @end
maybe_append_change_cipher_spec(#state{
- ssl_options =
- #{middlebox_comp_mode := true},
+ session = #session{session_id = Id},
handshake_env =
#handshake_env{
- change_cipher_spec_sent = false} = HSEnv} = State, Bin) ->
+ change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID ->
CCSBin = create_change_cipher_spec(State),
{State#state{handshake_env =
HSEnv#handshake_env{change_cipher_spec_sent = true}},
@@ -1207,7 +1222,7 @@ maybe_append_change_cipher_spec(#state{
maybe_append_change_cipher_spec(State, Bin) ->
{State, Bin}.
-maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) ->
+maybe_queue_cert_cert_cv(#state{client_certificate_status = not_requested} = State) ->
{ok, State};
maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
session = #session{session_id = _SessionId,
@@ -1347,8 +1362,9 @@ maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Con
cert_db_ref = CertDbRef}} = State,
#{verify := verify_peer,
signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert}, _) ->
- CertificateRequest = certificate_request(SignAlgs, SignAlgsCert, CertDbHandle, CertDbRef),
+ signature_algs_cert := SignAlgsCert,
+ certificate_authorities := CertAuthBool}, _) ->
+ CertificateRequest = certificate_request(SignAlgs, SignAlgsCert, CertDbHandle, CertDbRef, CertAuthBool),
{Connection:queue_handshake(CertificateRequest, State), wait_cert}.
maybe_send_certificate(State, PSK) when PSK =/= undefined ->
@@ -1431,7 +1447,8 @@ create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
process_certificate_request(#certificate_request_1_3{
extensions = Extensions},
#state{ssl_options = #{signature_algs := ClientSignAlgs},
- connection_env = #connection_env{cert_key_pairs = CertKeyPairs},
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts,
+ negotiated_version = Version},
static_env = #static_env{cert_db = CertDbHandle, cert_db_ref = CertDbRef},
session = Session0} =
State) ->
@@ -1439,12 +1456,13 @@ process_certificate_request(#certificate_request_1_3{
maps:get(signature_algs, Extensions, undefined)),
ServerSignAlgsCert = get_signature_scheme_list(
maps:get(signature_algs_cert, Extensions, undefined)),
- CertAuths = get_certificate_authorites(maps:get(certificate_authorities, Extensions, undefined)),
+ CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version),
Session = select_client_cert_key_pair(Session0, CertKeyPairs,
- ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs,
- CertDbHandle, CertDbRef, CertAuths),
- {ok, {State#state{client_certificate_requested = true, session = Session}, wait_cert}}.
+ ServerSignAlgs, ServerSignAlgsCert, filter_tls13_algs(ClientSignAlgs),
+ CertDbHandle, CertDbRef, CertAuths, undefined),
+ {ok, {State#state{client_certificate_status = requested, session = Session}, wait_cert}}.
process_certificate(#certificate_1_3{
certificate_request_context = <<>>,
@@ -1649,19 +1667,16 @@ calculate_client_early_traffic_secret(
PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
PendingRead1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret,
PendingRead0),
- PendingRead2 = update_connection_state(PendingRead1, undefined, undefined,
+ PendingRead = update_connection_state(PendingRead1, undefined, undefined,
undefined,
Key, IV, undefined),
- %% Signal start of early data. This is to prevent handshake messages to be
- %% counted in max_early_data_size.
- PendingRead = PendingRead2#{count_early_data => true},
State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}}
end.
-update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataLimit) ->
+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_limit => EarlyDataLimit},
+ early_data_accepted => EarlyDataExpected},
State#state{connection_states = CS#{current_read => Read}}.
maybe_store_early_data_secret(true, EarlySecret, State) ->
@@ -2064,7 +2079,7 @@ verify_signature_algorithm(#state{
static_env = #static_env{role = Role},
ssl_options = #{signature_algs := LocalSignAlgs}} = State0,
#certificate_verify_1_3{algorithm = PeerSignAlg}) ->
- case lists:member(PeerSignAlg, LocalSignAlgs) of
+ case lists:member(PeerSignAlg, filter_tls13_algs(LocalSignAlgs)) of
true ->
{ok, maybe_update_selected_sign_alg(State0, PeerSignAlg, Role)};
false ->
@@ -2312,14 +2327,14 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) ->
%% DSA keys are not supported by TLS 1.3
-select_sign_algo(dsa, _RSAKeySize, _PeerSignAlgs, _OwnSignAlgs, _Curve) ->
+select_sign_algo(dsa, _RSAKeySize, _CertSignAlg, _OwnSignAlgs, _Curve) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)};
select_sign_algo(_, _RSAKeySize, [], _, _) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)};
select_sign_algo(_, _RSAKeySize, undefined, _OwnSignAlgs, _) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)};
-select_sign_algo(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve) ->
- {_, S, _} = ssl_cipher:scheme_to_components(PeerSignAlg),
+select_sign_algo(PublicKeyAlgo, RSAKeySize, [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve) ->
+ {_, S, _} = ssl_cipher:scheme_to_components(CertSignAlg),
%% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
%% TLS handshake messages: filter sha-1 and rsa_pkcs1.
%%
@@ -2333,36 +2348,36 @@ select_sign_algo(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignA
orelse (PublicKeyAlgo =:= eddsa andalso S =:= eddsa)
)
andalso
- lists:member(PeerSignAlg, OwnSignAlgs) of
+ lists:member(CertSignAlg, OwnSignAlgs) of
true ->
validate_key_compatibility(PublicKeyAlgo, RSAKeySize,
- [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve);
+ [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve);
false ->
- select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs, Curve)
+ select_sign_algo(PublicKeyAlgo, RSAKeySize, CertSignAlgs, OwnSignAlgs, Curve)
end.
-validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve)
+validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve)
when PublicKeyAlgo =:= rsa orelse
PublicKeyAlgo =:= rsa_pss_pss ->
- {Hash, Sign, _} = ssl_cipher:scheme_to_components(PeerSignAlg),
+ {Hash, Sign, _} = ssl_cipher:scheme_to_components(CertSignAlg),
case (Sign =:= rsa_pss_rsae orelse Sign =:= rsa_pss_pss) andalso
is_rsa_key_compatible(RSAKeySize, Hash) of
true ->
- {ok, PeerSignAlg};
+ {ok, CertSignAlg};
false ->
- select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs, Curve)
+ select_sign_algo(PublicKeyAlgo, RSAKeySize, CertSignAlgs, OwnSignAlgs, Curve)
end;
-validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve)
+validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve)
when PublicKeyAlgo =:= ecdsa ->
- {_ , Sign, PeerCurve} = ssl_cipher:scheme_to_components(PeerSignAlg),
+ {_ , Sign, PeerCurve} = ssl_cipher:scheme_to_components(CertSignAlg),
case Sign =:= ecdsa andalso Curve =:= PeerCurve of
true ->
- {ok, PeerSignAlg};
+ {ok, CertSignAlg};
false ->
- select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs, Curve)
+ select_sign_algo(PublicKeyAlgo, RSAKeySize, CertSignAlgs, OwnSignAlgs, Curve)
end;
-validate_key_compatibility(_, _, [PeerSignAlg|_], _, _) ->
- {ok, PeerSignAlg}.
+validate_key_compatibility(_, _, [CertSignAlg|_], _, _) ->
+ {ok, CertSignAlg}.
is_rsa_key_compatible(KeySize, Hash) ->
HashSize = ssl_cipher:hash_size(Hash),
@@ -2419,18 +2434,14 @@ get_certificate_params(Cert) ->
SubjectPublicKeyAlgo = public_key_algo(SubjectPublicKeyAlgo0),
{SubjectPublicKeyAlgo, SignAlgo, SignHash, RSAKeySize, Curve}.
-oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm =
+oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm =
#'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
parameters = #'HashAlgorithm'{algorithm = HashOid}}}) ->
Hash = public_key:pkix_hash_type(HashOid),
{Hash, rsa_pss_pss};
oids_to_atoms(SignAlgo, _) ->
- case public_key:pkix_sign_types(SignAlgo) of
- {sha, Sign} ->
- {sha1, Sign};
- {_,_} = Algs ->
- Algs
- end.
+ public_key:pkix_sign_types(SignAlgo).
+
%% Note: copied from ssl_handshake
public_key_algo(?'id-RSASSA-PSS') ->
rsa_pss_pss;
@@ -2458,9 +2469,9 @@ get_signature_scheme_list(#signature_algorithms{
lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end,
ClientSignatureSchemes).
-get_certificate_authorites(#certificate_authorities{authorities = Auths}) ->
+get_certificate_authorities(#certificate_authorities{authorities = Auths}) ->
Auths;
-get_certificate_authorites(undefined) ->
+get_certificate_authorities(undefined) ->
[].
get_supported_groups(undefined = Groups) ->
@@ -2737,8 +2748,8 @@ maybe_send_early_data(#state{
%% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData
State3 = ssl_record:step_encryption_state_write(State2),
{ok, encode_early_data(Cipher, State3)};
- {ok, {_, _, _, _MaxSize}} ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, too_much_early_data)};
+ {ok, {_, _, _, MaxSize}} ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {too_much_early_data, {max, MaxSize}})};
{error, Alert} ->
{error, Alert}
end;
@@ -2886,7 +2897,7 @@ process_ticket(#{cipher_suite := CipherSuite,
ticket = Ticket,
extensions = Extensions
} = NewSessionTicket,
- TicketAge = erlang:system_time(seconds) - Timestamp,
+ TicketAge = erlang:system_time(millisecond) - Timestamp,
ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
Identity = #psk_identity{
identity = Ticket,
@@ -2966,55 +2977,83 @@ supported_groups_from_extensions(Extensions) ->
{ok, undefined}
end.
-select_server_cert_key_pair(_,[], _,_,_,_, {error, _} = Return) ->
- Return;
select_server_cert_key_pair(_,[], _,_,_,_, #session{}=Session) ->
+ %% Conformant Cert-Key pair with advertised signature algorithm is
+ %% selected.
+ {ok, Session};
+select_server_cert_key_pair(_,[], _,_,_,_, {fallback, #session{}=Session}) ->
+ %% Use fallback Cert-Key pair as no conformant pair to the advertised
+ %% signature algorithms was found.
{ok, Session};
select_server_cert_key_pair(_,[], _,_,_,_, undefined) ->
- {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_send_certificate_verifiable_by_client)};
+ {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_supply_acceptable_cert)};
select_server_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
ClientSignAlgs, ClientSignAlgsCert, CertAuths,
- #state{static_env = #static_env{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef} = State},
- Default) ->
+ #state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef} = State},
+ Default0) ->
{_, SignAlgo, SignHash, _, _} = get_certificate_params(Cert),
%% TODO: We do validate the signature algorithm and signature hash but we could also check
%% if the signing cert has a key on a curve supported by the client for ECDSA/EDDSA certs
case check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert) of
ok ->
case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of
- {ok, EncodeChain} ->
+ {ok, EncodeChain} -> %% Chain fullfills certificate_authorities extension
{ok, Session#session{own_certificates = EncodeChain, private_key = Key}};
{error, EncodeChain, not_in_auth_domain} ->
+ %% If this is the first chain to fulfill the signing requirement, use it as default,
+ %% if not later alternative also fulfills certificate_authorities extension
Default = Session#session{own_certificates = EncodeChain, private_key = Key},
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, CertAuths, State, Default)
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State, default_or_fallback(Default0, Default))
end;
- Error ->
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, CertAuths, State,
- default_cert_key_pair_return(Default, Error))
+ _ ->
+ %% If the server cannot produce a certificate chain that is signed only
+ %% via the indicated supported algorithms, then it SHOULD continue the
+ %% handshake by sending the client a certificate chain of its choice
+ case SignHash of
+ sha ->
+ %% According to "Server Certificate Selection - RFC 8446"
+ %% Never send cert using sha1 unless client allows it
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State, Default0);
+ _ ->
+ %% If there does not exist a default or fallback from previous alternatives
+ %% use this alternative as fallback.
+ Fallback = {fallback, Session#session{own_certificates = Certs, private_key = Key}},
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State,
+ default_or_fallback(Default0, Fallback))
+ end
end.
+default_or_fallback(undefined, DefaultOrFallback) ->
+ DefaultOrFallback;
+default_or_fallback({fallback, _}, #session{} = Default) ->
+ Default;
+default_or_fallback(Default, _) ->
+ Default.
+
select_client_cert_key_pair(Session0,
[#{private_key := NoKey, certs := [[]] = NoCerts}],
- _,_,_,_,_,_) ->
+ _,_,_,_,_,_, _) ->
%% No certificate supplied : send empty certificate
Session0#session{own_certificates = NoCerts,
private_key = NoKey};
-select_client_cert_key_pair(Session0, CertKeyPairs, ServerSignAlgs, ServerSignAlgsCert,
- ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths) ->
- select_client_cert_key_pair(Session0, CertKeyPairs, ServerSignAlgs, ServerSignAlgsCert,
- ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths, undefined).
-
-select_client_cert_key_pair(Session, [],_,_,_,_,_,_, undefined = Default) ->
- %% No certificate compliant with supported algorithms : send empty certificate in state 'wait_finished'
- Session#session{own_certificates = Default,
- private_key = Default};
-select_client_cert_key_pair(_,[],_,_,_,_,_,_,#session{}=Session) ->
- %% No certificate compliant with guide lines send default
- Session;
-
+select_client_cert_key_pair(Session, [],_,_,_,_,_,_, undefined) ->
+ %% No certificate compliant with supported algorithms and
+ %% extensison : send empty certificate in state 'wait_finished'
+ Session#session{own_certificates = [[]],
+ private_key = #{}};
+select_client_cert_key_pair(_,[],_,_,_,_,_,_, #session{} = Plausible) ->
+ %% If we do not find an alternative chain with a cert signed in auth_domain,
+ %% but have a single cert without chain certs it might be verifiable by
+ %% a server that has the means to recreate the chain
+ Plausible;
select_client_cert_key_pair(Session0, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
- ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths, Default) ->
+ ServerSignAlgs, ServerSignAlgsCert,
+ ClientSignAlgs, CertDbHandle, CertDbRef,
+ CertAuths, Plausible0) ->
{PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize, Curve} = get_certificate_params(Cert),
case select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs, Curve) of
{ok, SelectedSignAlg} ->
@@ -3028,24 +3067,25 @@ select_client_cert_key_pair(Session0, [#{private_key := Key, certs := [Cert| _]
private_key = Key
};
{error, EncodedChain, not_in_auth_domain} ->
- Session = Session0#session{sign_alg = SelectedSignAlg,
- own_certificates = EncodedChain,
- private_key = Key
- },
- select_client_cert_key_pair(Session, Rest, ServerSignAlgs, ServerSignAlgsCert,
- ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths,
- default_cert_key_pair_return(Default, Session))
+ Plausible = plausible_missing_chain(EncodedChain, Plausible0,
+ SelectedSignAlg, Key, Session0),
+ select_client_cert_key_pair(Session0, Rest, ServerSignAlgs, ServerSignAlgsCert,
+ ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths,
+ Plausible)
end;
_ ->
select_client_cert_key_pair(Session0, Rest, ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs,
- CertDbHandle, CertDbRef, CertAuths, Default)
+ CertDbHandle, CertDbRef, CertAuths, Plausible0)
end;
{error, _} ->
select_client_cert_key_pair(Session0, Rest, ServerSignAlgsCert, ServerSignAlgsCert, ClientSignAlgs,
- CertDbHandle, CertDbRef, CertAuths, Default)
+ CertDbHandle, CertDbRef, CertAuths, Plausible0)
end.
-default_cert_key_pair_return(undefined, Session) ->
- Session;
-default_cert_key_pair_return(Default, _) ->
- Default.
+plausible_missing_chain([_] = EncodedChain, undefined, SignAlg, Key, Session0) ->
+ Session0#session{sign_alg = SignAlg,
+ own_certificates = EncodedChain,
+ private_key = Key
+ };
+plausible_missing_chain(_,Plausible,_,_,_) ->
+ Plausible.
diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl
index 0c525bbb89..5af35208b9 100644
--- a/lib/ssl/src/tls_handshake_1_3.hrl
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -26,10 +26,10 @@
-ifndef(tls_handshake_1_3).
-define(tls_handshake_1_3, true).
-%% Common to TLS-1.3 and previous TLS versions
-%% Some definitions may not exist in TLS-1.3 this is
+%% Common to TLS-1.3 and previous TLS versions
+%% Some definitions may not exist in TLS-1.3 this is
%% handled elsewhere
--include("tls_handshake.hrl").
+-include("tls_handshake.hrl").
%% New handshake types in TLS-1.3 RFC 8446 B.3
-define(NEW_SESSION_TICKET, 4).
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 30341f1598..ff23b2a99b 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -202,7 +202,7 @@ encode_data(Data, Version,
%%--------------------------------------------------------------------
-spec decode_cipher_text(tls_version(), #ssl_tls{}, ssl_record:connection_states(), boolean()) ->
- {#ssl_tls{} | trial_decryption_failed,
+ {#ssl_tls{} | no_record,
ssl_record:connection_states()}| #alert{}.
%%
%% Description: Decode cipher text
@@ -498,10 +498,10 @@ initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
secure_renegotiation => undefined,
client_verify_data => undefined,
server_verify_data => undefined,
- max_early_data_size => MaxEarlyDataSize,
+ pending_early_data_size => MaxEarlyDataSize,
max_fragment_length => undefined,
trial_decryption => false,
- early_data_limit => false
+ early_data_expected => false
}.
%% Used by logging to recreate the received bytes
diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl
index a483e5ba5b..e446e481a7 100644
--- a/lib/ssl/src/tls_record.hrl
+++ b/lib/ssl/src/tls_record.hrl
@@ -30,10 +30,11 @@
-include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes
%% Used to handle tls_plain_text, tls_compressed and tls_cipher_text
--record(ssl_tls, {
- type,
- version,
- fragment
- }).
+-record(ssl_tls, {
+ type,
+ version,
+ fragment,
+ early_data = false % TLS-1.3
+ }).
-endif. % -ifdef(tls_record).
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index ae375e4da1..440d9e0998 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -21,12 +21,14 @@
-include("tls_record.hrl").
-include("tls_record_1_3.hrl").
+-include("tls_handshake_1_3.hrl").
-include("ssl_internal.hrl").
-include("ssl_alert.hrl").
-include("ssl_cipher.hrl").
%% Encoding
--export([encode_handshake/2, encode_alert_record/2,
+-export([encode_handshake/2,
+ encode_alert_record/2,
encode_data/2]).
-export([encode_plain_text/3]).
@@ -43,7 +45,8 @@
%
%% Description: Encodes a handshake message to send on the tls-1.3-socket.
%%--------------------------------------------------------------------
-encode_handshake(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} =
+encode_handshake(Frag, #{current_write :=
+ #{max_fragment_length := MaxFragmentLength}} =
ConnectionStates) ->
MaxLength = if is_integer(MaxFragmentLength) ->
MaxFragmentLength;
@@ -75,7 +78,8 @@ encode_alert_record(#alert{level = Level, description = Description},
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} =
+encode_data(Frag, #{current_write :=
+ #{max_fragment_length := MaxFragmentLength}} =
ConnectionStates) ->
MaxLength = if is_integer(MaxFragmentLength) ->
MaxFragmentLength;
@@ -85,7 +89,8 @@ encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}
Data = tls_record:split_iovec(Frag, MaxLength),
encode_iolist(?APPLICATION_DATA, Data, ConnectionStates).
-encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) ->
+encode_plain_text(Type, Data0, #{current_write := Write0} =
+ ConnectionStates) ->
PadLen = 0, %% TODO where to specify PadLen?
Data = inner_plaintext(Type, Data0, PadLen),
CipherFragment = encode_plain_text(Data, Write0),
@@ -107,12 +112,13 @@ encode_iolist(Type, Data, ConnectionStates0) ->
%%--------------------------------------------------------------------
-spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states()) ->
- {#ssl_tls{} | trial_decryption_failed,
+ {#ssl_tls{} | no_record,
ssl_record:connection_states()}| #alert{}.
%%
-%% Description: Decode cipher text, use legacy type ssl_tls instead of tls_cipher_text
-%% in decoding context so that we can reuse the code from earlier versions.
-%%--------------------------------------------------------------------
+%% Description: Decode cipher text, use legacy type ssl_tls instead of
+%% tls_cipher_text in decoding context so that we can reuse the code
+%% from earlier versions.
+%% --------------------------------------------------------------------
decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
version = ?LEGACY_VERSION,
fragment = CipherFragment},
@@ -126,22 +132,25 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
cipher_type = ?AEAD,
bulk_cipher_algorithm =
BulkCipherAlgo},
- max_early_data_size := MaxEarlyDataSize0,
+ pending_early_data_size := PendingMaxEarlyDataSize0,
trial_decryption := TrialDecryption,
- early_data_limit := EarlyDataLimit
+ early_data_accepted := EarlyDataAccepted
} = ReadState0} = ConnectionStates0) ->
case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of
#alert{} when TrialDecryption =:= true andalso
- MaxEarlyDataSize0 > 0 -> %% Trial decryption
- trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0,
- BulkCipherAlgo, CipherFragment);
+ EarlyDataAccepted =:= false andalso
+ PendingMaxEarlyDataSize0 > 0 -> %% Trial decryption
+ ignore_early_data(ConnectionStates0, ReadState0,
+ PendingMaxEarlyDataSize0,
+ BulkCipherAlgo, CipherFragment);
#alert{} = Alert ->
Alert;
- PlainFragment0 when EarlyDataLimit =:= true andalso
- MaxEarlyDataSize0 > 0 ->
+ PlainFragment0 when EarlyDataAccepted =:= true andalso
+ PendingMaxEarlyDataSize0 > 0 ->
PlainFragment = remove_padding(PlainFragment0),
- process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq,
- BulkCipherAlgo, CipherFragment, PlainFragment);
+ process_early_data(ConnectionStates0, ReadState0,
+ PendingMaxEarlyDataSize0, Seq,
+ PlainFragment);
PlainFragment0 ->
PlainFragment = remove_padding(PlainFragment0),
ConnectionStates =
@@ -159,20 +168,20 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
%% the signature algorithm of the client's certificate.)
decode_cipher_text(#ssl_tls{type = ?ALERT,
version = ?LEGACY_VERSION,
- fragment = <<2,47>>},
+ fragment = <<?FATAL,?ILLEGAL_PARAMETER>>},
ConnectionStates0) ->
{#ssl_tls{type = ?ALERT,
version = {3,4}, %% Internally use real version
- fragment = <<2,47>>}, ConnectionStates0};
+ fragment = <<?FATAL,?ILLEGAL_PARAMETER>>}, ConnectionStates0};
%% TLS 1.3 server can receive a User Cancelled Alert when handshake is
%% paused and then cancelled on the client side.
decode_cipher_text(#ssl_tls{type = ?ALERT,
version = ?LEGACY_VERSION,
- fragment = <<2,90>>},
+ fragment = <<?FATAL,?USER_CANCELED>>},
ConnectionStates0) ->
{#ssl_tls{type = ?ALERT,
version = {3,4}, %% Internally use real version
- fragment = <<2,90>>}, ConnectionStates0};
+ fragment = <<?FATAL,?USER_CANCELED>>}, ConnectionStates0};
%% RFC8446 - TLS 1.3
%% D.4. Middlebox Compatibility Mode
%% - If not offering early data, the client sends a dummy
@@ -208,48 +217,49 @@ decode_cipher_text(#ssl_tls{type = Type}, _) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0,
+ignore_early_data(ConnectionStates0, ReadState0, PendingMaxEarlyDataSize0,
BulkCipherAlgo, CipherFragment) ->
- MaxEarlyDataSize = update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment),
- ConnectionStates =
- ConnectionStates0#{current_read =>
- ReadState0#{max_early_data_size => MaxEarlyDataSize}},
- if MaxEarlyDataSize < 0 ->
- %% More early data is trial decrypted as the configured limit
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed);
- true ->
- {trial_decryption_failed, ConnectionStates}
- end.
-
-process_early_data(ConnectionStates0, ReadState0, _MaxEarlyDataSize0, Seq,
- _BulkCipherAlgo, _CipherFragment, PlainFragment)
- when PlainFragment =:= <<5,0,0,0,22>> ->
- %% struct {
- %% opaque content[TLSPlaintext.length]; <<5,0,0,0>> - 5 = EndOfEarlyData
- %% 0 = (uint24) size
- %% ContentType type; <<22>> - Handshake
- %% uint8 zeros[length_of_padding]; <<>> - no padding
- %% } TLSInnerPlaintext;
- %% EndOfEarlyData should not be counted into early data
+ PendingMaxEarlyDataSize =
+ approximate_pending_early_data_size(PendingMaxEarlyDataSize0,
+ BulkCipherAlgo, CipherFragment),
ConnectionStates =
- ConnectionStates0#{current_read =>
- ReadState0#{sequence_number => Seq + 1}},
- {decode_inner_plaintext(PlainFragment), ConnectionStates};
-process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq,
- BulkCipherAlgo, CipherFragment, PlainFragment) ->
+ ConnectionStates0#{current_read =>
+ ReadState0#{pending_early_data_size => PendingMaxEarlyDataSize}},
+ if PendingMaxEarlyDataSize < 0 ->
+ %% More early data is trial decrypted as the configured limit
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {decryption_failed,
+ {max_early_data_threshold_exceeded,
+ PendingMaxEarlyDataSize}});
+ true ->
+ {no_record, ConnectionStates}
+ end.
+process_early_data(ConnectionStates0, ReadState0, PendingMaxEarlyDataSize0, Seq,
+ PlainFragment) ->
%% First packet is deciphered anyway so we must check if more early data is received
%% than the configured limit (max_early_data_size).
- MaxEarlyDataSize =
- update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment),
- if MaxEarlyDataSize < 0 ->
- %% Too much early data received, send alert unexpected_message
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, too_much_early_data);
- true ->
+ Record = decode_inner_plaintext(PlainFragment),
+ case {Record#ssl_tls.type, remove_padding(Record#ssl_tls.fragment)} of
+ {?HANDSHAKE, <<?END_OF_EARLY_DATA>>} ->
ConnectionStates =
ConnectionStates0#{current_read =>
- ReadState0#{sequence_number => Seq + 1,
- max_early_data_size => MaxEarlyDataSize}},
- {decode_inner_plaintext(PlainFragment), ConnectionStates}
+ ReadState0#{sequence_number => Seq + 1}},
+ {Record, ConnectionStates};
+ {?APPLICATION_DATA, Data} ->
+ PendingMaxEarlyDataSize =
+ pending_early_data_size(PendingMaxEarlyDataSize0, Data),
+ if PendingMaxEarlyDataSize < 0 ->
+ %% Too much early data received, send alert unexpected_message
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE,
+ {too_much_early_data,
+ {max_early_data_threshold_exceeded,
+ PendingMaxEarlyDataSize}});
+ true ->
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{sequence_number => Seq + 1,
+ pending_early_data_size => PendingMaxEarlyDataSize}},
+ {Record#ssl_tls{early_data = true}, ConnectionStates}
+ end
end.
inner_plaintext(Type, Data, Length) ->
@@ -276,7 +286,8 @@ encode_plain_text(#inner_plaintext{
}) ->
PlainText = [Data, Type, Zeros],
Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen),
- #tls_cipher_text{opaque_type = 23, %% 23 (application_data) for outward compatibility
+ %% 23 (application_data) for outward compatibility
+ #tls_cipher_text{opaque_type = ?OPAQUE_TYPE,
legacy_version = {3,3},
encoded_record = Encoded};
encode_plain_text(#inner_plaintext{
@@ -320,7 +331,8 @@ cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type,
legacy_version = {MajVer, MinVer},
- encoded_record = Encoded}, #{sequence_number := Seq} = Write) ->
+ encoded_record = Encoded},
+ #{sequence_number := Seq} = Write) ->
Length = erlang:iolist_size(Encoded),
{[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded],
Write#{sequence_number => Seq +1}}.
@@ -333,11 +345,14 @@ decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
case ssl_cipher:aead_decrypt(BulkCipherAlgo, Key, Nonce, CipherText, CipherTag, AAD) of
Content when is_binary(Content) ->
Content;
- _ ->
+ Reason ->
+ ?SSL_LOG(debug, decrypt_error, [{reason,Reason},
+ {stacktrace, process_info(self(), current_stacktrace)}]),
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end
catch
- _:_ ->
+ _:Reason2:ST ->
+ ?SSL_LOG(debug, decrypt_error, [{reason,Reason2}, {stacktrace, ST}]),
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
@@ -379,19 +394,19 @@ remove_padding(InnerPlainText) ->
InnerPlainText
end.
-update_max_early_date_size(MaxEarlyDataSize, BulkCipherAlgo, CipherFragment) ->
- %% CipherFragment is the binary encoded form of a TLSInnerPlaintext:
- %%
- %% struct {
- %% opaque content[TLSPlaintext.length];
- %% ContentType type;
- %% uint8 zeros[length_of_padding];
- %% } TLSInnerPlaintext;
- %%
- TypeLen = 1,
- PaddingLen = 0, %% TODO Update formula when padding is implemented!
- MaxEarlyDataSize - (byte_size(CipherFragment) - TypeLen - PaddingLen -
- bca_tag_len(BulkCipherAlgo)).
+pending_early_data_size(PendingMaxEarlyDataSize, PlainFragment) ->
+ %% The maximum amount of 0-RTT data that the client is allowed to
+ %% send when using this ticket, in bytes. Only Application Data
+ %% payload (i.e., plaintext but not padding or the inner content
+ %% type byte) is counted.
+ PendingMaxEarlyDataSize - (byte_size(PlainFragment)).
+
+approximate_pending_early_data_size(PendingMaxEarlyDataSize,
+ BulkCipherAlgo, CipherFragment) ->
+ %% We can not know how much is padding!
+ InnerContTypeLen = 1,
+ PendingMaxEarlyDataSize - (byte_size(CipherFragment) -
+ InnerContTypeLen - bca_tag_len(BulkCipherAlgo)).
bca_tag_len(?AES_CCM_8) ->
8;
diff --git a/lib/ssl/src/tls_record_1_3.hrl b/lib/ssl/src/tls_record_1_3.hrl
index dbd1aafae5..c6214a5de3 100644
--- a/lib/ssl/src/tls_record_1_3.hrl
+++ b/lib/ssl/src/tls_record_1_3.hrl
@@ -27,6 +27,10 @@
-ifndef(tls_record_1_3).
-define(tls_record_1_3, true).
+%% Common to TLS-1.3 and previous TLS versions
+%% Some definitions may not exist in TLS-1.3 this is
+%% handled elsewhere
+-include("tls_record.hrl").
%% enum {
%% invalid(0),
%% %% defined in ssl_record.hrl
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index 68e4eeed4e..b53a801b90 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -68,7 +68,8 @@
key_update_at, %% TLS 1.3
bytes_sent, %% TLS 1.3
dist_handle,
- log_level
+ log_level,
+ hibernate_after
}).
-record(data,
@@ -230,7 +231,8 @@ init({call, From}, {Pid, #{current_write := WriteState,
negotiated_version := Version,
renegotiate_at := RenegotiateAt,
key_update_at := KeyUpdateAt,
- log_level := LogLevel}},
+ log_level := LogLevel,
+ hibernate_after := HibernateAfter}},
#data{connection_states = ConnectionStates, static = Static0} = StateData0) ->
StateData =
StateData0#data{connection_states = ConnectionStates#{current_write => WriteState},
@@ -245,10 +247,11 @@ init({call, From}, {Pid, #{current_write := WriteState,
renegotiate_at = RenegotiateAt,
key_update_at = KeyUpdateAt,
bytes_sent = 0,
- log_level = LogLevel}},
+ log_level = LogLevel,
+ hibernate_after = HibernateAfter}},
{next_state, handshake, StateData, [{reply, From, ok}]};
init(info = Type, Msg, StateData) ->
- handle_common(Type, Msg, StateData);
+ handle_common(?FUNCTION_NAME, Type, Msg, StateData);
init(_, _, _) ->
%% Just in case anything else sneaks through
{keep_state_and_data, [postpone]}.
@@ -281,14 +284,14 @@ connection({call, From}, downgrade, #data{connection_states =
#{current_write := Write}} = StateData) ->
{next_state, death_row, StateData, [{reply,From, {ok, Write}}]};
connection({call, From}, {set_opts, Opts}, StateData) ->
- handle_set_opts(From, Opts, StateData);
+ handle_set_opts(?FUNCTION_NAME, From, Opts, StateData);
connection({call, From}, dist_get_tls_socket,
#data{static = #static{transport_cb = Transport,
socket = Socket,
connection_pid = Pid,
trackers = Trackers}} = StateData) ->
TLSSocket = tls_gen_connection:socket([Pid, self()], Transport, Socket, Trackers),
- {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]};
+ hibernate_after(?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]);
connection({call, From}, {dist_handshake_complete, _Node, DHandle},
#data{static = #static{connection_pid = Pid} = Static} = StateData) ->
false = erlang:dist_ctrl_set_opt(DHandle, get_size, true),
@@ -296,15 +299,20 @@ connection({call, From}, {dist_handshake_complete, _Node, DHandle},
ok = ssl_gen_statem:dist_handshake_complete(Pid, DHandle),
%% From now on we execute on normal priority
process_flag(priority, normal),
- {keep_state, StateData#data{static = Static#static{dist_handle = DHandle}},
- [{reply,From,ok}|
- case dist_data(DHandle) of
- [] ->
- [];
- Data ->
- [{next_event, internal,
- {application_packets,{self(),undefined},Data}}]
- end]};
+
+ case dist_data(DHandle) of
+ [] ->
+ hibernate_after(?FUNCTION_NAME,
+ StateData#data{
+ static = Static#static{dist_handle = DHandle}},
+ [{reply,From,ok}]);
+ Data ->
+ {keep_state,
+ StateData#data{static = Static#static{dist_handle = DHandle}},
+ [{reply,From,ok},
+ {next_event, internal,
+ {application_packets, {self(),undefined}, Data}}]}
+ end;
connection(internal, {application_packets, From, Data}, StateData) ->
send_application_data(Data, From, ?FUNCTION_NAME, StateData);
connection(internal, {post_handshake_data, From, HSData}, StateData) ->
@@ -314,20 +322,22 @@ connection(cast, #alert{} = Alert, StateData0) ->
{next_state, ?FUNCTION_NAME, StateData};
connection(cast, {new_write, WritesState, Version},
#data{connection_states = ConnectionStates, static = Static} = StateData) ->
- {next_state, connection,
- StateData#data{connection_states =
- ConnectionStates#{current_write => WritesState},
- static = Static#static{negotiated_version = Version}}};
+ hibernate_after(connection,
+ StateData#data{connection_states =
+ ConnectionStates#{current_write => WritesState},
+ static =
+ Static#static{negotiated_version = Version}}, []);
%%
-connection(info, dist_data, #data{static = #static{dist_handle = DHandle}}) ->
- {keep_state_and_data,
+connection(info, dist_data,
+ #data{static = #static{dist_handle = DHandle}} = StateData) ->
case dist_data(DHandle) of
[] ->
- [];
+ hibernate_after(?FUNCTION_NAME, StateData, []);
Data ->
- [{next_event, internal,
- {application_packets,{self(),undefined},Data}}]
- end};
+ {keep_state_and_data,
+ [{next_event, internal,
+ {application_packets, {self(),undefined}, Data}}]}
+ end;
connection(info, tick, StateData) ->
consume_ticks(),
Data = [<<0:32>>], % encode_packet(4, <<>>)
@@ -342,8 +352,10 @@ connection(info, {send, From, Ref, Data}, _StateData) ->
{keep_state_and_data,
[{next_event, {call, {self(), undefined}},
{application_data, erlang:iolist_to_iovec(Data)}}]};
+connection(timeout, hibernate, _StateData) ->
+ {keep_state_and_data, [hibernate]};
connection(Type, Msg, StateData) ->
- handle_common(Type, Msg, StateData).
+ handle_common(?FUNCTION_NAME, Type, Msg, StateData).
%%--------------------------------------------------------------------
-spec handshake(gen_statem:event_type(),
@@ -352,7 +364,7 @@ connection(Type, Msg, StateData) ->
gen_statem:event_handler_result(atom()).
%%--------------------------------------------------------------------
handshake({call, From}, {set_opts, Opts}, StateData) ->
- handle_set_opts(From, Opts, StateData);
+ handle_set_opts(?FUNCTION_NAME, From, Opts, StateData);
handshake({call, _}, _, _) ->
%% Postpone all calls to the connection state
{keep_state_and_data, [postpone]};
@@ -376,7 +388,7 @@ handshake(info, {send, _, _, _}, _) ->
%% Testing only, OTP distribution test suites...
{keep_state_and_data, [postpone]};
handshake(Type, Msg, StateData) ->
- handle_common(Type, Msg, StateData).
+ handle_common(?FUNCTION_NAME, Type, Msg, StateData).
%%--------------------------------------------------------------------
-spec death_row(gen_statem:event_type(),
@@ -384,14 +396,18 @@ handshake(Type, Msg, StateData) ->
StateData :: term()) ->
gen_statem:event_handler_result(atom()).
%%--------------------------------------------------------------------
-death_row(state_timeout, Reason, _State) ->
+death_row(state_timeout, Reason, _StateData) ->
{stop, {shutdown, Reason}};
-death_row(info = Type, Msg, State) ->
- handle_common(Type, Msg, State);
-death_row(_Type, _Msg, _State) ->
+death_row(info = Type, Msg, StateData) ->
+ handle_common(?FUNCTION_NAME, Type, Msg, StateData);
+death_row(_Type, _Msg, _StateData) ->
%% Waste all other events
keep_state_and_data.
+%% State entry function that starts shutdown state_timeout
+death_row_shutdown(Reason, StateData) ->
+ {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}.
+
%%--------------------------------------------------------------------
-spec terminate(Reason :: term(), State :: term(), Data :: term()) ->
any().
@@ -413,36 +429,47 @@ code_change(_OldVsn, State, Data, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-
-handle_set_opts(From, Opts, #data{static = #static{socket_options = SockOpts} = Static} = StateData) ->
- {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}},
- [{reply, From, ok}]}.
-
-handle_common({call, From}, {set_opts, Opts},
+handle_set_opts(StateName, From, Opts,
+ #data{static = #static{socket_options = SockOpts} = Static}
+ = StateData) ->
+ hibernate_after(StateName,
+ StateData#data{
+ static =
+ Static#static{
+ socket_options = set_opts(SockOpts, Opts)}},
+ [{reply, From, ok}]).
+
+handle_common(StateName, {call, From}, {set_opts, Opts},
#data{static = #static{socket_options = SockOpts} = Static} = StateData) ->
- {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}},
- [{reply, From, ok}]};
-handle_common(info, {'EXIT', _Sup, shutdown},
+ hibernate_after(StateName,
+ StateData#data{
+ static =
+ Static#static{
+ socket_options = set_opts(SockOpts, Opts)}},
+ [{reply, From, ok}]);
+handle_common(_StateName, info, {'EXIT', _Sup, shutdown = Reason},
#data{static = #static{erl_dist = true}} = StateData) ->
%% When the connection is on its way down operations
- %% begin to fail. We wait for 5 seconds to receive
- %% possible exit signals for one of our links to the other
- %% involved distribution parties, in which case we want to use
- %% their exit reason for the connection teardown.
- {next_state, death_row, StateData, [{state_timeout, 5000, shutdown}]};
-handle_common(info, {'EXIT', _Dist, Reason},
+ %% begin to fail. We wait to receive possible exit signals
+ %% for one of our links to the other involved distribution parties,
+ %% in which case we want to use their exit reason
+ %% for the connection teardown.
+ death_row_shutdown(Reason, StateData);
+handle_common(_StateName, info, {'EXIT', _Dist, Reason},
#data{static = #static{erl_dist = true}} = StateData) ->
{stop, {shutdown, Reason}, StateData};
-handle_common(info, {'EXIT', _Sup, shutdown}, StateData) ->
+handle_common(_StateName, info, {'EXIT', _Sup, shutdown}, StateData) ->
{stop, shutdown, StateData};
-handle_common(info, Msg, #data{static = #static{log_level = Level}}) ->
- ssl_logger:log(info, Level, #{event => "TLS sender received unexpected info",
+handle_common(StateName, info, Msg,
+ #data{static = #static{log_level = Level}} = StateData) ->
+ ssl_logger:log(info, Level, #{event => "TLS sender received unexpected info",
reason => [{message, Msg}]}, ?LOCATION),
- keep_state_and_data;
-handle_common(Type, Msg, #data{static = #static{log_level = Level}}) ->
- ssl_logger:log(error, Level, #{event => "TLS sender received unexpected event",
+ hibernate_after(StateName, StateData, []);
+handle_common(StateName, Type, Msg,
+ #data{static = #static{log_level = Level}} = StateData) ->
+ ssl_logger:log(error, Level, #{event => "TLS sender received unexpected event",
reason => [{type, Type}, {message, Msg}]}, ?LOCATION),
- keep_state_and_data.
+ hibernate_after(StateName, StateData, []).
send_tls_alert(#alert{} = Alert,
#data{static = #static{negotiated_version = Version,
@@ -471,7 +498,6 @@ send_application_data(Data, From, StateName,
key_update ->
KeyUpdate = tls_handshake_1_3:key_update(update_requested),
{keep_state_and_data, [{next_event, internal, {post_handshake_data, From, KeyUpdate}},
- {next_event, internal, {key_update, From}},
{next_event, internal, {application_packets, From, Data}}]};
renegotiate ->
tls_dtls_connection:internal_renegotiation(Pid, ConnectionStates0),
@@ -491,15 +517,15 @@ send_application_data(Data, From, StateName,
ok when DistHandle =/= undefined ->
ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
StateData1 = update_bytes_sent(Version, StateData, Data),
- {next_state, StateName, StateData1, []};
+ hibernate_after(StateName, StateData1, []);
Reason when DistHandle =/= undefined ->
- {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]};
+ death_row_shutdown(Reason, StateData);
ok ->
ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
StateData1 = update_bytes_sent(Version, StateData, Data),
- {next_state, StateName, StateData1, [{reply, From, ok}]};
+ hibernate_after(StateName, StateData1, [{reply, From, ok}]);
Result ->
- {next_state, StateName, StateData, [{reply, From, Result}]}
+ hibernate_after(StateName, StateData, [{reply, From, Result}])
end
end.
@@ -522,7 +548,7 @@ send_post_handshake_data(Handshake, From, StateName,
StateData = maybe_update_cipher_key(StateData1, Handshake),
{next_state, StateName, StateData, []};
Reason when DistHandle =/= undefined ->
- {next_state, death_row, StateData1, [{state_timeout, 5000, Reason}]};
+ death_row_shutdown(Reason, StateData1);
ok ->
ssl_logger:debug(LogLevel, outbound, 'record', Encoded),
StateData = maybe_update_cipher_key(StateData1, Handshake),
@@ -679,3 +705,10 @@ consume_ticks() ->
after 0 ->
ok
end.
+
+hibernate_after(connection = StateName,
+ #data{static=#static{hibernate_after = HibernateAfter}} = State,
+ Actions) ->
+ {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
+hibernate_after(StateName, State, Actions) ->
+ {next_state, StateName, State, Actions}.
diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl
index b625cbcd52..f6b91404fb 100644
--- a/lib/ssl/src/tls_server_session_ticket.erl
+++ b/lib/ssl/src/tls_server_session_ticket.erl
@@ -181,7 +181,7 @@ inital_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) ->
}.
ticket_age_add() ->
- MaxTicketAge = 7 * 24 * 3600,
+ MaxTicketAge = 7 * 24 * 3600 * 1000,
IntMax = round(math:pow(2,32)) - 1,
MaxAgeAdd = IntMax - MaxTicketAge,
<<?UINT32(I)>> = crypto:strong_rand_bytes(4),
@@ -385,10 +385,10 @@ stateless_living_ticket(0, _, _, _, _) ->
stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) ->
ReportedAge = ObfAge - TicketAgeAdd,
RealAge = erlang:system_time(second) - Timestamp,
- (ReportedAge =< Lifetime)
+ (ReportedAge =< Lifetime * 1000)
andalso (RealAge =< Lifetime)
andalso (in_window(RealAge, Window)).
-
+
in_window(_, undefined) ->
true;
in_window(Age, Window) when is_integer(Window) ->
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index 80073952fc..779043f1de 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -391,10 +391,10 @@ code_change(_OldVsn, State, _Extra) ->
call(Pid, Msg) ->
gen_server:call(Pid, Msg, infinity).
-start_tls_server_connection(SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) ->
+start_tls_server_connection(#{sender_spawn_opts := SenderOpts} = SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) ->
try
{ok, DynSup} = tls_connection_sup:start_child([]),
- {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, []),
+ {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, [[{spawn_opt, SenderOpts}]]),
ConnArgs = [server, Sender, "localhost", Port, Socket,
{SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo],
{ok, Pid} = tls_dyn_connection_sup:start_child(DynSup, receiver, ConnArgs),
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 75d994f18c..dd891967bc 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -869,25 +869,47 @@ signature_algs({3, 3}, HashSigns) ->
default_signature_algs([{3, 4} = Version]) ->
default_signature_schemes(Version) ++ legacy_signature_schemes(Version);
default_signature_algs([{3, 4}, {3,3} | _]) ->
- default_signature_schemes({3,4}) ++ default_signature_algs([{3,3}]);
+ default_signature_schemes({3,4}) ++ default_pre_1_3_signature_algs_only();
default_signature_algs([{3, 3} = Version |_]) ->
- Default = [%% SHA2
- {sha512, ecdsa},
- {sha512, rsa},
- {sha384, ecdsa},
- {sha384, rsa},
- {sha256, ecdsa},
- {sha256, rsa},
- {sha224, ecdsa},
- {sha224, rsa},
- %% SHA
- {sha, ecdsa},
- {sha, rsa},
- {sha, dsa}],
+ Default = [%% SHA2 ++ PSS
+ {sha512, ecdsa},
+ rsa_pss_pss_sha512,
+ rsa_pss_rsae_sha512,
+ {sha512, rsa},
+ {sha384, ecdsa},
+ rsa_pss_pss_sha384,
+ rsa_pss_rsae_sha384,
+ {sha384, rsa},
+ {sha256, ecdsa},
+ rsa_pss_pss_sha256,
+ rsa_pss_rsae_sha256,
+ {sha256, rsa},
+ {sha224, ecdsa},
+ {sha224, rsa},
+ %% SHA
+ {sha, ecdsa},
+ {sha, rsa},
+ {sha, dsa}],
signature_algs(Version, Default);
default_signature_algs(_) ->
undefined.
+default_pre_1_3_signature_algs_only() ->
+ Default = [%% SHA2
+ {sha512, ecdsa},
+ {sha512, rsa},
+ {sha384, ecdsa},
+ {sha384, rsa},
+ {sha256, ecdsa},
+ {sha256, rsa},
+ {sha224, ecdsa},
+ {sha224, rsa},
+ %% SHA
+ {sha, ecdsa},
+ {sha, rsa},
+ {sha, dsa}],
+ signature_algs({3,3}, Default).
+
signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version)
andalso Version >= {3, 3} ->
@@ -898,7 +920,7 @@ signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version)
RSAPSSSupported = lists:member(rsa_pkcs1_pss_padding,
proplists:get_value(rsa_opts, CryptoSupports)),
Fun = fun (Scheme, Acc) when is_atom(Scheme) ->
- {Hash0, Sign0, Curve} =
+ {Hash, Sign0, Curve} =
ssl_cipher:scheme_to_components(Scheme),
Sign = case Sign0 of
rsa_pkcs1 ->
@@ -909,11 +931,6 @@ signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version)
rsa;
S -> S
end,
- Hash = case Hash0 of
- sha1 ->
- sha;
- H -> H
- end,
case proplists:get_bool(Sign, PubKeys)
andalso
(proplists:get_bool(Hash, Hashes)
@@ -957,7 +974,8 @@ signature_schemes(_, _) ->
[].
default_signature_schemes(Version) ->
- Default = [
+ Default = [eddsa_ed25519,
+ eddsa_ed448,
ecdsa_secp521r1_sha512,
ecdsa_secp384r1_sha384,
ecdsa_secp256r1_sha256,
@@ -966,9 +984,7 @@ default_signature_schemes(Version) ->
rsa_pss_pss_sha256,
rsa_pss_rsae_sha512,
rsa_pss_rsae_sha384,
- rsa_pss_rsae_sha256,
- eddsa_ed25519,
- eddsa_ed448
+ rsa_pss_rsae_sha256
],
signature_schemes(Version, Default).