summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ssl/src/Makefile2
-rw-r--r--lib/ssl/src/dtls_connection.erl281
-rw-r--r--lib/ssl/src/dtls_socket.erl2
-rw-r--r--lib/ssl/src/ssl.app.src2
-rw-r--r--lib/ssl/src/ssl.erl45
-rw-r--r--lib/ssl/src/ssl_connection.erl2174
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl1985
-rw-r--r--lib/ssl/src/tls_connection.erl370
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl131
-rw-r--r--lib/ssl/src/tls_connection_sup.erl4
-rw-r--r--lib/ssl/src/tls_gen_connection.erl63
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl66
-rw-r--r--lib/ssl/src/tls_sender.erl2
-rw-r--r--lib/ssl/src/tls_socket.erl6
-rw-r--r--lib/ssl/test/ssl_alpn_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_test_lib.erl6
-rw-r--r--lib/ssl/test/tls_api_SUITE.erl36
17 files changed, 2713 insertions, 2464 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 72dc629ca5..64d4ef1559 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -74,6 +74,7 @@ MODULES= \
ssl_dist_admin_sup \
ssl_dist_connection_sup \
ssl_dist_sup \
+ ssl_gen_statem \
ssl_handshake \
ssl_listen_tracker_sup \
ssl_logger \
@@ -94,7 +95,6 @@ MODULES= \
tls_connection_1_3 \
tls_handshake \
tls_handshake_1_3 \
- tls_gen_connection\
tls_record \
tls_record_1_3 \
tls_client_ticket_store \
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 7b4b1109df..59ee6634ef 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -23,6 +23,9 @@
-behaviour(gen_statem).
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
-include("dtls_connection.hrl").
-include("dtls_handshake.hrl").
-include("ssl_alert.hrl").
@@ -31,34 +34,57 @@
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
%% Internal application API
%% Setup
--export([start_fsm/8, start_link/7, init/1, pids/1]).
+-export([start_fsm/8,
+ start_link/7,
+ init/1,
+ pids/1]).
%% State transition handling
--export([next_event/3, next_event/4, handle_protocol_record/3]).
+-export([next_event/3,
+ next_event/4,
+ handle_protocol_record/3]).
%% Handshake handling
--export([renegotiate/2, send_handshake/2,
- queue_handshake/2, queue_change_cipher/2,
- reinit/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]).
+-export([renegotiate/2,
+ send_handshake/2,
+ queue_handshake/2,
+ queue_change_cipher/2,
+ reinit/1,
+ reinit_handshake_data/1,
+ select_sni_extension/1,
+ empty_connection_state/2]).
%% Alert and close handling
--export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
+-export([encode_alert/3,
+ send_alert/2,
+ send_alert_in_connection/2,
+ close/5,
+ protocol_name/0]).
%% Data handling
-export([socket/4, setopts/3, getopts/3]).
%% gen_statem state functions
--export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, user_hello/3, wait_ocsp_stapling/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+-export([initial_hello/3,
+ config_error/3,
+ downgrade/3,
+ hello/3,
+ user_hello/3,
+ wait_ocsp_stapling/3,
+ certify/3,
+ cipher/3,
+ abbreviated/3,
connection/3]).
+
%% gen_statem callbacks
--export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
+-export([callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
%%====================================================================
%% Internal application API
@@ -72,8 +98,8 @@ start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
try
{ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
- ssl_connection:handshake(SslSocket, Timeout)
+ {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
+ ssl_gen_statem:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
Error
@@ -93,12 +119,12 @@ 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),
try
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- gen_statem:enter_loop(?MODULE, [], init, State)
+ 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}},
- gen_statem:enter_loop(?MODULE, [], error, EState)
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
pids(_) ->
@@ -171,7 +197,7 @@ next_event(StateName, no_record,
#state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
case next_record(State0) of
{no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
+ ssl_gen_statem:hibernate_after(StateName, State, Actions);
{#ssl_tls{epoch = CurrentEpoch,
type = ?HANDSHAKE,
version = Version} = Record, State1} ->
@@ -244,12 +270,12 @@ next_event(StateName, Record,
%%% DTLS record protocol level application data messages
handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
- case ssl_connection:read_application_data(Data, State0) of
+ case ssl_gen_statem:read_application_data(Data, State0) of
{stop, _, _} = Stop->
Stop;
{Record, State1} ->
{next_state, StateName, State, Actions} = next_event(StateName0, Record, State1),
- ssl_connection:hibernate_after(StateName, State, Actions)
+ ssl_gen_statem:hibernate_after(StateName, State, Actions)
end;
%%% DTLS record protocol level handshake messages
handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
@@ -426,13 +452,13 @@ getopts(Transport, Socket, Tag) ->
%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-init(enter, _, State) ->
+initial_hello(enter, _, State) ->
{keep_state, State};
-init({call, From}, {start, Timeout},
+initial_hello({call, From}, {start, Timeout},
#state{static_env = #static_env{host = Host,
port = Port,
role = client,
@@ -459,33 +485,28 @@ init({call, From}, {start, Timeout},
session = Session,
start_or_recv_from = From},
next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
-init({call, _} = Type, Event, #state{static_env = #static_env{role = server},
+initial_hello({call, _} = Type, Event, #state{static_env = #static_env{role = server},
protocol_specific = PS} = State) ->
- Result = gen_handshake(?FUNCTION_NAME, Type, Event,
- State#state{protocol_specific = PS#{current_cookie_secret => dtls_v1:cookie_secret(),
- previous_cookie_secret => <<>>,
- ignored_alerts => 0,
- max_ignored_alerts => 10}}),
+ Result = ssl_gen_statem:?FUNCTION_NAME(Type, Event,
+ State#state{protocol_specific =
+ PS#{current_cookie_secret => dtls_v1:cookie_secret(),
+ previous_cookie_secret => <<>>,
+ ignored_alerts => 0,
+ max_ignored_alerts => 10}}),
erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
Result;
-init(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-error(enter, _, State) ->
+config_error(enter, _, State) ->
{keep_state, State};
-error({call, From}, {start, _Timeout},
- #state{protocol_specific = #{error := Error}} = State) ->
- {stop_and_reply, {shutdown, normal},
- [{reply, From, {error, Error}}], State};
-error({call, _} = Call, Msg, State) ->
- gen_handshake(?FUNCTION_NAME, Call, Msg, State);
-error(_, _, _) ->
- {keep_state_and_data, [postpone]}.
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
@@ -506,23 +527,27 @@ hello(internal, #client_hello{cookie = <<>>,
handshake_env = HsEnv,
connection_env = CEnv,
protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
- State1 = ssl_connection:handle_sni_extension(State0, Hello),
- {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
- Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
- %% FROM RFC 6347 regarding HelloVerifyRequest message:
- %% The server_version field has the same syntax as in TLS. However, in
- %% order to avoid the requirement to do version negotiation in the
- %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS
- %% version 1.0 regardless of the version of TLS that is expected to be
- %% negotiated.
- VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
- State2 = prepare_flight(State1#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
- {State, Actions} = send_handshake(VerifyRequest, State2),
- next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{
- tls_handshake_history =
- ssl_handshake:init_handshake_history()}},
- Actions);
+ case ssl_connection:handle_sni_extension(State0, Hello) of
+ #state{} = State1 ->
+ {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
+ Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
+ %% FROM RFC 6347 regarding HelloVerifyRequest message:
+ %% The server_version field has the same syntax as in TLS. However, in
+ %% order to avoid the requirement to do version negotiation in the
+ %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS
+ %% version 1.0 regardless of the version of TLS that is expected to be
+ %% negotiated.
+ VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
+ State2 = prepare_flight(State1#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
+ {State, Actions} = send_handshake(VerifyRequest, State2),
+ next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history()}},
+ Actions);
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version,?FUNCTION_NAME, State0)
+ end;
hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client,
host = Host,
port = Port},
@@ -549,14 +574,18 @@ hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #sta
State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version} % RequestedVersion
},
next_event(?FUNCTION_NAME, no_record, State, Actions);
-hello(internal, #client_hello{extensions = Extensions} = Hello,
+hello(internal, #client_hello{extensions = Extensions, client_version = ClientVersion} = Hello,
#state{ssl_options = #{handshake := hello},
handshake_env = HsEnv,
start_or_recv_from = From} = State0) ->
- State = ssl_connection:handle_sni_extension(State0, Hello),
- {next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{hello = Hello}},
- [{reply, From, {ok, Extensions}}]};
+ case ssl_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}}]};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ClientVersion, ?FUNCTION_NAME, State0)
+ end;
hello(internal, #server_hello{extensions = Extensions} = Hello,
#state{ssl_options = #{
handshake := hello},
@@ -571,8 +600,7 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #sta
socket = Socket},
protocol_specific = #{current_cookie_secret := Secret,
previous_cookie_secret := PSecret}
- } = State0) ->
- State = ssl_connection:handle_sni_extension(State0, Hello),
+ } = State) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
case dtls_handshake:cookie(Secret, IP, Port, Hello) of
Cookie ->
@@ -677,7 +705,7 @@ certify(enter, _, State0) ->
certify(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
certify(internal = Type, #server_hello_done{} = Event, State) ->
- ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event, prepare_flight(State));
certify(internal, #change_cipher_spec{type = <<1>>}, State0) ->
{State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
{next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
@@ -701,17 +729,16 @@ cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event,
#state{connection_states = ConnectionStates0} = State) ->
ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read),
ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read),
- ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates});
cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates,
protocol_specific = PS} = State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event,
- prepare_flight(State#state{connection_states = ConnectionStates,
- protocol_specific = PS#{flight_state => connection}}),
- ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event,
+ prepare_flight(State#state{connection_states = ConnectionStates,
+ protocol_specific = PS#{flight_state => connection}}));
cipher(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -761,16 +788,16 @@ connection(internal, #client_hello{}, #state{static_env = #static_env{role = ser
handshake_env = #handshake_env{allow_renegotiate = false}} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
State1 = send_alert(Alert, State0),
- {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE),
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, ?MODULE),
next_event(?FUNCTION_NAME, Record, State);
connection({call, From}, {application_data, Data}, State) ->
try
send_application_data(Data, From, ?FUNCTION_NAME, State)
catch throw:Error ->
- ssl_connection:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}])
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}])
end;
connection(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State).
%%TODO does this make sense for DTLS ?
%%--------------------------------------------------------------------
@@ -780,7 +807,7 @@ connection(Type, Event, State) ->
downgrade(enter, _, State) ->
{keep_state, State};
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
%% gen_statem callbacks
@@ -789,13 +816,13 @@ callback_mode() ->
[state_functions, state_enter].
terminate(Reason, StateName, State) ->
- ssl_connection:terminate(Reason, StateName, State).
+ ssl_gen_statem:terminate(Reason, StateName, State).
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
format_status(Type, Data) ->
- ssl_connection:format_status(Type, Data).
+ ssl_gen_statem:format_status(Type, Data).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -903,40 +930,40 @@ dtls_version(hello, Version, #state{static_env = #static_env{role = server},
dtls_version(_,_, State) ->
State.
-handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
- #state{connection_states = ConnectionStates0,
- static_env = #static_env{trackers = Trackers},
- handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
- renegotiation = {Renegotiation, _},
- negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = CEnv,
- session = #session{own_certificates = OwnCerts} = Session0,
- ssl_options = SslOpts} = State0) ->
- SessionTracker = proplists:get_value(session_id_tracker, Trackers),
- case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0,
- ConnectionStates0, OwnCerts, KeyExAlg}, Renegotiation) of
- #alert{} = Alert ->
- handle_own_alert(Alert, ClientVersion, hello, State0);
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
-
- State = prepare_flight(State0#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = Version},
- handshake_env = HsEnv#handshake_env{
- hashsign_algorithm = HashSign,
+handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) ->
+ case ssl_connection:handle_sni_extension(State0, Hello) of
+ #state{connection_states = ConnectionStates0,
+ static_env = #static_env{trackers = Trackers},
+ handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = CEnv,
+ session = #session{own_certificates = OwnCerts} = Session0,
+ ssl_options = SslOpts} = State1 ->
+ SessionTracker = proplists:get_value(session_id_tracker, Trackers),
+ case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0,
+ ConnectionStates0, OwnCerts, KeyExAlg}, Renegotiation) of
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ClientVersion, hello, State1);
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+
+ State = prepare_flight(State0#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
client_hello_version = ClientVersion,
- negotiated_protocol = Protocol},
- session = Session}),
-
- ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt},
- State, ?MODULE)
- end.
-
-
+ negotiated_protocol = Protocol},
+ session = Session}),
+ {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}
+ end;
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ClientVersion, hello, State0)
+ end.
%% raw data from socket, unpack records
handle_info({Protocol, _, _, _, Data}, StateName,
#state{static_env = #static_env{role = Role,
@@ -945,7 +972,7 @@ handle_info({Protocol, _, _, _, Data}, StateName,
{Record, State} ->
next_event(StateName, Record, State);
#alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
{stop, {shutdown, own_alert}, State0}
end;
@@ -982,7 +1009,7 @@ handle_info({CloseTag, Socket}, StateName,
ok
end,
Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
{stop, {shutdown, transport_closed}, State};
true ->
%% Fixes non-delivery of final DTLS record in {active, once}.
@@ -999,7 +1026,7 @@ handle_info(new_cookie_secret, StateName,
CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(),
previous_cookie_secret => Secret}}};
handle_info(Msg, StateName, State) ->
- ssl_connection:StateName(info, Msg, State, ?MODULE).
+ ssl_gen_statem:handle_info(Msg, StateName, State).
handle_state_timeout(flight_retransmission_timeout, StateName,
#state{protocol_specific =
@@ -1015,9 +1042,9 @@ handle_alerts([], Result) ->
handle_alerts(_, {stop, _, _} = Stop) ->
Stop;
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State));
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)).
handle_own_alert(Alert, Version, StateName,
#state{static_env = #static_env{data_tag = udp,
@@ -1028,10 +1055,10 @@ handle_own_alert(Alert, Version, StateName,
log_ignore_alert(LogLevel, StateName, Alert, Role),
{next_state, StateName, State};
{false, State} ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State)
end;
handle_own_alert(Alert, Version, StateName, State) ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State).
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State).
encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) ->
Fragments = lists:map(fun(Handshake) ->
@@ -1047,12 +1074,12 @@ decode_alerts(Bin) ->
gen_handshake(StateName, Type, Event,
#state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try ssl_connection:StateName(Type, Event, State, ?MODULE) of
+ try ssl_connection:StateName(Type, Event, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
@@ -1063,7 +1090,7 @@ gen_info(Event, connection = StateName, #state{connection_env = #connection_env
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
malformed_data),
Version, StateName, State)
end;
@@ -1074,7 +1101,7 @@ gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_ve
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
@@ -1277,9 +1304,9 @@ send_application_data(Data, From, _StateName,
case send(Transport, Socket, Msgs) of
ok ->
ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
- ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]);
+ ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]);
Result ->
- ssl_connection:hibernate_after(connection, State, [{reply, From, Result}])
+ ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}])
end
end.
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index 647acf7dfe..f1569f5069 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -85,7 +85,7 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo
inet_ssl = SocketOpts}, Timeout) ->
case Transport:open(0, SocketOpts ++ internal_inet_values()) of
{ok, Socket} ->
- ssl_connection:connect(ConnectionCb, Address, Port, {{Address, Port},Socket},
+ ssl_gen_statem:connect(ConnectionCb, Address, Port, {{Address, Port},Socket},
{SslOpts,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 51fa3172e0..0326161fe2 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -5,7 +5,6 @@
%% TLS/SSL
tls_connection,
tls_connection_1_3,
- tls_gen_connection,
tls_handshake,
tls_handshake_1_3,
tls_record,
@@ -37,6 +36,7 @@
%% Both TLS/SSL and DTLS
ssl_config,
ssl_connection,
+ ssl_gen_statem,
ssl_handshake,
ssl_record,
ssl_cipher,
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 7718de1b7b..65f2e60aba 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -20,7 +20,8 @@
%%
-%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl
+%%% Purpose : Main API module for the SSL application that implements TLS and DTLS
+%%% SSL is a legacy name.
-module(ssl).
@@ -734,7 +735,7 @@ handshake(ListenSocket) ->
handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity) ->
- ssl_connection:handshake(Socket, Timeout);
+ ssl_gen_statem:handshake(Socket, Timeout);
%% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to
%% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns
@@ -764,7 +765,7 @@ handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when
try
Tracker = proplists:get_value(option_tracker, Trackers),
{ok, EmOpts, _} = tls_socket:get_all_opts(Tracker),
- ssl_connection:handshake(Socket, {SslOpts,
+ ssl_gen_statem:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
@@ -773,7 +774,7 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout)
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
{ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid),
- ssl_connection:handshake(Socket, {SslOpts,
+ ssl_gen_statem:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
@@ -791,7 +792,7 @@ handshake(Socket, SslOptions, Timeout) when is_port(Socket),
ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
{ok, Port} = tls_socket:port(Transport, Socket),
{ok, SessionIdHandle} = tls_socket:session_id_tracker(SslOpts),
- ssl_connection:handshake(ConnetionCb, Port, Socket,
+ ssl_gen_statem:handshake(ConnetionCb, Port, Socket,
{SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{}),
[{session_id_tracker, SessionIdHandle}]},
@@ -827,14 +828,14 @@ handshake_continue(Socket, SSLOptions) ->
%% Description: Continues the handshke possible with newly supplied options.
%%--------------------------------------------------------------------
handshake_continue(Socket, SSLOptions, Timeout) ->
- ssl_connection:handshake_continue(Socket, SSLOptions, Timeout).
+ ssl_gen_statem:handshake_continue(Socket, SSLOptions, Timeout).
%%--------------------------------------------------------------------
-spec handshake_cancel(#sslsocket{}) -> any().
%%
%% Description: Cancels the handshakes sending a close alert.
%%--------------------------------------------------------------------
handshake_cancel(Socket) ->
- ssl_connection:handshake_cancel(Socket).
+ ssl_gen_statem:handshake_cancel(Socket).
%%--------------------------------------------------------------------
-spec close(SslSocket) -> ok | {error, Reason} when
@@ -844,7 +845,7 @@ handshake_cancel(Socket) ->
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT});
+ ssl_gen_statem:close(Pid, {close, ?DEFAULT_TIMEOUT});
close(#sslsocket{pid = {dtls, #config{dtls_handler = {_, _}}}} = DTLSListen) ->
dtls_socket:close(DTLSListen);
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}) ->
@@ -862,7 +863,7 @@ close(#sslsocket{pid = [TLSPid|_]},
{Pid, Timeout} = DownGrade) when is_pid(TLSPid),
is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- case ssl_connection:close(TLSPid, {close, DownGrade}) of
+ case ssl_gen_statem:close(TLSPid, {close, DownGrade}) of
ok -> %% In normal close {error, closed} is regarded as ok, as it is not interesting which side
%% that got to do the actual close. But in the downgrade case only {ok, Port} is a sucess.
{error, closed};
@@ -871,7 +872,7 @@ close(#sslsocket{pid = [TLSPid|_]},
end;
close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- ssl_connection:close(TLSPid, {close, Timeout});
+ ssl_gen_statem:close(TLSPid, {close, Timeout});
close(#sslsocket{pid = {dtls = ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
dtls_socket:close(Transport, ListenSocket);
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
@@ -885,7 +886,7 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}
%% Description: Sends data over the ssl connection
%%--------------------------------------------------------------------
send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) ->
- ssl_connection:send(Pid, Data);
+ ssl_gen_statem:send(Pid, Data);
send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) ->
tls_sender:send_data(Pid, erlang:iolist_to_iovec(Data));
send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) ->
@@ -918,7 +919,7 @@ recv(Socket, Length) ->
recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
- ssl_connection:recv(Pid, Length, Timeout);
+ ssl_gen_statem:recv(Pid, Length, Timeout);
recv(#sslsocket{pid = {dtls,_}}, _, _) ->
{error,enotconn};
recv(#sslsocket{pid = {Listen,
@@ -936,7 +937,7 @@ recv(#sslsocket{pid = {Listen,
%% or once.
%%--------------------------------------------------------------------
controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
- ssl_connection:new_user(Pid, NewOwner);
+ ssl_gen_statem:new_user(Pid, NewOwner);
controlling_process(#sslsocket{pid = {dtls, _}},
NewOwner) when is_pid(NewOwner) ->
ok; %% Meaningless but let it be allowed to conform with TLS
@@ -956,7 +957,7 @@ controlling_process(#sslsocket{pid = {Listen,
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- case ssl_connection:connection_information(Pid, false) of
+ case ssl_gen_statem:connection_information(Pid, false) of
{ok, Info} ->
{ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]};
Error ->
@@ -976,7 +977,7 @@ connection_information(#sslsocket{pid = {dtls,_}}) ->
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) ->
- case ssl_connection:connection_information(Pid, include_security_info(Items)) of
+ case ssl_gen_statem:connection_information(Pid, include_security_info(Items)) of
{ok, Info} ->
{ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items),
Value =/= undefined]};
@@ -1012,7 +1013,7 @@ peername(#sslsocket{pid = {dtls,_}}) ->
%% Description: Returns the peercert.
%%--------------------------------------------------------------------
peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- case ssl_connection:peer_certificate(Pid) of
+ case ssl_gen_statem:peer_certificate(Pid) of
{ok, undefined} ->
{error, no_peercert};
Result ->
@@ -1033,7 +1034,7 @@ peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
%% protocol has been negotiated will return {error, protocol_not_negotiated}
%%--------------------------------------------------------------------
negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- ssl_connection:negotiated_protocol(Pid).
+ ssl_gen_statem:negotiated_protocol(Pid).
%%--------------------------------------------------------------------
-spec cipher_suites() -> [old_cipher_suite()] | [string()].
@@ -1210,7 +1211,7 @@ groups(default) ->
%% Description: Gets options
%%--------------------------------------------------------------------
getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) ->
- ssl_connection:get_opts(Pid, OptionTags);
+ ssl_gen_statem:get_opts(Pid, OptionTags);
getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) ->
try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of
{ok, _} = Result ->
@@ -1248,11 +1249,11 @@ setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Opt
Options ->
case proplists:get_value(packet, Options, undefined) of
undefined ->
- ssl_connection:set_opts(Pid, Options);
+ ssl_gen_statem:set_opts(Pid, Options);
PacketOpt ->
case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of
ok ->
- ssl_connection:set_opts(Pid, Options);
+ ssl_gen_statem:set_opts(Pid, Options);
Error ->
Error
end
@@ -1265,7 +1266,7 @@ setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0)
try proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Options0) of
Options ->
- ssl_connection:set_opts(Pid, Options)
+ ssl_gen_statem:set_opts(Pid, Options)
catch
_:_ ->
{error, {options, {not_a_proplist, Options0}}}
@@ -1341,7 +1342,7 @@ shutdown(#sslsocket{pid = {Listen, #config{transport_info = Info}}},
shutdown(#sslsocket{pid = {dtls,_}},_) ->
{error, enotconn};
shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) ->
- ssl_connection:shutdown(Pid, How).
+ ssl_gen_statem:shutdown(Pid, How).
%%--------------------------------------------------------------------
-spec sockname(SslSocket) ->
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 3128154f1a..9a1dd239aa 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -26,6 +26,9 @@
-module(ssl_connection).
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
-include("ssl_api.hrl").
-include("ssl_connection.hrl").
-include("ssl_handshake.hrl").
@@ -34,294 +37,49 @@
-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
-
-%% Setup
-
--export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5,
- handshake_continue/3, handshake_cancel/1,
- socket_control/4, socket_control/5]).
-
-%% User Events
--export([send/2, recv/3, close/2, shutdown/2,
- new_user/2, get_opts/2, set_opts/2,
- peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5,
- connection_information/2
- ]).
-%% Alert and close handling
--export([handle_own_alert/4, handle_alert/3,
- handle_normal_shutdown/3,
- handle_trusted_certs_db/1,
- maybe_invalidate_session/6]).
+%% TLS-1.0 to TLS-1.2 Specific User Events
+-export([renegotiation/1, prf/5]).
-%% Data handling
--export([read_application_data/2, internal_renegotiation/2]).
+%% Data handling. Note renegotiation is replaced by sesion key update mechanism in TLS-1.3
+-export([internal_renegotiation/2]).
%% Help functions for tls|dtls_connection.erl
--export([handle_session/7, handle_sni_extension/2,
- handle_sni_extension_tls13/2, ssl_config/3,
- prepare_connection/2, hibernate_after/3]).
-
-%% General gen_statem state functions with extra callback argument
-%% to determine if it is an SSL/TLS or DTLS gen_statem machine, and gen_handshake that wrapps
-%% handling of common state handling for handshake messages
--export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, wait_ocsp_stapling/4, cipher/4,
- connection/4, downgrade/4, gen_handshake/5]).
-
-%% gen_statem callbacks
--export([terminate/3, format_status/2]).
-
-%% Erlang Distribution export
--export([dist_handshake_complete/2]).
-
-%%====================================================================
-%% Setup
-%%====================================================================
-%%--------------------------------------------------------------------
--spec connect(tls_connection | dtls_connection,
- ssl:host(), inet:port_number(),
- port() | {tuple(), port()}, %% TLS | DTLS
- {ssl_options(), #socket_options{},
- %% Tracker only needed on server side
- undefined},
- pid(), tuple(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Connect to an ssl server.
-%%--------------------------------------------------------------------
-connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
- try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
- Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssl_not_started}
- end.
-%%--------------------------------------------------------------------
--spec handshake(tls_connection | dtls_connection,
- inet:port_number(), port(),
- {ssl_options(), #socket_options{}, list()},
- pid(), tuple(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Performs accept on an ssl listen socket. e.i. performs
-%% ssl handshake.
-%%--------------------------------------------------------------------
-handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
- try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
- CbInfo, Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssl_not_started}
- end.
-
-%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {ok, #sslsocket{}, map()}| {error, reason()}.
-%%
-%% Description: Starts ssl handshake.
-%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
- case call(Pid, {start, Timeout}) of
- connected ->
- {ok, Socket};
- {ok, Ext} ->
- {ok, Socket, no_records(Ext)};
- Error ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) ->
- {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}.
+-export([handle_session/7,
+ handle_sni_extension/2]).
+
+%% General state handlingfor TLS-1.0 to TLS-1.2 and gen_handshake that wrapps
+%% handling of common state handling for handshake messages for error handling
+-export([hello/3,
+ user_hello/3,
+ abbreviated/3,
+ certify/3,
+ wait_ocsp_stapling/3,
+ cipher/3,
+ connection/3,
+ downgrade/3,
+ gen_handshake/4]).
+
+%%--------------------------------------------------------------------
+-spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
+ ok.
%%
-%% Description: Starts ssl handshake with some new options
-%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
- case call(Pid, {start, SslOptions, Timeout}) of
- connected ->
- {ok, Socket};
- Error ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
- timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
-%%
-%% Description: Continues handshake with new options
-%%--------------------------------------------------------------------
-handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
- case call(Pid, {handshake_continue, SslOptions, Timeout}) of
- connected ->
- {ok, Socket};
- Error ->
- Error
- end.
-%%--------------------------------------------------------------------
--spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}.
-%%
-%% Description: Cancels connection
+%% Description: Starts a renegotiation of the ssl session.
%%--------------------------------------------------------------------
-handshake_cancel(#sslsocket{pid = [Pid|_]}) ->
- case call(Pid, cancel) of
- closed ->
- ok;
- Error ->
- Error
- end.
-%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Set the ssl process to own the accept socket
-%%--------------------------------------------------------------------
-socket_control(Connection, Socket, Pid, Transport) ->
- socket_control(Connection, Socket, Pid, Transport, undefined).
-
-%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%--------------------------------------------------------------------
-socket_control(Connection, Socket, Pids, Transport, udp_listener) ->
- %% dtls listener process must have the socket control
- {ok, Connection:socket(Pids, Transport, Socket, undefined)};
-
-socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
- case Transport:controlling_process(Socket, Pid) of
- ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
- {error, Reason} ->
- {error, Reason}
- end;
-socket_control(dtls_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
- case Transport:controlling_process(Socket, Pid) of
- ok ->
- {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)};
- {error, Reason} ->
- {error, Reason}
- end.
-
+internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
+ gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
%%====================================================================
%% User events
%%====================================================================
%%--------------------------------------------------------------------
--spec send(pid(), iodata()) -> ok | {error, reason()}.
-%%
-%% Description: Sends data over the ssl connection
-%%--------------------------------------------------------------------
-send(Pid, Data) ->
- call(Pid, {application_data,
- %% iolist_to_iovec should really
- %% be called iodata_to_iovec()
- erlang:iolist_to_iovec(Data)}).
-
-%%--------------------------------------------------------------------
--spec recv(pid(), integer(), timeout()) ->
- {ok, binary() | list()} | {error, reason()}.
-%%
-%% Description: Receives data when active = false
-%%--------------------------------------------------------------------
-recv(Pid, Length, Timeout) ->
- call(Pid, {recv, Length, Timeout}).
-
-%%--------------------------------------------------------------------
--spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Get connection information
-%%--------------------------------------------------------------------
-connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) ->
- case call(Pid, {connection_information, IncludeSecrityInfo}) of
- {ok, Info} when IncludeSecrityInfo == true ->
- {ok, maybe_add_keylog(Info)};
- Other ->
- Other
- end.
-
-%%--------------------------------------------------------------------
--spec close(pid(), {close, Timeout::integer() |
- {NewController::pid(), Timeout::integer()}}) ->
- ok | {ok, port()} | {error, reason()}.
-%%
-%% Description: Close an ssl connection
-%%--------------------------------------------------------------------
-close(ConnectionPid, How) ->
- case call(ConnectionPid, How) of
- {error, closed} ->
- ok;
- Other ->
- Other
- end.
-%%--------------------------------------------------------------------
--spec shutdown(pid(), atom()) -> ok | {error, reason()}.
-%%
-%% Description: Same as gen_tcp:shutdown/2
-%%--------------------------------------------------------------------
-shutdown(ConnectionPid, How) ->
- call(ConnectionPid, {shutdown, How}).
-
-%%--------------------------------------------------------------------
--spec new_user(pid(), pid()) -> ok | {error, reason()}.
-%%
-%% Description: Changes process that receives the messages when active = true
-%% or once.
-%%--------------------------------------------------------------------
-new_user(ConnectionPid, User) ->
- call(ConnectionPid, {new_user, User}).
-
-%%--------------------------------------------------------------------
--spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}.
-%%
-%% Description: Returns the negotiated protocol
-%%--------------------------------------------------------------------
-negotiated_protocol(ConnectionPid) ->
- call(ConnectionPid, negotiated_protocol).
-
-%%--------------------------------------------------------------------
--spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Same as inet:getopts/2
-%%--------------------------------------------------------------------
-get_opts(ConnectionPid, OptTags) ->
- call(ConnectionPid, {get_opts, OptTags}).
-%%--------------------------------------------------------------------
--spec set_opts(pid(), list()) -> ok | {error, reason()}.
-%%
-%% Description: Same as inet:setopts/2
-%%--------------------------------------------------------------------
-set_opts(ConnectionPid, Options) ->
- call(ConnectionPid, {set_opts, Options}).
-
-%%--------------------------------------------------------------------
--spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
-%%
-%% Description: Returns the peer cert
-%%--------------------------------------------------------------------
-peer_certificate(ConnectionPid) ->
- call(ConnectionPid, peer_certificate).
-
-%%--------------------------------------------------------------------
-spec renegotiation(pid()) -> ok | {error, reason()}.
%%
%% Description: Starts a renegotiation of the ssl session.
%%--------------------------------------------------------------------
renegotiation(ConnectionPid) ->
- call(ConnectionPid, renegotiate).
-
-%%--------------------------------------------------------------------
--spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
- ok.
-%%
-%% Description: Starts a renegotiation of the ssl session.
-%%--------------------------------------------------------------------
-internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
- gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
-
-dist_handshake_complete(ConnectionPid, DHandle) ->
- gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
+ ssl_gen_statem:call(ConnectionPid, renegotiate).
%%--------------------------------------------------------------------
-spec prf(pid(), binary() | 'master_secret', binary(),
@@ -331,423 +89,7 @@ dist_handshake_complete(ConnectionPid, DHandle) ->
%% Description: use a ssl sessions TLS PRF to generate key material
%%--------------------------------------------------------------------
prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
- call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
-
-
-%%====================================================================
-%% Alert and close handling
-%%====================================================================
-handle_own_alert(Alert0, _, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- ssl_options = #{log_level := LogLevel}} = State) ->
- try %% Try to tell the other side
- send_alert(Alert0, StateName, State)
- catch _:_ -> %% Can crash if we are in a uninitialized state
- ignore
- end,
- try %% Try to tell the local user
- Alert = Alert0#alert{role = Role},
- log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert),
- handle_normal_shutdown(Alert,StateName, State)
- catch _:_ ->
- ok
- end,
- {stop, {shutdown, own_alert}, State}.
-
-handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
- socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- trackers = Trackers},
- handshake_env = #handshake_env{renegotiation = {false, first}},
- start_or_recv_from = StartFrom} = State) ->
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
-
-handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
- socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- trackers = Trackers},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- socket_options = Opts,
- start_or_recv_from = RecvFrom} = State) ->
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
-
-handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
- #state{static_env = #static_env{role = Role,
- socket = Socket,
- host = Host,
- port = Port,
- trackers = Trackers,
- transport_cb = Transport,
- protocol_cb = Connection},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- ssl_options = #{log_level := LogLevel},
- start_or_recv_from = From,
- session = Session,
- socket_options = Opts} = State) ->
- invalidate_session(Role, Host, Port, Session),
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role, Connection:protocol_name(),
- StateName, Alert),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
-
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- downgrade= StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, Alert}]};
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0,
- StateName, #state{static_env = #static_env{role = Role}} = State) ->
- Alert = Alert0#alert{role = opposite_role(Role)},
- handle_normal_shutdown(Alert, StateName, State),
- {stop,{shutdown, peer_close}, State};
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, internal}},
- ssl_options = #{log_level := LogLevel}} = State) ->
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert),
- handle_normal_shutdown(Alert, StateName, State),
- {stop,{shutdown, peer_close}, State};
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
- ssl_options = #{log_level := LogLevel}
- } = State0) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- State = Connection:reinit_handshake_data(State0),
- Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
- ssl_options = #{log_level := LogLevel}
- } = State0) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- %% Go back to connection!
- State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
- Connection:next_event(connection, no_record, State);
-
-%% Gracefully log and ignore all other warning alerts
-handle_alert(#alert{level = ?WARNING} = Alert, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- ssl_options = #{log_level := LogLevel}} = State) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName,
- Alert#alert{role = opposite_role(Role)}),
- Connection:next_event(StateName, no_record, State).
-
-maybe_invalidate_session(undefined,_, _, _, _, _) ->
- ok;
-maybe_invalidate_session({3, 4},_, _, _, _, _) ->
- ok;
-maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
- maybe_invalidate_session(Type, Role, Host, Port, Session).
-
-%%====================================================================
-%% Data handling
-%%====================================================================
-passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
- %% Assert! Erl distribution uses active sockets
- connection_env = #connection_env{erl_dist_handle = undefined}}
- = State0, StateName, Connection, StartTimerAction) ->
- case BufferSize of
- 0 ->
- Connection:next_event(StateName, no_record, State0, StartTimerAction);
- _ ->
- case read_application_data(State0, Front, BufferSize, Rear) of
- {stop, _, _} = ShutdownError ->
- ShutdownError;
- {Record, State} ->
- case State#state.start_or_recv_from of
- undefined ->
- %% Cancel recv timeout as data has been delivered
- Connection:next_event(StateName, Record, State,
- [{{timeout, recv}, infinity, timeout}]);
- _ ->
- Connection:next_event(StateName, Record, State, StartTimerAction)
- end
- end
- end.
-
-read_application_data(
- Data,
- #state{
- user_data_buffer = {Front0,BufferSize0,Rear0},
- connection_env = #connection_env{erl_dist_handle = DHandle}} = State) ->
- %%
- Front = Front0,
- BufferSize = BufferSize0 + byte_size(Data),
- Rear = [Data|Rear0],
- case DHandle of
- undefined ->
- read_application_data(State, Front, BufferSize, Rear);
- _ ->
- 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}}}
- end
- end.
-
-
-read_application_data(#state{
- socket_options = SocketOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) ->
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead).
-
-%% Pick binary from queue front, if empty wait for more data
-read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
- read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin);
-read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) ->
- 0 = BufferSize, % Assert
- {no_record, State#state{socket_options = SocketOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {Front,BufferSize,Rear}}};
-read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
- [Bin|Front] = lists:reverse(Rear),
- read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin).
-
-read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) ->
- %% Done with this binary - get next
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead);
-read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) ->
- %% Decode one packet from a binary
- case get_data(SocketOpts0, BytesToRead, Bin0) of
- {ok, Data, Bin} -> % Send data
- BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)),
- read_application_data_deliver(
- State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data);
- {more, undefined} ->
- %% We need more data, do not know how much
- if
- byte_size(Bin0) < BufferSize0 ->
- %% We have more data in the buffer besides the first binary - concatenate all and retry
- Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
- read_application_data_bin(
- State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin);
- true ->
- %% All data is in the first binary, no use to retry - wait for more
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}
- end;
- {more, Size} when Size =< BufferSize0 ->
- %% We have a packet in the buffer - collect it in a binary and decode
- {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]),
- Bin = iolist_to_binary(Data),
- read_application_data_bin(
- State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin);
- {more, _Size} ->
- %% We do not have a packet in the buffer - wait for more
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
- passive ->
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
- {error,_Reason} ->
- %% Invalid packet in packet mode
- #state{
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}}} = State,
- Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
- deliver_packet_error(
- Connection:pids(State), Transport, Socket, SocketOpts0,
- Buffer, Pid, RecvFrom, Trackers, Connection),
- {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Buffer],BufferSize0,[]}}}
- end.
-
-read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) ->
- #state{
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}}} = State,
- SocketOpts =
- deliver_app_data(
- Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection),
- if
- SocketOpts#socket_options.active =:= false ->
- %% Passive mode, wait for active once or recv
- {no_record,
- State#state{
- user_data_buffer = {Front,BufferSize,Rear},
- start_or_recv_from = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpts
- }};
- true -> %% Try to deliver more data
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined)
- end.
-
-
-read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) ->
- read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin);
-read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) ->
- BufferSize = 0,
- {Front,BufferSize,Rear};
-read_application_dist_data(DHandle, [], BufferSize, Rear) ->
- [Bin|Front] = lists:reverse(Rear),
- read_application_dist_data(DHandle, Front, BufferSize, [], Bin).
-%%
-read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
- case Bin0 of
- %%
- %% START Optimization
- %% It is cheaper to match out several packets in one match operation than to loop for each
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary,
- SizeC:32, DataC:SizeC/binary,
- SizeD:32, DataD:SizeD/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD ->
- %% We have 4 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- erlang:dist_ctrl_put_data(DHandle, DataC),
- erlang:dist_ctrl_put_data(DHandle, DataD),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary,
- SizeC:32, DataC:SizeC/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB, 0 < SizeC ->
- %% We have 3 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- erlang:dist_ctrl_put_data(DHandle, DataC),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB ->
- %% We have 2 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest);
- %% END Optimization
- %%
- %% Basic one packet code path
- <<Size:32, Data:Size/binary, Rest/binary>> ->
- %% We have a complete packet in the first binary
- 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
- read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
- <<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
- %% We have a complete packet in the buffer
- %% - fetch the missing content from the buffer front
- {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
- 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
- read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
- <<Bin/binary>> ->
- %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
- %% match out the whole binary which will trick the optimization into keeping the match context
- %% for the first binary contains complete packet code above
- case Bin of
- <<_Size:32, _InsufficientData/binary>> ->
- %% We have a length field in the first binary but there is not enough data
- %% in the buffer to form a complete packet - await more data
- {[Bin|Front0],BufferSize,Rear0};
- <<IncompleteLengthField/binary>> when 4 < BufferSize ->
- %% We do not have a length field in the first binary but the buffer
- %% contains enough data to maybe form a packet
- %% - fetch a tiny binary from the buffer front to complete the length field
- {LengthField,Front,Rear} =
- case IncompleteLengthField of
- <<>> ->
- iovec_from_front(4, Front0, Rear0, []);
- _ ->
- iovec_from_front(
- 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField])
- end,
- LengthBin = iolist_to_binary(LengthField),
- read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
- <<IncompleteLengthField/binary>> ->
- %% We do not have enough data in the buffer to even form a length field - await more data
- case IncompleteLengthField of
- <<>> ->
- {Front0,BufferSize,Rear0};
- _ ->
- {[IncompleteLengthField|Front0],BufferSize,Rear0}
- end
- end
- end.
-
-iovec_from_front(0, Front, Rear, Acc) ->
- {lists:reverse(Acc),Front,Rear};
-iovec_from_front(Size, [], Rear, Acc) ->
- case Rear of
- %% Avoid lists:reverse/1 for simple cases.
- %% Case clause for [] to avoid infinite loop.
- [_] ->
- iovec_from_front(Size, Rear, [], Acc);
- [Bin2,Bin1] ->
- iovec_from_front(Size, [Bin1,Bin2], [], Acc);
- [Bin3,Bin2,Bin1] ->
- iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc);
- [_,_,_|_] = Rear ->
- iovec_from_front(Size, lists:reverse(Rear), [], Acc)
- end;
-iovec_from_front(Size, [Bin|Front], Rear, []) ->
- case Bin of
- <<Last:Size/binary>> -> % Just enough
- {[Last],Front,Rear};
- <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
- {[Last],[Rest|Front],Rear};
- <<>> -> % Not enough, skip empty binaries
- iovec_from_front(Size, Front, Rear, []);
- <<_/binary>> -> % Not enough
- BinSize = byte_size(Bin),
- iovec_from_front(Size - BinSize, Front, Rear, [Bin])
- end;
-iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
- case Bin of
- <<Last:Size/binary>> -> % Just enough
- {lists:reverse(Acc, [Last]),Front,Rear};
- <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
- {lists:reverse(Acc, [Last]),[Rest|Front],Rear};
- <<>> -> % Not enough, skip empty binaries
- iovec_from_front(Size, Front, Rear, Acc);
- <<_/binary>> -> % Not enough
- BinSize = byte_size(Bin),
- iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
- end.
-
+ ssl_gen_statem:call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
%%====================================================================
%% Help functions for tls|dtls_connection.erl
@@ -792,145 +134,85 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
State#state{connection_states = ConnectionStates})
end.
-%%--------------------------------------------------------------------
--spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
-%%--------------------------------------------------------------------
-ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
- handshake_env = HsEnv,
- connection_env = CEnv} = State0) ->
- {ok, #{cert_db_ref := Ref,
- cert_db_handle := CertDbHandle,
- fileref_db_handle := FileRefHandle,
- session_cache := CacheHandle,
- crl_db_info := CRLDbHandle,
- private_key := Key,
- dh_params := DHParams,
- own_certificates := OwnCerts}} =
- ssl_config:init(Opts, Role),
- TimeStamp = erlang:monotonic_time(),
- Session = State0#state.session,
-
- State0#state{session = Session#session{own_certificates = OwnCerts,
- time_stamp = TimeStamp},
- static_env = InitStatEnv0#static_env{
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle
- },
- handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
- connection_env = CEnv#connection_env{private_key = Key},
- ssl_options = Opts}.
%%====================================================================
%% gen_statem general state functions with connection cb argument
%%====================================================================
-%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | {start, {list(), list()}, timeout()}| term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-
-init({call, From}, {start, Timeout}, State0, Connection) ->
- Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From},
- [{{timeout, handshake}, Timeout, close}]);
-init({call, From}, {start, {Opts, EmOpts}, Timeout},
- #state{static_env = #static_env{role = Role},
- ssl_options = OrigSSLOptions,
- socket_options = SockOpts} = State0, Connection) ->
- try
- SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
- State = ssl_config(SslOpts, Role, State0),
- init({call, From}, {start, Timeout},
- State#state{ssl_options = SslOpts,
- socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
- catch throw:Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
- end;
-init({call, From}, {new_user, _} = Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-init({call, From}, _Msg, _State, _Connection) ->
- {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]};
-init(_Type, _Event, _State, _Connection) ->
- {keep_state_and_data, [postpone]}.
-
-%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{},
- tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-error({call, From}, {close, _}, State, _Connection) ->
- {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
-error({call, From}, _Msg, State, _Connection) ->
- {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}.
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
- #hello_request{} | #server_hello{} | term(),
- #state{}, tls_connection | dtls_connection) ->
+ #hello_request{} | #server_hello{} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-hello({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) ->
- do_server_hello(Type, ServerHelloExt, State, Connection);
-hello(info, Msg, State, _) ->
+hello({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+hello(internal, {common_client_hello, Type, ServerHelloExt}, State) ->
+ do_server_hello(Type, ServerHelloExt, State);
+hello(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
-hello(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+hello(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+hello(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
-user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
+%%--------------------------------------------------------------------
+-spec user_hello(gen_statem:event_type(),
+ #hello_request{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
gen_statem:reply(From, ok),
- handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
Version, ?FUNCTION_NAME, State);
user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
#state{static_env = #static_env{role = Role},
handshake_env = #handshake_env{hello = Hello},
- ssl_options = Options0} = State0, _Connection) ->
+ ssl_options = Options0} = State0) ->
Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
- State = ssl_config(Options, Role, State0),
+ 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}]};
-user_hello(_, _, _, _) ->
+user_hello(_, _, _) ->
{keep_state_and_data, [postpone]}.
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(),
#hello_request{} | #finished{} | term(),
- #state{}, tls_connection | dtls_connection) ->
+ #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-abbreviated({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
+abbreviated({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = server},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
handshake_env = #handshake_env{tls_handshake_history = Hist,
expecting_finished = true} = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} =
- State0, Connection) ->
+ State0) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
get_current_prf(ConnectionStates0, write),
MasterSecret, Hist) of
verified ->
ConnectionStates =
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
- {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates,
- handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
+ {Record, State} =
+ ssl_gen_statem:prepare_connection(State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{expecting_finished = false}},
+ Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = client},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
handshake_env = #handshake_env{tls_handshake_history = Hist0},
connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret},
- connection_states = ConnectionStates0} = State0, Connection) ->
+ connection_states = ConnectionStates0} = State0) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
get_pending_prf(ConnectionStates0, write),
MasterSecret, Hist0) of
@@ -940,130 +222,144 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,
{#state{handshake_env = HsEnv} = State1, Actions} =
finalize_handshake(State0#state{connection_states = ConnectionStates1},
?FUNCTION_NAME, Connection),
- {Record, State} = prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
+ {Record, State} =
+ ssl_gen_statem:prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}},
+ Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State,
- Connection) ->
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
expecting_next_protocol_negotiation = false}});
abbreviated(internal,
#change_cipher_spec{type = <<1>>},
- #state{connection_states = ConnectionStates0,
- handshake_env = HsEnv} = State, Connection) ->
+ #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0,
+ handshake_env = HsEnv} = State) ->
ConnectionStates1 =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
- ConnectionStates1,
+ ConnectionStates1,
handshake_env = HsEnv#handshake_env{expecting_finished = true}});
-abbreviated(info, Msg, State, _) ->
+abbreviated(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
-abbreviated(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+abbreviated(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+abbreviated(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
-spec wait_ocsp_stapling(gen_statem:event_type(),
#certificate{} | #certificate_status{} | term(),
- #state{}, tls_connection | dtls_connection) ->
+ #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-wait_ocsp_stapling(internal, #certificate{}, State, Connection) ->
+wait_ocsp_stapling(internal, #certificate{}, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
%% Postpone message, should be handled in certify after receiving staple message
Connection:next_event(?FUNCTION_NAME, no_record, State, [{postpone, true}]);
%% Receive OCSP staple message
wait_ocsp_stapling(internal, #certificate_status{} = CertStatus,
- #state{handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State,
- Connection) ->
- Connection:next_event(certify, no_record, State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
- OcspState#{ocsp_expect => stapled,
- ocsp_response => CertStatus}}});
+ #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State) ->
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
+ OcspState#{ocsp_expect => stapled,
+ ocsp_response => CertStatus}}});
%% Server did not send OCSP staple message
-wait_ocsp_stapling(internal, Msg, #state{handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State, Connection)
+wait_ocsp_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State)
when is_record(Msg, server_key_exchange) orelse
is_record(Msg, hello_request) orelse
is_record(Msg, certificate_request) orelse
is_record(Msg, server_hello_done) orelse
is_record(Msg, client_key_exchange) ->
- Connection:next_event(certify, no_record, State#state{handshake_env =
- HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
[{postpone, true}]);
-wait_ocsp_stapling(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
+wait_ocsp_stapling(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+wait_ocsp_stapling(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(),
#hello_request{} | #certificate{} | #server_key_exchange{} |
#certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
- #state{}, tls_connection | dtls_connection) ->
+ #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-certify({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-certify(info, Msg, State, _) ->
+certify({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+certify(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
#state{static_env = #static_env{role = server},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = #{verify := verify_peer,
fail_if_no_peer_cert := true}} =
- State, _) ->
+ State) ->
Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided),
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
- #state{static_env = #static_env{role = server},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
ssl_options = #{verify := verify_peer,
fail_if_no_peer_cert := false}} =
- State0, Connection) ->
+ State0) ->
Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
certify(internal, #certificate{},
#state{static_env = #static_env{role = server},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = #{verify := verify_none}} =
- State, _) ->
+ State) ->
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{},
- #state{handshake_env = #handshake_env{
- ocsp_stapling_state = #{ocsp_expect := staple}}} = State, Connection) ->
+ #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = #{ocsp_expect := staple}}} = State) ->
Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]);
-certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, #state{static_env =
- #static_env{
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbInfo},
- handshake_env = #handshake_env{
- ocsp_stapling_state = #{ocsp_expect := Status} = OcspState},
- connection_env = #connection_env{
- negotiated_version = Version},
- ssl_options = Opts} = State, Connection) when Status =/= staple ->
+certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert,
+ #state{static_env = #static_env{
+ role = Role,
+ host = Host,
+ protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbInfo},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = #{ocsp_expect := Status} = OcspState},
+ connection_env = #connection_env{
+ negotiated_version = Version},
+ ssl_options = Opts} = State) 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, []);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ State#state{client_certificate_requested = false}, Connection, []);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
certify(internal, #server_key_exchange{exchange_keys = Keys},
- #state{static_env = #static_env{role = client},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
public_key_info = PubKeyInfo} = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
session = Session,
- connection_states = ConnectionStates} = State, Connection)
+ connection_states = ConnectionStates} = State)
when KexAlg == dhe_dss;
KexAlg == dhe_rsa;
KexAlg == ecdhe_rsa;
@@ -1096,14 +392,14 @@ certify(internal, #server_key_exchange{exchange_keys = Keys},
session = session_handle_params(Params#server_key_params.params, Session)},
Connection);
false ->
- handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR),
Version, ?FUNCTION_NAME, State)
end
end;
certify(internal, #certificate_request{},
#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = KexAlg},
- connection_env = #connection_env{negotiated_version = Version}} = State, _)
+ connection_env = #connection_env{negotiated_version = Version}} = State)
when KexAlg == dh_anon;
KexAlg == ecdh_anon;
KexAlg == psk;
@@ -1113,25 +409,27 @@ certify(internal, #certificate_request{},
KexAlg == srp_dss;
KexAlg == srp_rsa;
KexAlg == srp_anon ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
Version, ?FUNCTION_NAME, State);
certify(internal, #certificate_request{},
- #state{static_env = #static_env{role = client},
- session = #session{own_certificates = undefined}} = State, Connection) ->
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ session = #session{own_certificates = undefined}} = 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 algorihms needed as there is
%% no certificate to verify.
Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
certify(internal, #certificate_request{} = CertRequest,
- #state{static_env = #static_env{role = client},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
handshake_env = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
session = #session{own_certificates = [Cert|_]},
- ssl_options = #{signature_algs := SupportedHashSigns}} = State, Connection) ->
+ ssl_options = #{signature_algs := SupportedHashSigns}} = State) ->
case ssl_handshake:select_hashsign(CertRequest, Cert,
SupportedHashSigns, ssl:tls_version(Version)) of
#alert {} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
NegotiatedHashSign ->
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{client_certificate_requested = true,
@@ -1139,17 +437,18 @@ certify(internal, #certificate_request{} = CertRequest,
end;
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
session = #session{master_secret = undefined},
connection_env = #connection_env{negotiated_version = Version},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
premaster_secret = undefined,
server_psk_identity = PSKIdentity} = HsEnv,
- ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection)
+ ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
when KexAlg == psk ->
case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
PremasterSecret ->
State = master_secret(PremasterSecret,
State0#state{handshake_env =
@@ -1157,20 +456,21 @@ certify(internal, #server_hello_done{},
client_certify_and_key_exchange(State, Connection)
end;
certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version,
handshake_env = #handshake_env{kex_algorithm = KexAlg,
premaster_secret = undefined,
server_psk_identity = PSKIdentity} = HsEnv,
session = #session{master_secret = undefined},
- ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection)
+ ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
when KexAlg == rsa_psk ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
RSAPremasterSecret) of
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
PremasterSecret ->
State = master_secret(PremasterSecret,
State0#state{handshake_env =
@@ -1179,26 +479,28 @@ certify(internal, #server_hello_done{},
end;
%% Master secret was determined with help of server-key exchange msg
certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version},
handshake_env = #handshake_env{premaster_secret = undefined},
session = #session{master_secret = MasterSecret} = Session,
- connection_states = ConnectionStates0} = State0, Connection) ->
+ connection_states = ConnectionStates0} = State0) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
State = State0#state{connection_states = ConnectionStates},
client_certify_and_key_exchange(State, Connection);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
%% Master secret is calculated from premaster_secret
certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version},
handshake_env = #handshake_env{premaster_secret = PremasterSecret},
session = Session0,
- connection_states = ConnectionStates0} = State0, Connection) ->
+ connection_states = ConnectionStates0} = State0) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
@@ -1207,47 +509,52 @@ certify(internal, #server_hello_done{},
session = Session},
client_certify_and_key_exchange(State, Connection);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
certify(internal = Type, #client_key_exchange{} = Msg,
#state{static_env = #static_env{role = server},
client_certificate_requested = true,
- ssl_options = #{fail_if_no_peer_cert := true}} = State,
- Connection) ->
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{fail_if_no_peer_cert := true}} = State) ->
%% We expect a certificate here
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection);
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}),
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #client_key_exchange{exchange_keys = Keys},
State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg},
- connection_env = #connection_env{negotiated_version = Version}}, Connection) ->
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version}}) ->
try
certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)),
State, Connection)
catch
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
-certify(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+certify(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+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(),
- #state{}, tls_connection | dtls_connection) ->
+ #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-cipher({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-cipher(info, Msg, State, _) ->
+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},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
handshake_env = #handshake_env{tls_handshake_history = Hist,
kex_algorithm = KexAlg,
public_key_info = PubKeyInfo} = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret}
- } = State, Connection) ->
+ } = State) ->
TLSVersion = ssl:tls_version(Version),
%% Use negotiated value if TLS-1.2 otherwhise return default
@@ -1258,16 +565,15 @@ cipher(internal, #certificate_verify{signature = Signature,
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
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},
- connection_env = #connection_env{negotiated_version = Version}} = State0,
- _Connection) ->
- handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
+ connection_env = #connection_env{negotiated_version = Version}} = State0) ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
cipher(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = Role,
host = Host,
@@ -1279,119 +585,90 @@ cipher(internal, #finished{verify_data = Data} = Finished,
session = #session{master_secret = MasterSecret}
= Session0,
ssl_options = SslOpts,
- connection_states = ConnectionStates0} = State, Connection) ->
+ connection_states = ConnectionStates0} = State) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
opposite_role(Role),
get_current_prf(ConnectionStates0, read),
MasterSecret, Hist) of
verified ->
Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0),
- cipher_role(Role, Data, Session,
- State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection);
+ cipher_role(Role, Data, Session,
+ State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{static_env = #static_env{role = server},
+ #state{static_env = #static_env{role = server, protocol_cb = Connection},
handshake_env = #handshake_env{expecting_finished = true,
- expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record,
+ expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
expecting_next_protocol_negotiation = false}});
-cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} =
- State, Connection) ->
+cipher(internal, #change_cipher_spec{type = <<1>>},
+ #state{handshake_env = HsEnv,
+ static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State) ->
ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true},
connection_states = ConnectionStates});
-cipher(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
+cipher(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+cipher(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
%%--------------------------------------------------------------------
--spec connection(gen_statem:event_type(), term(),
- #state{}, tls_connection | dtls_connection) ->
+-spec connection(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-connection({call, RecvFrom}, {recv, N, Timeout},
- #state{static_env = #static_env{protocol_cb = Connection},
- socket_options =
- #socket_options{active = false}} = State0, Connection) ->
- passive_receive(State0#state{bytes_to_read = N,
- start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection,
- [{{timeout, recv}, Timeout, timeout}]);
-
connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = HsEnv} = State,
- Connection) ->
+ handshake_env = HsEnv} = State) ->
Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
-connection({call, From}, peer_certificate,
- #state{session = #session{peer_certificate = Cert}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
-connection({call, From}, {connection_information, true}, State, _) ->
- Info = connection_info(State) ++ security_info(State),
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
-connection({call, From}, {connection_information, false}, State, _) ->
- Info = connection_info(State),
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
-connection({call, From}, negotiated_protocol,
+connection({call, From}, negotiated_protocol,
#state{handshake_env = #handshake_env{alpn = undefined,
- negotiated_protocol = undefined}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
-connection({call, From}, negotiated_protocol,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
#state{handshake_env = #handshake_env{alpn = undefined,
- negotiated_protocol = SelectedProtocol}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
+ negotiated_protocol = SelectedProtocol}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, negotiated_protocol,
#state{handshake_env = #handshake_env{alpn = SelectedProtocol,
- negotiated_protocol = undefined}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
-connection({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection},
handshake_env = HsEnv,
- connection_states = ConnectionStates}
- = State, Connection) ->
+ connection_states = ConnectionStates}
+ = State) ->
Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
connection_states = ConnectionStates#{current_write => WriteState}}, []);
-connection(cast, {dist_handshake_complete, DHandle},
- #state{ssl_options = #{erl_dist := true},
- connection_env = CEnv,
- socket_options = SockOpts} = State0, Connection) ->
- process_flag(priority, normal),
- State1 =
- State0#state{
- socket_options = SockOpts#socket_options{active = true},
- connection_env = CEnv#connection_env{erl_dist_handle = DHandle},
- bytes_to_read = undefined},
- {Record, State} = read_application_data(<<>>, State1),
- Connection:next_event(connection, Record, State);
-connection(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom} = State, Connection) ->
- passive_receive(State, ?FUNCTION_NAME, Connection, []);
-connection(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+connection(internal, {handshake, {#hello_request{} = Handshake, _}},
+ #state{handshake_env = HsEnv} = State) ->
+ %% Should not be included in handshake history
+ {next_state, ?FUNCTION_NAME, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
+ [{next_event, internal, Handshake}]};
+connection(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec downgrade(gen_statem:event_type(), term(),
- #state{}, tls_connection | dtls_connection) ->
+-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-downgrade(Type, Event, State, Connection) ->
- handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection).
+downgrade(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
gen_handshake(StateName, Type, Event,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State, Connection) ->
- try ssl_connection:StateName(Type, Event, State, Connection) of
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try ssl_connection:StateName(Type, Event, State) of
Result ->
Result
catch
_:_ ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
@@ -1400,136 +677,12 @@ gen_handshake(StateName, Type, Event,
%% Event handling functions called by state functions to handle
%% common or unexpected events for the state.
%%--------------------------------------------------------------------
-handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName,
- #state{static_env = #static_env{role = client},
- handshake_env = HsEnv} = State, _) ->
- %% Should not be included in handshake history
- {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
- [{next_event, internal, Handshake}]};
-handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName,
- #state{static_env = #static_env{role = client}}, _)
- when StateName =/= connection ->
- keep_state_and_data;
-handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
- #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
- connection_env = #connection_env{negotiated_version = _Version}} = State0,
- _Connection) ->
- Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
- {next_state, StateName,
- State0#state{handshake_env =
- HsEnv#handshake_env{tls_handshake_history = Hist}},
- [{next_event, internal, Handshake}]};
-handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) ->
- 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{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version,
- StateName, State);
-handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State, _) ->
- {stop_and_reply,
- {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}};
-handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State, _) ->
- {next_state, StateName, State#state{start_or_recv_from = undefined,
- bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]};
-handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}, _) when
- StateName =/= connection ->
- {keep_state_and_data, [postpone]};
-handle_common_event(Type, Msg, StateName, #state{connection_env =
- #connection_env{negotiated_version = Version}} = State,
- _) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}),
- handle_own_alert(Alert, Version, StateName, State).
-
-handle_call({application_data, _Data}, _, _, _, _) ->
- %% In renegotiation priorities handshake, send data when handshake is finished
- {keep_state_and_data, [postpone]};
-handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State, _Connection) ->
- %% Run terminate before returning so that the reuseaddr
- %% inet-option works properly
- Result = terminate(Close, StateName, State),
- {stop_and_reply,
- {shutdown, normal},
- {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}};
-handle_call({shutdown, read_write = How}, From, StateName,
- #state{static_env = #static_env{transport_cb = Transport,
- socket = Socket},
- connection_env = CEnv} = State, _) ->
- try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- StateName, State) of
- _ ->
- case Transport:shutdown(Socket, How) of
- ok ->
- {next_state, StateName, State#state{connection_env =
- CEnv#connection_env{terminated = true}},
- [{reply, From, ok}]};
- Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, Error},
- State#state{connection_env = CEnv#connection_env{terminated = true}}}
- end
- catch
- throw:Return ->
- Return
- end;
-handle_call({shutdown, How0}, From, StateName,
- #state{static_env = #static_env{transport_cb = Transport,
- socket = Socket}} = State, _) ->
- case Transport:shutdown(Socket, How0) of
- ok ->
- {next_state, StateName, State, [{reply, From, ok}]};
- Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
- end;
-handle_call({recv, _N, _Timeout}, From, _,
- #state{socket_options =
- #socket_options{active = Active}}, _) when Active =/= false ->
- {keep_state_and_data, [{reply, From, {error, einval}}]};
-handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) ->
- %% Doing renegotiate wait with handling request until renegotiate is
- %% finished.
- {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
- [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]};
-handle_call({new_user, User}, From, StateName,
- State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) ->
- NewMon = erlang:monitor(process, User),
- erlang:demonitor(OldMon, [flush]),
- {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
- [{reply, From, ok}]};
-handle_call({get_opts, OptTags}, From, _,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- socket_options = SockOpts}, Connection) ->
- OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []),
- {keep_state_and_data, [{reply, From, OptsReply}]};
-handle_call({set_opts, Opts0}, From, StateName,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}},
- socket_options = Opts1
- } = State0, Connection) ->
- {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
- case {proplists:lookup(active, Opts0), Opts} of
- {{_, N}, #socket_options{active=false}} when is_integer(N) ->
- send_user(
- Pid,
- format_passive(
- Connection:pids(State0), Transport, Socket, Trackers, Connection));
- _ ->
- ok
- end,
- State = State0#state{socket_options = Opts},
- handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
-
-handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection ->
+handle_call(renegotiate, From, StateName, _) when StateName =/= connection ->
{keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
#state{connection_states = ConnectionStates,
- connection_env = #connection_env{negotiated_version = Version}}, _) ->
+ connection_env = #connection_env{negotiated_version = Version}}) ->
#{security_parameters := SecParams} =
ssl_record:current_connection_state(ConnectionStates, read),
#security_parameters{master_secret = MasterSecret,
@@ -1552,229 +705,24 @@ handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
error:Reason -> {error, Reason}
end,
{keep_state_and_data, [{reply, From, Reply}]};
-handle_call(_,_,_,_,_) ->
- {keep_state_and_data, [postpone]}.
+handle_call(Msg, From, StateName, State) ->
+ ssl_gen_statem:handle_call(Msg, From, StateName, State).
-handle_info({ErrorTag, Socket, econnaborted}, StateName,
- #state{static_env = #static_env{role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- transport_cb = Transport,
- error_tag = ErrorTag,
- trackers = Trackers,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = Type},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- start_or_recv_from = StartFrom
- } = State) when StateName =/= connection ->
-
- maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers,Socket,
- StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
-
-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),
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}),
- handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown,normal}, State};
-
-handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
- #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
- ssl_options = #{erl_dist := true}}) ->
- {stop, {shutdown, Reason}};
-handle_info({'DOWN', MonitorRef, _, _, _}, _,
- #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) ->
- {stop, {shutdown, normal}};
-handle_info({'EXIT', Pid, _Reason}, StateName,
- #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) ->
- %% It seems the user application has linked to us
- %% - ignore that and let the monitor handle this
- {next_state, StateName, State};
-%%% So that terminate will be run when supervisor issues shutdown
-handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
- {stop, shutdown, State};
-handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
- %% Handle as transport close"
- {stop,{shutdown, transport_closed}, State};
-handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
- {stop,{shutdown, Reason}, State};
-
-handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) ->
- {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),
- {next_state, StateName, State}.
-
-%%====================================================================
-%% general gen_statem callbacks
-%%====================================================================
-terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) ->
- %% Happens when user closes the connection using ssl:close/1
- %% we want to guarantee that Transport:close has been called
- %% when ssl:close/1 returns unless it is a downgrade where
- %% we want to guarantee that close alert is received before
- %% returning. In both cases terminate has been run manually
- %% before run by gen_statem which will end up here
- ok;
-terminate({shutdown, transport_closed} = Reason,
- _StateName, #state{static_env = #static_env{protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport}} = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined);
-terminate({shutdown, own_alert}, _StateName, #state{
- static_env = #static_env{protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport}} = State) ->
- handle_trusted_certs_db(State),
- case application:get_env(ssl, alert_timeout) of
- {ok, Timeout} when is_integer(Timeout) ->
- Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined);
- _ ->
- Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
- end;
-terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket}
- } = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined);
-terminate(Reason, connection, #state{static_env = #static_env{
- protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket},
- connection_states = ConnectionStates,
- ssl_options = #{padding_check := Check}
- } = State) ->
- handle_trusted_certs_db(State),
- Alert = terminate_alert(Reason),
- %% Send the termination ALERT if possible
- catch (ok = Connection:send_alert_in_connection(Alert, State)),
- Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
-terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
- protocol_cb = Connection,
- socket = Socket}
- } = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined).
-
-format_status(normal, [_, StateName, State]) ->
- [{data, [{"State", {StateName, State}}]}];
-format_status(terminate, [_, StateName, State]) ->
- SslOptions = (State#state.ssl_options),
- NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
- cert => ?SECRET_PRINTOUT,
- cacerts => ?SECRET_PRINTOUT,
- key => ?SECRET_PRINTOUT,
- dh => ?SECRET_PRINTOUT,
- psk_identity => ?SECRET_PRINTOUT,
- srp_identity => ?SECRET_PRINTOUT},
- [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
- protocol_buffers = ?SECRET_PRINTOUT,
- user_data_buffer = ?SECRET_PRINTOUT,
- handshake_env = ?SECRET_PRINTOUT,
- connection_env = ?SECRET_PRINTOUT,
- session = ?SECRET_PRINTOUT,
- ssl_options = NewOptions,
- flight_buffer = ?SECRET_PRINTOUT}
- }}]}].
+handle_info(Msg, StateName, State) ->
+ ssl_gen_statem:handle_info(Msg, StateName, State).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
- Connection:send_alert_in_connection(Alert, State);
-send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
- Connection:send_alert(Alert, State).
-
-connection_info(#state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = #handshake_env{sni_hostname = SNIHostname,
- resumption = Resumption},
- session = #session{session_id = SessionId,
- cipher_suite = CipherSuite,
- srp_username = SrpUsername,
- ecc = ECCCurve} = Session,
- connection_states = #{current_write := CurrentWrite},
- connection_env = #connection_env{negotiated_version = {_,_} = Version},
- ssl_options = Opts}) ->
- RecordCB = record_cb(Connection),
- CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- IsNamedCurveSuite = lists:member(KexAlg,
- [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]),
- CurveInfo = case ECCCurve of
- {namedCurve, Curve} when IsNamedCurveSuite ->
- [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
- _ ->
- []
- end,
-
- MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of
- MaxFragmentLength when is_integer(MaxFragmentLength) ->
- [{max_fragment_length, MaxFragmentLength}];
- _ ->
- []
- end,
- [{protocol, RecordCB:protocol_version(Version)},
- {session_id, SessionId},
- {session_data, term_to_binary(Session)},
- {session_resumption, Resumption},
- {selected_cipher_suite, CipherSuiteDef},
- {sni_hostname, SNIHostname},
- {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts).
-
-security_info(#state{connection_states = ConnectionStates,
- static_env = #static_env{role = Role},
- ssl_options = #{keep_secrets := KeepSecrets}}) ->
- ReadState = ssl_record:current_connection_state(ConnectionStates, read),
- #{security_parameters :=
- #security_parameters{client_random = ClientRand,
- server_random = ServerRand,
- master_secret = MasterSecret,
- application_traffic_secret = AppTrafSecretRead}} = ReadState,
- BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}],
- if KeepSecrets =/= true ->
- BaseSecurityInfo;
- true ->
- #{security_parameters :=
- #security_parameters{application_traffic_secret = AppTrafSecretWrite}} =
- ssl_record:current_connection_state(ConnectionStates, write),
- BaseSecurityInfo ++
- if Role == server ->
- [{server_traffic_secret_0, AppTrafSecretWrite}, {client_traffic_secret_0, AppTrafSecretRead}];
- true ->
- [{client_traffic_secret_0, AppTrafSecretWrite}, {server_traffic_secret_0, AppTrafSecretRead}]
- end ++
- case ReadState of
- #{client_handshake_traffic_secret := ClientHSTrafficSecret,
- server_handshake_traffic_secret := ServerHSTrafficSecret} ->
- [{client_handshake_traffic_secret, ClientHSTrafficSecret},
- {server_handshake_traffic_secret, ServerHSTrafficSecret}];
- _ ->
- []
- end
- end.
-
do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
ServerHelloExt,
#state{connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{protocol_cb = Connection},
handshake_env = HsEnv,
session = #session{session_id = SessId},
connection_states = ConnectionStates0,
ssl_options = #{versions := [HighestVersion|_]}}
- = State0, Connection) when is_atom(Type) ->
+ = State0) when is_atom(Type) ->
%% TLS 1.3 - Section 4.1.3
%% Override server random values for TLS 1.3 downgrade protection mechanism.
ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion),
@@ -1850,6 +798,7 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression,
session_id = SessionId},
#state{session = Session0,
+ static_env = #static_env{protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
try server_certify_and_key_exchange(State0, Connection) of
#state{} = State1 ->
@@ -1861,11 +810,12 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite,
Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
catch
#alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0)
end.
resumed_server_hello(#state{session = Session,
connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
@@ -1877,7 +827,7 @@ resumed_server_hello(#state{session = Session,
finalize_handshake(State1, abbreviated, Connection),
Connection:next_event(abbreviated, no_record, State, Actions);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0)
end.
server_hello(ServerHello, State0, Connection) ->
@@ -1892,7 +842,8 @@ server_hello_done(State, Connection) ->
handle_peer_cert(Role, PeerCert, PublicKeyInfo,
#state{handshake_env = HsEnv,
- session = #session{cipher_suite = CipherSuite} = Session} = State0,
+ static_env = #static_env{protocol_cb = Connection},
+ session = #session{cipher_suite = CipherSuite} = Session} = State0,
Connection, Actions) ->
State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo},
session =
@@ -1947,7 +898,7 @@ verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
State.
client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} =
- State0, Connection) ->
+ State0, Connection) ->
try do_client_certify_and_key_exchange(State0, Connection) of
State1 = #state{} ->
{State2, Actions} = finalize_handshake(State1, certify, Connection),
@@ -1957,7 +908,7 @@ client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiat
Connection:next_event(cipher, no_record, State, Actions)
catch
throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0)
end.
do_client_certify_and_key_exchange(State0, Connection) ->
@@ -2392,7 +1343,7 @@ calculate_master_secret(PremasterSecret,
session = Session},
Connection:next_event(Next, no_record, State);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0)
end.
finalize_handshake(State0, StateName, Connection) ->
@@ -2562,22 +1513,22 @@ handle_srp_identity(Username, {Fun, UserState}) ->
end.
-cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State0,
- Connection) ->
+cipher_role(client, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State0) ->
ConnectionStates = ssl_record:set_server_verify_data(current_both, Data,
ConnectionStates0),
- {Record, State} = prepare_connection(State0#state{session = Session,
- connection_states = ConnectionStates},
- Connection),
+ {Record, State} = ssl_gen_statem:prepare_connection(State0#state{session = Session,
+ connection_states = ConnectionStates},
+ Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
-cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0,
- Connection) ->
+cipher_role(server, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State0) ->
ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data,
ConnectionStates0),
{State1, Actions} =
finalize_handshake(State0#state{connection_states = ConnectionStates1,
session = Session}, cipher, Connection),
- {Record, State} = prepare_connection(State1, Connection),
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]).
is_anonymous(KexAlg) when KexAlg == dh_anon;
@@ -2603,197 +1554,7 @@ opposite_role(client) ->
opposite_role(server) ->
client.
-record_cb(tls_connection) ->
- tls_record;
-record_cb(dtls_connection) ->
- dtls_record.
-
-call(FsmPid, Event) ->
- try gen_statem:call(FsmPid, Event)
- catch
- exit:{noproc, _} ->
- {error, closed};
- exit:{normal, _} ->
- {error, closed};
- exit:{{shutdown, _},_} ->
- {error, closed}
- end.
-
-get_socket_opts(_, _,_,[], _, Acc) ->
- {ok, Acc};
-get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{mode, SockOpts#socket_options.mode} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) ->
- case SockOpts#socket_options.packet of
- {Type, headers} ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
- Type ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
- end;
-get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{header, SockOpts#socket_options.header} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{active, SockOpts#socket_options.active} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
- case Connection:getopts(Transport, Socket, [Tag]) of
- {ok, [Opt]} ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]);
- {error, Reason} ->
- {error, {options, {socket_options, Tag, Reason}}}
- end;
-get_socket_opts(_,_, _,Opts, _,_) ->
- {error, {options, {socket_options, Opts, function_clause}}}.
-
-set_socket_opts(_,_,_, [], SockOpts, []) ->
- {ok, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) ->
- %% Set non emulated options
- try ConnectionCb:setopts(Transport, Socket, Other) of
- ok ->
- {ok, SockOpts};
- {error, InetError} ->
- {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
- catch
- _:Error ->
- %% So that inet behavior does not crash our process
- {{error, {options, {socket_options, Other, Error}}}, SockOpts}
- end;
-
-set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
- when Mode == list; Mode == binary ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{mode = Mode}, Other);
-set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
- when Packet == raw;
- Packet == 0;
- Packet == 1;
- Packet == 2;
- Packet == 4;
- Packet == asn1;
- Packet == cdr;
- Packet == sunrm;
- Packet == fcgi;
- Packet == tpkt;
- Packet == line;
- Packet == http;
- Packet == httph;
- Packet == http_bin;
- Packet == httph_bin ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{packet = Packet}, Other);
-set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
- when is_integer(Header) ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{header = Header}, Other);
-set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) ->
- {{error,{options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
- when Active == once;
- Active == true;
- Active == false ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{active = Active}, Other);
-set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
- SockOpts=#socket_options{active = Active0}, Other)
- when Active1 >= -32768, Active1 =< 32767 ->
- Active = if
- is_integer(Active0), Active0 + Active1 < -32768 ->
- error;
- is_integer(Active0), Active0 + Active1 =< 0 ->
- false;
- is_integer(Active0), Active0 + Active1 > 32767 ->
- error;
- Active1 =< 0 ->
- false;
- is_integer(Active0) ->
- Active0 + Active1;
- true ->
- Active1
- end,
- case Active of
- error ->
- {{error, {options, {socket_options, Opt}} }, SockOpts};
- _ ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{active = Active}, Other)
- end;
-set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}} }, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]).
-
-
-
-hibernate_after(connection = StateName,
- #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
- Actions) ->
- {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
-hibernate_after(StateName, State, Actions) ->
- {next_state, StateName, State, Actions}.
-
-
-terminate_alert(normal) ->
- ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
-terminate_alert({Reason, _}) when Reason == close;
- Reason == shutdown ->
- ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
-terminate_alert(_) ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR).
-
-handle_trusted_certs_db(#state{ssl_options =
- #{cacertfile := <<>>, cacerts := []}}) ->
- %% No trusted certs specified
- ok;
-handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
- cert_db = CertDb},
- ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
- %% Certs provided as DER directly can not be shared
- %% with other connections and it is safe to delete them when the connection ends.
- ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
-handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) ->
- %% Something went wrong early (typically cacertfile does not
- %% exist) so there is nothing to handle
- ok;
-handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
- file_ref_db = RefDb},
- ssl_options = #{cacertfile := File}}) ->
- case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
- 0 ->
- ssl_manager:clean_cert_db(Ref, File);
- _ ->
- ok
- end.
-prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
- start_or_recv_from = RecvFrom} = State0, Connection)
- when Renegotiate =/= {false, first},
- RecvFrom =/= undefined ->
- State = Connection:reinit(State0),
- {no_record, ack_connection(State)};
-prepare_connection(State0, Connection) ->
- State = Connection:reinit(State0),
- {no_record, ack_connection(State)}.
-
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
- Initiater == internal ->
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
- gen_statem:reply(From, ok),
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
- start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
- gen_statem:reply(StartFrom, connected),
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
- start_or_recv_from = undefined};
-ack_connection(State) ->
- State.
session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
Session#session{ecc = ECCurve};
@@ -2861,7 +1622,7 @@ handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
connection_states = ConnectionStates,
session = Session});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State)
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State)
end.
make_premaster_secret({MajVer, MinVer}, rsa) ->
@@ -2882,394 +1643,16 @@ negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
HashSign.
-ssl_options_list(SslOptions) ->
- L = maps:to_list(SslOptions),
- ssl_options_list(L, []).
-
-ssl_options_list([], Acc) ->
- lists:reverse(Acc);
-%% Skip internal options, only return user options
-ssl_options_list([{protocol, _}| T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{erl_dist, _}|T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{renegotiate_at, _}|T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{max_fragment_length, _}|T], Acc) ->
- %% skip max_fragment_length from options since it is taken above from connection_states
- ssl_options_list(T, Acc);
-ssl_options_list([{ciphers = Key, Value}|T], Acc) ->
- ssl_options_list(T,
- [{Key, lists:map(
- fun(Suite) ->
- ssl_cipher_format:suite_bin_to_map(Suite)
- end, Value)}
- | Acc]);
-ssl_options_list([{Key, Value}|T], Acc) ->
- ssl_options_list(T, [{Key, Value} | Acc]).
-
-handle_active_option(false, connection = StateName, To, Reply, State) ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
-handle_active_option(_, connection = StateName, To, Reply, #state{static_env = #static_env{role = Role},
- connection_env = #connection_env{terminated = true},
- user_data_buffer = {_,0,_}} = State) ->
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_delivered),
- handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop_and_reply,{shutdown, peer_close}, [{reply, To, Reply}]};
-handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
- user_data_buffer = {_,0,_}} = State0) ->
- case Connection:next_event(StateName0, no_record, State0) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
- {stop, _, _} = Stop ->
- Stop
- end;
-handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) ->
- %% Active once already set
- {next_state, StateName, State, [{reply, To, Reply}]};
-
-%% user_data_buffer nonempty
-handle_active_option(_, StateName0, To, Reply,
- #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
- case read_application_data(<<>>, State0) of
- {stop, _, _} = Stop ->
- Stop;
- {Record, State1} ->
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State1) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
- {stop, _, _} = Stop ->
- Stop
- end
- end.
-
-
-%% Picks ClientData
-get_data(#socket_options{active=false}, undefined, _Bin) ->
- %% Recv timed out save buffer data until next recv
- passive;
-get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin)
- when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
- case Bin of
- <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
- %% Active true or once, or passive mode recv(0)
- {ok, Bin, <<>>};
- <<Data:BytesToRead/binary, Rest/binary>> ->
- %% Passive Mode, recv(Bytes)
- {ok, Data, Rest};
- <<_/binary>> ->
- %% Passive Mode not enough data
- {more, BytesToRead}
- end;
-get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
- PacketOpts = [{packet_size, Size}],
- decode_packet(Type, Bin, PacketOpts).
-
-decode_packet({http, headers}, Buffer, PacketOpts) ->
- decode_packet(httph, Buffer, PacketOpts);
-decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
- decode_packet(httph_bin, Buffer, PacketOpts);
-decode_packet(Type, Buffer, PacketOpts) ->
- erlang:decode_packet(Type, Buffer, PacketOpts).
-
-%% Just like with gen_tcp sockets, an ssl socket that has been configured with
-%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
-%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
-%% represent the current state as follows:
-%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
-%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
-%% Note that if the user has explicitly configured the socket to expect
-%% HTTP headers using the {packet, httph} option, we don't do any automatic
-%% switching of states.
-deliver_app_data(
- CPids, Transport, Socket,
- #socket_options{active=Active, packet=Type} = SOpts,
- Data, Pid, From, Trackers, Connection) ->
- %%
- send_or_reply(
- Active, Pid, From,
- format_reply(
- CPids, Transport, Socket, SOpts, Data, Trackers, Connection)),
- SO =
- case Data of
- {P, _, _, _}
- when ((P =:= http_request) or (P =:= http_response)),
- ((Type =:= http) or (Type =:= http_bin)) ->
- SOpts#socket_options{packet={Type, headers}};
- http_eoh when tuple_size(Type) =:= 2 ->
- %% End of headers - expect another Request/Response line
- {Type1, headers} = Type,
- SOpts#socket_options{packet=Type1};
- _ ->
- SOpts
- end,
- case Active of
- once ->
- SO#socket_options{active=false};
- 1 ->
- send_user(
- Pid,
- format_passive(
- CPids, Transport, Socket, Trackers, Connection)),
- SO#socket_options{active=false};
- N when is_integer(N) ->
- SO#socket_options{active=N - 1};
- _ ->
- SO
- end.
-
-format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet,
- header = Header}, Data, _, _) ->
- {ok, do_format_reply(Mode, Packet, Header, Data)};
-format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
- header = Header}, Data, Trackers, Connection) ->
- {ssl, Connection:socket(CPids, Transport, Socket, Trackers),
- do_format_reply(Mode, Packet, Header, Data)}.
-
-deliver_packet_error(CPids, Transport, Socket,
- SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) ->
- send_or_reply(Active, Pid, From, format_packet_error(CPids,
- Transport, Socket, SO, Data, Trackers, Connection)).
-
-format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
- {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
-format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
- Data, Trackers, Connection) ->
- {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers),
- {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
-
-do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
- header(N, Data);
-do_format_reply(binary, _, _, Data) ->
- Data;
-do_format_reply(list, Packet, _, Data)
- when Packet == http; Packet == {http, headers};
- Packet == http_bin; Packet == {http_bin, headers};
- Packet == httph; Packet == httph_bin ->
- Data;
-do_format_reply(list, _,_, Data) ->
- binary_to_list(Data).
-
-format_passive(CPids, Transport, Socket, Trackers, Connection) ->
- {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}.
-
-header(0, <<>>) ->
- <<>>;
-header(_, <<>>) ->
- [];
-header(0, Binary) ->
- Binary;
-header(N, Binary) ->
- <<?BYTE(ByteN), NewBinary/binary>> = Binary,
- [ByteN | header(N-1, NewBinary)].
-
-send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
- gen_statem:reply(From, Data);
-send_or_reply(false, Pid, undefined, _) when is_pid(Pid) ->
- ok;
-send_or_reply(_, no_pid, _, _) ->
- ok;
-send_or_reply(_, Pid, _, Data) ->
- send_user(Pid, Data).
-
-send_user(Pid, Msg) ->
- Pid ! Msg,
- ok.
-
-alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
-alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection).
-
-alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
-
-alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
- %% If there is an outstanding ssl_accept | recv
- %% From will be defined and send_or_reply will
- %% send the appropriate error message.
- ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
- send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
- case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
- closed ->
- send_or_reply(Active, Pid, From,
- {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)});
- ReasonCode ->
- send_or_reply(Active, Pid, From,
- {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode})
- end.
-
-log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
- ssl_logger:log(notice, Level, #{protocol => ProtocolName,
- role => Role,
- statename => StateName,
- alert => Alert,
- alerter => own}, Alert#alert.where);
-log_alert(Level, Role, ProtocolName, StateName, Alert) ->
- ssl_logger:log(notice, Level, #{protocol => ProtocolName,
- role => Role,
- statename => StateName,
- alert => Alert,
- alerter => peer}, Alert#alert.where).
-
-maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
- invalidate_session(Role, Host, Port, Session);
-maybe_invalidate_session(_, _, _, _, _) ->
- ok.
-
-invalidate_session(client, Host, Port, Session) ->
- ssl_manager:invalidate_session(Host, Port, Session);
-invalidate_session(server, _, _, _) ->
- ok.
-
-%% Handle SNI extension in TLS 1.3
-handle_sni_extension_tls13(undefined, State) ->
- {ok, State};
-handle_sni_extension_tls13(#sni{hostname = Hostname}, State0) ->
- case check_hostname(State0, Hostname) of
- valid ->
- State1 = handle_sni_hostname(Hostname, State0),
- State = set_sni_guided_cert_selection(State1, true),
- {ok, State};
- unrecognized_name ->
- {ok, handle_sni_hostname(Hostname, State0)};
- #alert{} = Alert ->
- {error, Alert}
- end.
-
-set_sni_guided_cert_selection(#state{handshake_env = HsEnv0} = State, Bool) ->
- HsEnv = HsEnv0#handshake_env{sni_guided_cert_selection = Bool},
- State#state{handshake_env = HsEnv}.
-
-check_hostname(#state{ssl_options = SslOptions}, Hostname) ->
- case is_sni_value(Hostname) of
- true ->
- case is_hostname_recognized(SslOptions, Hostname) of
- true ->
- valid;
- false ->
- %% We should send an alert but for interoperability reasons we
- %% allow the connection to be established.
- %% ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME)
- unrecognized_name
- end;
- false ->
- ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME,
- {sni_included_trailing_dot, Hostname})
- end.
-
-is_hostname_recognized(#{sni_fun := undefined,
- sni_hosts := SNIHosts}, Hostname) ->
- proplists:is_defined(Hostname, SNIHosts);
-is_hostname_recognized(_, _) ->
- true.
-
%% Handle SNI extension in pre-TLS 1.3 and DTLS
handle_sni_extension(#state{static_env =
#static_env{protocol_cb = Connection}} = State0,
Hello) ->
PossibleSNI = Connection:select_sni_extension(Hello),
- case do_handle_sni_extension(PossibleSNI, State0) of
- #state{} = State1 ->
- State1;
- #alert{} = Alert0 ->
- ssl_connection:handle_own_alert(Alert0, undefined, hello,
- State0)
- end.
-
-do_handle_sni_extension(undefined, State) ->
- State;
-do_handle_sni_extension(#sni{hostname = Hostname},
- #state{ssl_options = SslOptions} = State0) ->
- case is_sni_value(Hostname) of
- true ->
- case is_hostname_recognized(SslOptions, Hostname) of
- true ->
- State1 = handle_sni_hostname(Hostname, State0),
- set_sni_guided_cert_selection(State1, true);
- false ->
- %% We should send an alert but for interoperability reasons we
- %% allow the connection to be established.
- %% ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME)
- handle_sni_hostname(Hostname, State0)
- end;
- false ->
- ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, {sni_included_trailing_dot, Hostname})
- end.
-
-handle_sni_hostname(Hostname,
- #state{static_env = #static_env{role = Role} = InitStatEnv0,
- handshake_env = HsEnv,
- connection_env = CEnv} = State0) ->
- NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
- case NewOptions of
- undefined ->
- State0;
- _ ->
- {ok, #{cert_db_ref := Ref,
- cert_db_handle := CertDbHandle,
- fileref_db_handle := FileRefHandle,
- session_cache := CacheHandle,
- crl_db_info := CRLDbHandle,
- private_key := Key,
- dh_params := DHParams,
- own_certificates := OwnCerts}} =
- ssl_config:init(NewOptions, Role),
- State0#state{
- session = State0#state.session#session{own_certificates = OwnCerts},
- static_env = InitStatEnv0#static_env{
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle
- },
- connection_env = CEnv#connection_env{private_key = Key},
- ssl_options = NewOptions,
- handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
- diffie_hellman_params = DHParams}
- }
- end.
-
-update_ssl_options_from_sni(#{sni_fun := SNIFun,
- sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
- SSLOption =
- case SNIFun of
- undefined ->
- proplists:get_value(SNIHostname,
- SNIHosts);
- SNIFun ->
- SNIFun(SNIHostname)
- end,
- case SSLOption of
- undefined ->
- undefined;
- _ ->
- ssl:handle_options(SSLOption, server, OrigSSLOptions)
- end.
-
-new_emulated([], EmOpts) ->
- EmOpts;
-new_emulated(NewEmOpts, _) ->
- NewEmOpts.
-
-no_records(Extensions) ->
- maps:map(fun(_, Value) ->
- ssl_handshake:extension_value(Value)
- end, Extensions).
-
-is_sni_value(Hostname) ->
- case hd(lists:reverse(Hostname)) of
- $. ->
- false;
- _ ->
- true
+ case ssl_gen_statem:handle_sni_extension(PossibleSNI, State0) of
+ {ok, State} ->
+ State;
+ {error, Alert} ->
+ Alert
end.
ensure_tls({254, _} = Version) ->
@@ -3289,52 +1672,3 @@ ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) ->
ocsp_responder_certs => [],
ocsp_state => OcspState
}.
-
-%% Maybe add NSS keylog info according to
-%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
-maybe_add_keylog(Info) ->
- maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info).
-
-maybe_add_keylog({_, 'tlsv1.2'}, Info) ->
- try
- {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
- {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info),
- ClientRandom = binary:decode_unsigned(ClientRandomBin),
- MasterSecret = binary:decode_unsigned(MasterSecretBin),
- Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])],
- Info ++ [{keylog,Keylog}]
- catch
- _Cxx:_Exx ->
- Info
- end;
-maybe_add_keylog({_, 'tlsv1.3'}, Info) ->
- try
- {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
- {client_traffic_secret_0, ClientTrafficSecret0Bin} = lists:keyfind(client_traffic_secret_0, 1, Info),
- {server_traffic_secret_0, ServerTrafficSecret0Bin} = lists:keyfind(server_traffic_secret_0, 1, Info),
- {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info),
- {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info),
- {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info),
- ClientRandom = binary:decode_unsigned(ClientRandomBin),
- ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf),
- ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf),
- ClientHSecret = keylog_secret(ClientHSecretBin, Prf),
- ServerHSecret = keylog_secret(ServerHSecretBin, Prf),
- Keylog = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret,
- io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret,
- io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0,
- io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0],
- Info ++ [{keylog,Keylog}]
- catch
- _Cxx:_Exx ->
- Info
- end;
-maybe_add_keylog(_, Info) ->
- Info.
-
-keylog_secret(SecretBin, sha256) ->
- io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]);
-keylog_secret(SecretBin, sha384) ->
- io_lib:format("~96.16.0B", [binary:decode_unsigned(SecretBin)]);
-keylog_secret(SecretBin, sha512) ->
- io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]).
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
new file mode 100644
index 0000000000..4b13bfdec6
--- /dev/null
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -0,0 +1,1985 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2020. 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
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Provid help function to handle generic parts of TLS
+%% connection fsms
+%%----------------------------------------------------------------------
+
+-module(ssl_gen_statem).
+
+-include_lib("kernel/include/logger.hrl").
+
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_alert.hrl").
+-include("tls_handshake.hrl").
+
+%% Initial Erlang process setup
+-export([start_link/8,
+ init/1]).
+
+%% TLS connection setup
+-export([ssl_config/3,
+ connect/8,
+ handshake/7,
+ handshake/2,
+ handshake/3,
+ handshake_continue/3,
+ handshake_cancel/1,
+ handle_sni_extension/2,
+ socket_control/4,
+ socket_control/5,
+ prepare_connection/2]).
+
+%% User Events
+-export([send/2,
+ recv/3,
+ close/2,
+ shutdown/2,
+ new_user/2,
+ get_opts/2,
+ set_opts/2,
+ peer_certificate/1,
+ negotiated_protocol/1,
+ connection_information/2
+ ]).
+
+%% Erlang Distribution export
+-export([dist_handshake_complete/2]).
+
+%% Generic fsm states
+-export([initial_hello/3,
+ config_error/3,
+ connection/3]).
+
+-export([call/2,
+ handle_common_event/4,
+ handle_call/4,
+ handle_info/3
+ ]).
+
+-export([hibernate_after/3]).
+
+%% Data handling
+-export([read_application_data/2]).
+
+%% Alert and close handling
+-export([send_alert/3,
+ handle_own_alert/4,
+ handle_alert/3,
+ handle_normal_shutdown/3,
+ handle_trusted_certs_db/1,
+ maybe_invalidate_session/6,
+ maybe_invalidate_session/5,
+ terminate/3]).
+
+%% Log handling
+-export([format_status/2]).
+
+%%--------------------------------------------------------------------
+%%% Initial Erlang process setup
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), 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]])}.
+
+%%--------------------------------------------------------------------
+-spec init(list()) -> no_return().
+%% Description: Initialization
+%%--------------------------------------------------------------------
+init([_Role, Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+ process_flag(trap_exit, true),
+ link(Sender),
+ case ErlDist of
+ true ->
+ process_flag(priority, max);
+ _ ->
+ ok
+ end,
+ ConnectionFsm = connection_fsm(TLSOpts),
+ ConnectionFsm:init(InitArgs).
+
+%%====================================================================
+%% TLS connection setup
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
+%%--------------------------------------------------------------------
+ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
+ {ok, #{cert_db_ref := Ref,
+ cert_db_handle := CertDbHandle,
+ fileref_db_handle := FileRefHandle,
+ session_cache := CacheHandle,
+ crl_db_info := CRLDbHandle,
+ private_key := Key,
+ dh_params := DHParams,
+ own_certificates := OwnCerts}} =
+ ssl_config:init(Opts, Role),
+ TimeStamp = erlang:monotonic_time(),
+ Session = State0#state.session,
+
+ State0#state{session = Session#session{own_certificates = OwnCerts,
+ time_stamp = TimeStamp},
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
+ handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = Opts}.
+
+%%--------------------------------------------------------------------
+-spec connect(tls_connection | dtls_connection,
+ ssl:host(), inet:port_number(),
+ port() | {tuple(), port()}, %% TLS | DTLS
+ {ssl_options(), #socket_options{},
+ %% Tracker only needed on server side
+ undefined},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Connect to an ssl server.
+%%--------------------------------------------------------------------
+connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+%%--------------------------------------------------------------------
+-spec handshake(tls_connection | dtls_connection,
+ inet:port_number(), port(),
+ {ssl_options(), #socket_options{}, list()},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {ok, #sslsocket{}, map()}| {error, reason()}.
+%%
+%% Description: Starts ssl handshake.
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
+ case call(Pid, {start, Timeout}) of
+ connected ->
+ {ok, Socket};
+ {ok, Ext} ->
+ {ok, Socket, no_records(Ext)};
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) ->
+ {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}.
+%%
+%% Description: Starts ssl handshake with some new options
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
+ case call(Pid, {start, SslOptions, Timeout}) of
+ connected ->
+ {ok, Socket};
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
+ timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
+%%
+%% Description: Continues handshake with new options
+%%--------------------------------------------------------------------
+handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
+ case call(Pid, {handshake_continue, SslOptions, Timeout}) of
+ connected ->
+ {ok, Socket};
+ Error ->
+ Error
+ end.
+%%--------------------------------------------------------------------
+-spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}.
+%%
+%% Description: Cancels connection
+%%--------------------------------------------------------------------
+handshake_cancel(#sslsocket{pid = [Pid|_]}) ->
+ case call(Pid, cancel) of
+ closed ->
+ ok;
+ Error ->
+ Error
+ end.
+%--------------------------------------------------------------------
+-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Set the ssl process to own the accept socket
+%%--------------------------------------------------------------------
+socket_control(Connection, Socket, Pid, Transport) ->
+ socket_control(Connection, Socket, Pid, Transport, undefined).
+
+%--------------------------------------------------------------------
+-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%--------------------------------------------------------------------
+socket_control(Connection, Socket, Pids, Transport, udp_listener) ->
+ %% dtls listener process must have the socket control
+ {ok, Connection:socket(Pids, Transport, Socket, undefined)};
+
+socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+socket_control(dtls_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
+ start_or_recv_from = RecvFrom} = State0, Connection)
+ when Renegotiate =/= {false, first},
+ RecvFrom =/= undefined ->
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)};
+prepare_connection(State0, Connection) ->
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)}.
+
+%%====================================================================
+%% User events
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec send(pid(), iodata()) -> ok | {error, reason()}.
+%%
+%% Description: Sends data over the ssl connection
+%%--------------------------------------------------------------------
+send(Pid, Data) ->
+ call(Pid, {application_data,
+ %% iolist_to_iovec should really
+ %% be called iodata_to_iovec()
+ erlang:iolist_to_iovec(Data)}).
+
+%%--------------------------------------------------------------------
+-spec recv(pid(), integer(), timeout()) ->
+ {ok, binary() | list()} | {error, reason()}.
+%%
+%% Description: Receives data when active = false
+%%--------------------------------------------------------------------
+recv(Pid, Length, Timeout) ->
+ call(Pid, {recv, Length, Timeout}).
+
+%%--------------------------------------------------------------------
+-spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Get connection information
+%%--------------------------------------------------------------------
+connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) ->
+ case call(Pid, {connection_information, IncludeSecrityInfo}) of
+ {ok, Info} when IncludeSecrityInfo == true ->
+ {ok, maybe_add_keylog(Info)};
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+-spec close(pid(), {close, Timeout::integer() |
+ {NewController::pid(), Timeout::integer()}}) ->
+ ok | {ok, port()} | {error, reason()}.
+%%
+%% Description: Close an ssl connection
+%%--------------------------------------------------------------------
+close(ConnectionPid, How) ->
+ case call(ConnectionPid, How) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+%%--------------------------------------------------------------------
+-spec shutdown(pid(), atom()) -> ok | {error, reason()}.
+%%
+%% Description: Same as gen_tcp:shutdown/2
+%%--------------------------------------------------------------------
+shutdown(ConnectionPid, How) ->
+ call(ConnectionPid, {shutdown, How}).
+
+%%--------------------------------------------------------------------
+-spec new_user(pid(), pid()) -> ok | {error, reason()}.
+%%
+%% Description: Changes process that receives the messages when active = true
+%% or once.
+%%--------------------------------------------------------------------
+new_user(ConnectionPid, User) ->
+ call(ConnectionPid, {new_user, User}).
+
+%%--------------------------------------------------------------------
+-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Same as inet:getopts/2
+%%--------------------------------------------------------------------
+get_opts(ConnectionPid, OptTags) ->
+ call(ConnectionPid, {get_opts, OptTags}).
+%%--------------------------------------------------------------------
+-spec set_opts(pid(), list()) -> ok | {error, reason()}.
+%%
+%% Description: Same as inet:setopts/2
+%%--------------------------------------------------------------------
+set_opts(ConnectionPid, Options) ->
+ call(ConnectionPid, {set_opts, Options}).
+
+%%--------------------------------------------------------------------
+-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
+%%
+%% Description: Returns the peer cert
+%%--------------------------------------------------------------------
+peer_certificate(ConnectionPid) ->
+ call(ConnectionPid, peer_certificate).
+
+%%--------------------------------------------------------------------
+-spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}.
+%%
+%% Description: Returns the negotiated protocol
+%%--------------------------------------------------------------------
+negotiated_protocol(ConnectionPid) ->
+ call(ConnectionPid, negotiated_protocol).
+
+dist_handshake_complete(ConnectionPid, DHandle) ->
+ gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
+
+handle_sni_extension(undefined, State) ->
+ {ok, State};
+handle_sni_extension(#sni{hostname = Hostname}, State0) ->
+ case check_hostname(State0, Hostname) of
+ valid ->
+ State1 = handle_sni_hostname(Hostname, State0),
+ State = set_sni_guided_cert_selection(State1, true),
+ {ok, State};
+ unrecognized_name ->
+ {ok, handle_sni_hostname(Hostname, State0)};
+ #alert{} = Alert ->
+ {error, Alert}
+ end.
+
+%%====================================================================
+%% Generic states
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | {start, {list(), list()}, timeout()}| term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello({call, From}, {start, Timeout},
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState0} = HsEnv,
+ connection_env = CEnv,
+ ssl_options = #{log_level := LogLevel,
+ %% Use highest version in initial ClientHello.
+ %% Versions is a descending list of supported versions.
+ versions := [HelloVersion|_] = Versions,
+ session_tickets := SessionTickets,
+ ocsp_stapling := OcspStaplingOpt,
+ ocsp_nonce := OcspNonceOpt} = SslOpts,
+ session = Session,
+ connection_states = ConnectionStates0
+ } = State0) ->
+
+ KeyShare = maybe_generate_client_shares(SslOpts),
+ %% Update UseTicket in case of automatic session resumption
+ {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
+ TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
+ OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
+ Session#session.session_id,
+ Renegotiation,
+ Session#session.own_certificates,
+ KeyShare,
+ TicketData,
+ OcspNonce),
+
+ Handshake0 = ssl_handshake:init_handshake_history(),
+
+ %% Update pre_shared_key extension with binders (TLS 1.3)
+ Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion),
+
+ MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined),
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+
+ {BinMsg, ConnectionStates, Handshake} =
+ Connection:encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0),
+
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+
+ %% RequestedVersion is used as the legacy record protocol version and shall be
+ %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the
+ %% lowest supported protocol version.
+ %%
+ %% negotiated_version is also used by the TLS 1.3 state machine and is set after
+ %% ServerHello is processed.
+ RequestedVersion = tls_record:hello_version(Versions),
+ State = State1#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{
+ negotiated_version = RequestedVersion},
+ session = Session,
+ handshake_env = HsEnv#handshake_env{
+ tls_handshake_history = Handshake,
+ ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}},
+ start_or_recv_from = From,
+ key_share = KeyShare},
+ case connection_fsm(SslOpts) of
+ tls_connection ->
+ Connection:next_event(hello, no_record, State,
+ [{{timeout, handshake}, Timeout, close}]);
+ tls_connection_1_3 ->
+ Connection:next_event(wait_sh, no_record, State,
+ [{{timeout, handshake}, Timeout, close}])
+ end;
+initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{protocol_cb = Connection},
+ ssl_options = Opts} = State0) ->
+
+ case connection_fsm(Opts) of
+ tls_connection ->
+ Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From},
+ [{{timeout, handshake}, Timeout, close}]);
+ tls_connection_1_3 ->
+ Connection:next_event(start, no_record, State0#state{start_or_recv_from = From},
+ [{{timeout, handshake}, Timeout, close}])
+ end;
+initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout},
+ #state{static_env = #static_env{role = Role},
+ ssl_options = OrigSSLOptions,
+ socket_options = SockOpts} = State0) ->
+ try
+ SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
+ State = ssl_config(SslOpts, Role, State0),
+ initial_hello({call, From}, {start, Timeout},
+ State#state{ssl_options = SslOpts,
+ socket_options = new_emulated(EmOpts, SockOpts)})
+ catch throw:Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
+ end;
+initial_hello({call, From}, {new_user, _} = Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+initial_hello({call, From}, _Msg, _State) ->
+ {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]};
+initial_hello(_Type, _Event, _State) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error({call, From}, {start, _Timeout},
+ #state{protocol_specific = #{error := Error}} = State) ->
+ {stop_and_reply, {shutdown, normal},
+ [{reply, From, {error, Error}}], State};
+config_error({call, From}, {close, _}, State) ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
+config_error({call, From}, _Msg, State) ->
+ {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]};
+config_error(_Type, _Event, _State) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection({call, RecvFrom}, {recv, N, Timeout},
+ #state{static_env = #static_env{protocol_cb = Connection},
+ socket_options =
+ #socket_options{active = false}} = State0) ->
+ passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection,
+ [{{timeout, recv}, Timeout, timeout}]);
+connection({call, From}, peer_certificate,
+ #state{session = #session{peer_certificate = Cert}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
+connection({call, From}, {connection_information, true}, State) ->
+ Info = connection_info(State) ++ security_info(State),
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
+connection({call, From}, {connection_information, false}, State) ->
+ Info = connection_info(State),
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = undefined}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = SelectedProtocol}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From},
+ {close, {Pid, _Timeout}},
+ #state{connection_env = #connection_env{terminated = closed} = CEnv,
+ protocol_specific = PS} = State) ->
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{terminated = true,
+ downgrade = {Pid, From}},
+ protocol_specific = PS#{active_n_toggle => true,
+ active_n => 1}
+ },
+ [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
+connection({call, From},
+ {close,{Pid, Timeout}},
+ #state{connection_states = ConnectionStates,
+ static_env = #static_env{protocol_cb = Connection},
+ protocol_specific = #{sender := Sender} = PS,
+ connection_env = CEnv
+ } = State0) ->
+ case tls_sender:downgrade(Sender, Timeout) of
+ {ok, Write} ->
+ %% User downgrades connection
+ %% When downgrading an TLS connection to a transport connection
+ %% we must recive the close alert from the peer before releasing the
+ %% transport socket.
+ State = Connection:send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ State0#state{connection_states =
+ ConnectionStates#{current_write => Write}}),
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{downgrade = {Pid, From},
+ terminated = true},
+ protocol_specific = PS#{active_n_toggle => true,
+ active_n => 1}
+ },
+ [{timeout, Timeout, downgrade}]};
+ {error, timeout} ->
+ {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
+ end;
+connection({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+connection(cast, {dist_handshake_complete, DHandle},
+ #state{ssl_options = #{erl_dist := true},
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = CEnv,
+ socket_options = SockOpts} = State0) ->
+ process_flag(priority, normal),
+ State1 =
+ State0#state{
+ socket_options = SockOpts#socket_options{active = true},
+ connection_env = CEnv#connection_env{erl_dist_handle = DHandle},
+ bytes_to_read = undefined},
+ {Record, State} = read_application_data(<<>>, State1),
+ Connection:next_event(connection, Record, State);
+connection(info, Msg, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:handle_info(Msg, ?FUNCTION_NAME, State);
+connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom,
+ static_env = #static_env{protocol_cb = Connection}} = State) ->
+ passive_receive(State, ?FUNCTION_NAME, Connection, []);
+connection(Type, Msg, State) ->
+ handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%====================================================================
+%% Event/Msg handling
+%%====================================================================
+handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = _Version}} = State0) ->
+ Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
+ {next_state, StateName,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{tls_handshake_history = Hist}},
+ [{next_event, internal, Handshake}]};
+handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ 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{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, StateName, State);
+handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) ->
+ {stop_and_reply,
+ {shutdown, user_timeout},
+ {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}};
+handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State) ->
+ {next_state, StateName, State#state{start_or_recv_from = undefined,
+ bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]};
+handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}) when
+ StateName =/= connection ->
+ {keep_state_and_data, [postpone]};
+handle_common_event(Type, Msg, StateName, #state{connection_env =
+ #connection_env{negotiated_version = Version}} = State) ->
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}),
+ handle_own_alert(Alert, Version, StateName, State).
+
+handle_call({application_data, _Data}, _, _, _) ->
+ %% In renegotiation priorities handshake, send data when handshake is finished
+ {keep_state_and_data, [postpone]};
+handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State) ->
+ %% Run terminate before returning so that the reuseaddr
+ %% inet-option works properly
+ Result = terminate(Close, StateName, State),
+ {stop_and_reply,
+ {shutdown, normal},
+ {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}};
+handle_call({shutdown, read_write = How}, From, StateName,
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket},
+ connection_env = CEnv} = State) ->
+ try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ StateName, State) of
+ _ ->
+ case Transport:shutdown(Socket, How) of
+ ok ->
+ {next_state, StateName, State#state{connection_env =
+ CEnv#connection_env{terminated = true}},
+ [{reply, From, ok}]};
+ Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error},
+ State#state{connection_env = CEnv#connection_env{terminated = true}}}
+ end
+ catch
+ throw:Return ->
+ Return
+ end;
+handle_call({shutdown, How0}, From, StateName,
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket}} = State) ->
+ case Transport:shutdown(Socket, How0) of
+ ok ->
+ {next_state, StateName, State, [{reply, From, ok}]};
+ Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
+ end;
+handle_call({recv, _N, _Timeout}, From, _,
+ #state{socket_options =
+ #socket_options{active = Active}}) when Active =/= false ->
+ {keep_state_and_data, [{reply, From, {error, einval}}]};
+handle_call({recv, N, Timeout}, RecvFrom, StateName, State) ->
+ %% Doing renegotiate wait with handling request until renegotiate is
+ %% finished.
+ {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
+ [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]};
+handle_call({new_user, User}, From, StateName,
+ State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}) ->
+ NewMon = erlang:monitor(process, User),
+ erlang:demonitor(OldMon, [flush]),
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
+ [{reply, From, ok}]};
+handle_call({get_opts, OptTags}, From, _,
+ #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport},
+ socket_options = SockOpts}) ->
+ OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []),
+ {keep_state_and_data, [{reply, From, OptsReply}]};
+handle_call({set_opts, Opts0}, From, StateName,
+ #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}},
+ socket_options = Opts1
+ } = State0) ->
+ {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
+ case {proplists:lookup(active, Opts0), Opts} of
+ {{_, N}, #socket_options{active=false}} when is_integer(N) ->
+ send_user(
+ Pid,
+ format_passive(
+ Connection:pids(State0), Transport, Socket, Trackers, Connection));
+ _ ->
+ ok
+ end,
+ State = State0#state{socket_options = Opts},
+ handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
+
+handle_call(renegotiate, From, StateName, _) when StateName =/= connection ->
+ {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
+
+handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
+ #state{connection_states = ConnectionStates,
+ connection_env = #connection_env{negotiated_version = Version}}) ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{master_secret = MasterSecret,
+ client_random = ClientRandom,
+ server_random = ServerRandom,
+ prf_algorithm = PRFAlgorithm} = SecParams,
+ Reply = try
+ SecretToUse = case Secret of
+ _ when is_binary(Secret) -> Secret;
+ master_secret -> MasterSecret
+ end,
+ SeedToUse = lists:reverse(
+ lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
+ (client_random, Acc) -> [ClientRandom|Acc];
+ (server_random, Acc) -> [ServerRandom|Acc]
+ end, [], Seed)),
+ ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
+ catch
+ exit:_ -> {error, badarg};
+ error:Reason -> {error, Reason}
+ end,
+ {keep_state_and_data, [{reply, From, Reply}]};
+handle_call(_,_,_,_) ->
+ {keep_state_and_data, [postpone]}.
+handle_info({ErrorTag, Socket, econnaborted}, StateName,
+ #state{static_env = #static_env{role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ transport_cb = Transport,
+ error_tag = ErrorTag,
+ trackers = Trackers,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = Type},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ start_or_recv_from = StartFrom
+ } = State) when StateName =/= connection ->
+
+ maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers,Socket,
+ StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
+ {stop, {shutdown, normal}, State};
+
+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),
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}),
+ handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown,normal}, State};
+
+handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
+ ssl_options = #{erl_dist := true}}) ->
+ {stop, {shutdown, Reason}};
+handle_info({'DOWN', MonitorRef, _, _, _}, _,
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) ->
+ {stop, {shutdown, normal}};
+handle_info({'EXIT', Pid, _Reason}, StateName,
+ #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) ->
+ %% It seems the user application has linked to us
+ %% - ignore that and let the monitor handle this
+ {next_state, StateName, State};
+%%% So that terminate will be run when supervisor issues shutdown
+handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
+ {stop, shutdown, State};
+handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
+ %% Handle as transport close"
+ {stop,{shutdown, transport_closed}, State};
+handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
+ {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),
+ {next_state, StateName, State}.
+
+%%====================================================================
+%% Application Data
+%%====================================================================
+read_application_data(Data,
+ #state{user_data_buffer =
+ {Front0,BufferSize0,Rear0},
+ connection_env =
+ #connection_env{erl_dist_handle = DHandle}}
+ = State) ->
+ Front = Front0,
+ BufferSize = BufferSize0 + byte_size(Data),
+ Rear = [Data|Rear0],
+ case DHandle of
+ undefined ->
+ read_application_data(State, Front, BufferSize, Rear);
+ _ ->
+ 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}}}
+ end
+ end.
+passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
+ %% Assert! Erl distribution uses active sockets
+ connection_env = #connection_env{erl_dist_handle = undefined}}
+ = State0, StateName, Connection, StartTimerAction) ->
+ case BufferSize of
+ 0 ->
+ Connection:next_event(StateName, no_record, State0, StartTimerAction);
+ _ ->
+ case read_application_data(State0, Front, BufferSize, Rear) of
+ {stop, _, _} = ShutdownError ->
+ ShutdownError;
+ {Record, State} ->
+ case State#state.start_or_recv_from of
+ undefined ->
+ %% Cancel recv timeout as data has been delivered
+ Connection:next_event(StateName, Record, State,
+ [{{timeout, recv}, infinity, timeout}]);
+ _ ->
+ Connection:next_event(StateName, Record, State, StartTimerAction)
+ end
+ end
+ end.
+
+%%====================================================================
+%% Hibernation
+%%====================================================================
+
+hibernate_after(connection = StateName,
+ #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
+ Actions) ->
+ {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
+hibernate_after(StateName, State, Actions) ->
+ {next_state, StateName, State, Actions}.
+
+%%====================================================================
+%% Alert and close handling
+%%====================================================================
+send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:send_alert_in_connection(Alert, State);
+send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:send_alert(Alert, State).
+
+handle_own_alert(Alert0, _, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ try %% Try to tell the other side
+ send_alert(Alert0, StateName, State)
+ catch _:_ -> %% Can crash if we are in a uninitialized state
+ ignore
+ end,
+ try %% Try to tell the local user
+ Alert = Alert0#alert{role = Role},
+ log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert),
+ handle_normal_shutdown(Alert,StateName, State)
+ catch _:_ ->
+ ok
+ end,
+ {stop, {shutdown, own_alert}, State}.
+
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ trackers = Trackers},
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
+
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ trackers = Trackers},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ socket_options = Opts,
+ start_or_recv_from = RecvFrom} = State) ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
+
+handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ host = Host,
+ port = Port,
+ trackers = Trackers,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ ssl_options = #{log_level := LogLevel},
+ start_or_recv_from = From,
+ session = Session,
+ socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role, Connection:protocol_name(),
+ StateName, Alert),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
+ {stop, {shutdown, normal}, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ downgrade= StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, Alert}]};
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0,
+ StateName, #state{static_env = #static_env{role = Role}} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ handle_normal_shutdown(Alert, StateName, State),
+ {stop,{shutdown, peer_close}, State};
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, internal}},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert),
+ handle_normal_shutdown(Alert, StateName, State),
+ {stop,{shutdown, peer_close}, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = #{log_level := LogLevel}
+ } = State0) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ gen_statem:reply(From, {error, renegotiation_rejected}),
+ State = Connection:reinit_handshake_data(State0),
+ Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = #{log_level := LogLevel}
+ } = State0) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ gen_statem:reply(From, {error, renegotiation_rejected}),
+ %% Go back to connection!
+ State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
+ Connection:next_event(connection, no_record, State);
+
+%% Gracefully log and ignore all other warning alerts
+handle_alert(#alert{level = ?WARNING} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = opposite_role(Role)}),
+ Connection:next_event(StateName, no_record, State).
+handle_trusted_certs_db(#state{ssl_options =
+ #{cacertfile := <<>>, cacerts := []}}) ->
+ %% No trusted certs specified
+ ok;
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ cert_db = CertDb},
+ ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
+ %% Certs provided as DER directly can not be shared
+ %% with other connections and it is safe to delete them when the connection ends.
+ ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
+handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) ->
+ %% Something went wrong early (typically cacertfile does not
+ %% exist) so there is nothing to handle
+ ok;
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ file_ref_db = RefDb},
+ ssl_options = #{cacertfile := File}}) ->
+ case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
+ 0 ->
+ ssl_manager:clean_cert_db(Ref, File);
+ _ ->
+ ok
+ end.
+
+maybe_invalidate_session({3, 4},_, _, _, _, _) ->
+ ok;
+maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
+ maybe_invalidate_session(Type, Role, Host, Port, Session).
+
+maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
+ invalidate_session(Role, Host, Port, Session);
+maybe_invalidate_session(_, _, _, _, _) ->
+ ok.
+
+terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) ->
+ %% Happens when user closes the connection using ssl:close/1
+ %% we want to guarantee that Transport:close has been called
+ %% when ssl:close/1 returns unless it is a downgrade where
+ %% we want to guarantee that close alert is received before
+ %% returning. In both cases terminate has been run manually
+ %% before run by gen_statem which will end up here
+ ok;
+terminate({shutdown, transport_closed} = Reason,
+ _StateName, #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate({shutdown, own_alert}, _StateName, #state{
+ static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
+ handle_trusted_certs_db(State),
+ case application:get_env(ssl, alert_timeout) of
+ {ok, Timeout} when is_integer(Timeout) ->
+ Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined);
+ _ ->
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
+ end;
+terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate(Reason, connection, #state{static_env = #static_env{
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ connection_states = ConnectionStates,
+ ssl_options = #{padding_check := Check}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Alert = terminate_alert(Reason),
+ %% Send the termination ALERT if possible
+ catch (ok = Connection:send_alert_in_connection(Alert, State)),
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
+terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
+ protocol_cb = Connection,
+ socket = Socket}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined).
+
+%%====================================================================
+%% Log handling
+%%====================================================================
+format_status(normal, [_, StateName, State]) ->
+ [{data, [{"State", {StateName, State}}]}];
+format_status(terminate, [_, StateName, State]) ->
+ SslOptions = (State#state.ssl_options),
+ NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
+ cert => ?SECRET_PRINTOUT,
+ cacerts => ?SECRET_PRINTOUT,
+ key => ?SECRET_PRINTOUT,
+ dh => ?SECRET_PRINTOUT,
+ psk_identity => ?SECRET_PRINTOUT,
+ srp_identity => ?SECRET_PRINTOUT},
+ [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
+ protocol_buffers = ?SECRET_PRINTOUT,
+ user_data_buffer = ?SECRET_PRINTOUT,
+ handshake_env = ?SECRET_PRINTOUT,
+ connection_env = ?SECRET_PRINTOUT,
+ session = ?SECRET_PRINTOUT,
+ ssl_options = NewOptions,
+ flight_buffer = ?SECRET_PRINTOUT}
+ }}]}].
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+connection_fsm(#{versions := [{3,4}]}) ->
+ tls_connection_1_3;
+connection_fsm(_) ->
+ tls_connection.
+
+call(FsmPid, Event) ->
+ try gen_statem:call(FsmPid, Event)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{{shutdown, _},_} ->
+ {error, closed}
+ end.
+
+check_hostname(#state{ssl_options = SslOptions}, Hostname) ->
+ case is_sni_value(Hostname) of
+ true ->
+ case is_hostname_recognized(SslOptions, Hostname) of
+ true ->
+ valid;
+ false ->
+ %% We should send an alert but for interoperability reasons we
+ %% allow the connection to be established.
+ %% ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME)
+ unrecognized_name
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME,
+ {sni_included_trailing_dot, Hostname})
+ end.
+
+is_sni_value(Hostname) ->
+ case hd(lists:reverse(Hostname)) of
+ $. ->
+ false;
+ _ ->
+ true
+ end.
+
+is_hostname_recognized(#{sni_fun := undefined,
+ sni_hosts := SNIHosts}, Hostname) ->
+ proplists:is_defined(Hostname, SNIHosts);
+is_hostname_recognized(_, _) ->
+ true.
+
+handle_sni_hostname(Hostname,
+ #state{static_env = #static_env{role = Role} = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
+ NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
+ case NewOptions of
+ undefined ->
+ State0;
+ _ ->
+ {ok, #{cert_db_ref := Ref,
+ cert_db_handle := CertDbHandle,
+ fileref_db_handle := FileRefHandle,
+ session_cache := CacheHandle,
+ crl_db_info := CRLDbHandle,
+ private_key := Key,
+ dh_params := DHParams,
+ own_certificates := OwnCerts}} =
+ ssl_config:init(NewOptions, Role),
+ State0#state{
+ session = State0#state.session#session{own_certificates = OwnCerts},
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = NewOptions,
+ handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
+ diffie_hellman_params = DHParams}
+ }
+ end.
+
+update_ssl_options_from_sni(#{sni_fun := SNIFun,
+ sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
+ SSLOption =
+ case SNIFun of
+ undefined ->
+ proplists:get_value(SNIHostname,
+ SNIHosts);
+ SNIFun ->
+ SNIFun(SNIHostname)
+ end,
+ case SSLOption of
+ undefined ->
+ undefined;
+ _ ->
+ ssl:handle_options(SSLOption, server, OrigSSLOptions)
+ end.
+
+set_sni_guided_cert_selection(#state{handshake_env = HsEnv0} = State, Bool) ->
+ HsEnv = HsEnv0#handshake_env{sni_guided_cert_selection = Bool},
+ State#state{handshake_env = HsEnv}.
+
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
+ Initiater == internal ->
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
+ gen_statem:reply(From, ok),
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
+ start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
+ gen_statem:reply(StartFrom, connected),
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
+ start_or_recv_from = undefined};
+ack_connection(State) ->
+ State.
+
+no_records(Extensions) ->
+ maps:map(fun(_, Value) ->
+ ssl_handshake:extension_value(Value)
+ end, Extensions).
+
+handle_active_option(false, connection = StateName, To, Reply, State) ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+
+handle_active_option(_, connection = StateName, To, Reply, #state{static_env = #static_env{role = Role},
+ connection_env = #connection_env{terminated = true},
+ user_data_buffer = {_,0,_}} = State) ->
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_delivered),
+ handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop_and_reply,{shutdown, peer_close}, [{reply, To, Reply}]};
+handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
+ user_data_buffer = {_,0,_}} = State0) ->
+ case Connection:next_event(StateName0, no_record, State0) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, _, _} = Stop ->
+ Stop
+ end;
+handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) ->
+ %% Active once already set
+ {next_state, StateName, State, [{reply, To, Reply}]};
+
+%% user_data_buffer nonempty
+handle_active_option(_, StateName0, To, Reply,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
+ case read_application_data(<<>>, State0) of
+ {stop, _, _} = Stop ->
+ Stop;
+ {Record, State1} ->
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_event(StateName0, Record, State1) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, _, _} = Stop ->
+ Stop
+ end
+ end.
+
+read_application_data(#state{
+ socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) ->
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead).
+
+%% Pick binary from queue front, if empty wait for more data
+read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin);
+read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ 0 = BufferSize, % Assert
+ {no_record, State#state{socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {Front,BufferSize,Rear}}};
+read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin).
+
+read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) ->
+ %% Done with this binary - get next
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead);
+read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) ->
+ %% Decode one packet from a binary
+ case get_data(SocketOpts0, BytesToRead, Bin0) of
+ {ok, Data, Bin} -> % Send data
+ BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)),
+ read_application_data_deliver(
+ State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data);
+ {more, undefined} ->
+ %% We need more data, do not know how much
+ if
+ byte_size(Bin0) < BufferSize0 ->
+ %% We have more data in the buffer besides the first binary - concatenate all and retry
+ Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ read_application_data_bin(
+ State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin);
+ true ->
+ %% All data is in the first binary, no use to retry - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}
+ end;
+ {more, Size} when Size =< BufferSize0 ->
+ %% We have a packet in the buffer - collect it in a binary and decode
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]),
+ Bin = iolist_to_binary(Data),
+ read_application_data_bin(
+ State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin);
+ {more, _Size} ->
+ %% We do not have a packet in the buffer - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ passive ->
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ {error,_Reason} ->
+ %% Invalid packet in packet mode
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ deliver_packet_error(
+ Connection:pids(State), Transport, Socket, SocketOpts0,
+ Buffer, Pid, RecvFrom, Trackers, Connection),
+ {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Buffer],BufferSize0,[]}}}
+ end.
+
+read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) ->
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ SocketOpts =
+ deliver_app_data(
+ Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection),
+ if
+ SocketOpts#socket_options.active =:= false ->
+ %% Passive mode, wait for active once or recv
+ {no_record,
+ State#state{
+ user_data_buffer = {Front,BufferSize,Rear},
+ start_or_recv_from = undefined,
+ bytes_to_read = undefined,
+ socket_options = SocketOpts
+ }};
+ true -> %% Try to deliver more data
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined)
+ end.
+
+
+read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) ->
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin);
+read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) ->
+ BufferSize = 0,
+ {Front,BufferSize,Rear};
+read_application_dist_data(DHandle, [], BufferSize, Rear) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_dist_data(DHandle, Front, BufferSize, [], Bin).
+%%
+read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
+ case Bin0 of
+ %%
+ %% START Optimization
+ %% It is cheaper to match out several packets in one match operation than to loop for each
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary,
+ SizeD:32, DataD:SizeD/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD ->
+ %% We have 4 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ erlang:dist_ctrl_put_data(DHandle, DataD),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC ->
+ %% We have 3 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB ->
+ %% We have 2 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest);
+ %% END Optimization
+ %%
+ %% Basic one packet code path
+ <<Size:32, Data:Size/binary, Rest/binary>> ->
+ %% We have a complete packet in the first binary
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
+ <<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
+ %% We have a complete packet in the buffer
+ %% - fetch the missing content from the buffer front
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
+ <<Bin/binary>> ->
+ %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
+ %% match out the whole binary which will trick the optimization into keeping the match context
+ %% for the first binary contains complete packet code above
+ case Bin of
+ <<_Size:32, _InsufficientData/binary>> ->
+ %% We have a length field in the first binary but there is not enough data
+ %% in the buffer to form a complete packet - await more data
+ {[Bin|Front0],BufferSize,Rear0};
+ <<IncompleteLengthField/binary>> when 4 < BufferSize ->
+ %% We do not have a length field in the first binary but the buffer
+ %% contains enough data to maybe form a packet
+ %% - fetch a tiny binary from the buffer front to complete the length field
+ {LengthField,Front,Rear} =
+ case IncompleteLengthField of
+ <<>> ->
+ iovec_from_front(4, Front0, Rear0, []);
+ _ ->
+ iovec_from_front(
+ 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField])
+ end,
+ LengthBin = iolist_to_binary(LengthField),
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
+ <<IncompleteLengthField/binary>> ->
+ %% We do not have enough data in the buffer to even form a length field - await more data
+ case IncompleteLengthField of
+ <<>> ->
+ {Front0,BufferSize,Rear0};
+ _ ->
+ {[IncompleteLengthField|Front0],BufferSize,Rear0}
+ end
+ end
+ end.
+
+iovec_from_front(0, Front, Rear, Acc) ->
+ {lists:reverse(Acc),Front,Rear};
+iovec_from_front(Size, [], Rear, Acc) ->
+ case Rear of
+ %% Avoid lists:reverse/1 for simple cases.
+ %% Case clause for [] to avoid infinite loop.
+ [_] ->
+ iovec_from_front(Size, Rear, [], Acc);
+ [Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2], [], Acc);
+ [Bin3,Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc);
+ [_,_,_|_] = Rear ->
+ iovec_from_front(Size, lists:reverse(Rear), [], Acc)
+ end;
+iovec_from_front(Size, [Bin|Front], Rear, []) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {[Last],Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {[Last],[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, []);
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin])
+ end;
+iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {lists:reverse(Acc, [Last]),Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {lists:reverse(Acc, [Last]),[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, Acc);
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
+ end.
+%% Picks ClientData
+get_data(#socket_options{active=false}, undefined, _Bin) ->
+ %% Recv timed out save buffer data until next recv
+ passive;
+get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin)
+ when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
+ case Bin of
+ <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
+ %% Active true or once, or passive mode recv(0)
+ {ok, Bin, <<>>};
+ <<Data:BytesToRead/binary, Rest/binary>> ->
+ %% Passive Mode, recv(Bytes)
+ {ok, Data, Rest};
+ <<_/binary>> ->
+ %% Passive Mode not enough data
+ {more, BytesToRead}
+ end;
+get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
+ PacketOpts = [{packet_size, Size}],
+ decode_packet(Type, Bin, PacketOpts).
+
+decode_packet({http, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph, Buffer, PacketOpts);
+decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph_bin, Buffer, PacketOpts);
+decode_packet(Type, Buffer, PacketOpts) ->
+ erlang:decode_packet(Type, Buffer, PacketOpts).
+
+%% Just like with gen_tcp sockets, an ssl socket that has been configured with
+%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
+%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
+%% represent the current state as follows:
+%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
+%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
+%% Note that if the user has explicitly configured the socket to expect
+%% HTTP headers using the {packet, httph} option, we don't do any automatic
+%% switching of states.
+deliver_app_data(CPids, Transport, Socket,
+ #socket_options{active=Active, packet=Type} = SOpts,
+ Data, Pid, From, Trackers, Connection) ->
+ send_or_reply(Active, Pid, From,
+ format_reply(CPids, Transport, Socket,
+ SOpts, Data, Trackers, Connection)),
+ SO =
+ case Data of
+ {P, _, _, _}
+ when ((P =:= http_request) or (P =:= http_response)),
+ ((Type =:= http) or (Type =:= http_bin)) ->
+ SOpts#socket_options{packet={Type, headers}};
+ http_eoh when tuple_size(Type) =:= 2 ->
+ %% End of headers - expect another Request/Response line
+ {Type1, headers} = Type,
+ SOpts#socket_options{packet=Type1};
+ _ ->
+ SOpts
+ end,
+ case Active of
+ once ->
+ SO#socket_options{active=false};
+ 1 ->
+ send_user(Pid,
+ format_passive(CPids, Transport,
+ Socket, Trackers, Connection)),
+ SO#socket_options{active=false};
+ N when is_integer(N) ->
+ SO#socket_options{active=N - 1};
+ _ ->
+ SO
+ end.
+
+format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet,
+ header = Header}, Data, _, _) ->
+ {ok, do_format_reply(Mode, Packet, Header, Data)};
+format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
+ header = Header}, Data, Trackers, Connection) ->
+ {ssl, Connection:socket(CPids, Transport, Socket, Trackers),
+ do_format_reply(Mode, Packet, Header, Data)}.
+
+deliver_packet_error(CPids, Transport, Socket,
+ SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) ->
+ send_or_reply(Active, Pid, From, format_packet_error(CPids,
+ Transport, Socket, SO, Data, Trackers, Connection)).
+
+format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
+ {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
+format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
+ Data, Trackers, Connection) ->
+ {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers),
+ {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
+
+do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
+ header(N, Data);
+do_format_reply(binary, _, _, Data) ->
+ Data;
+do_format_reply(list, Packet, _, Data)
+ when Packet == http; Packet == {http, headers};
+ Packet == http_bin; Packet == {http_bin, headers};
+ Packet == httph; Packet == httph_bin ->
+ Data;
+do_format_reply(list, _,_, Data) ->
+ binary_to_list(Data).
+
+format_passive(CPids, Transport, Socket, Trackers, Connection) ->
+ {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}.
+
+header(0, <<>>) ->
+ <<>>;
+header(_, <<>>) ->
+ [];
+header(0, Binary) ->
+ Binary;
+header(N, Binary) ->
+ <<?BYTE(ByteN), NewBinary/binary>> = Binary,
+ [ByteN | header(N-1, NewBinary)].
+
+send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
+ gen_statem:reply(From, Data);
+send_or_reply(false, Pid, undefined, _) when is_pid(Pid) ->
+ ok;
+send_or_reply(_, no_pid, _, _) ->
+ ok;
+send_or_reply(_, Pid, _, Data) ->
+ send_user(Pid, Data).
+
+send_user(Pid, Msg) ->
+ Pid ! Msg,
+ ok.
+
+alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
+alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection).
+
+alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
+
+alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
+ %% If there is an outstanding ssl_accept | recv
+ %% From will be defined and send_or_reply will
+ %% send the appropriate error message.
+ ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
+ send_or_reply(Active, Pid, From, {error, ReasonCode});
+alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
+ case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
+ closed ->
+ send_or_reply(Active, Pid, From,
+ {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)});
+ ReasonCode ->
+ send_or_reply(Active, Pid, From,
+ {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode})
+ end.
+
+log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => own}, Alert#alert.where);
+log_alert(Level, Role, ProtocolName, StateName, Alert) ->
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => peer}, Alert#alert.where).
+
+terminate_alert(normal) ->
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
+terminate_alert({Reason, _}) when Reason == close;
+ Reason == shutdown ->
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
+terminate_alert(_) ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR).
+
+
+invalidate_session(client, Host, Port, Session) ->
+ ssl_manager:invalidate_session(Host, Port, Session);
+invalidate_session(server, _, _, _) ->
+ ok.
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname,
+ resumption = Resumption},
+ session = #session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ srp_username = SrpUsername,
+ ecc = ECCCurve} = Session,
+ connection_states = #{current_write := CurrentWrite},
+ connection_env = #connection_env{negotiated_version = {_,_} = Version},
+ ssl_options = #{protocol := Protocol} = Opts}) ->
+ RecordCB = record_cb(Protocol),
+ CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ IsNamedCurveSuite = lists:member(KexAlg,
+ [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]),
+ CurveInfo = case ECCCurve of
+ {namedCurve, Curve} when IsNamedCurveSuite ->
+ [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
+ _ ->
+ []
+ end,
+ MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of
+ MaxFragmentLength when is_integer(MaxFragmentLength) ->
+ [{max_fragment_length, MaxFragmentLength}];
+ _ ->
+ []
+ end,
+ [{protocol, RecordCB:protocol_version(Version)},
+ {session_id, SessionId},
+ {session_data, term_to_binary(Session)},
+ {session_resumption, Resumption},
+ {selected_cipher_suite, CipherSuiteDef},
+ {sni_hostname, SNIHostname},
+ {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts).
+
+security_info(#state{connection_states = ConnectionStates,
+ static_env = #static_env{role = Role},
+ ssl_options = #{keep_secrets := KeepSecrets}}) ->
+ ReadState = ssl_record:current_connection_state(ConnectionStates, read),
+ #{security_parameters :=
+ #security_parameters{client_random = ClientRand,
+ server_random = ServerRand,
+ master_secret = MasterSecret,
+ application_traffic_secret = AppTrafSecretRead}} = ReadState,
+ BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}],
+ if KeepSecrets =/= true ->
+ BaseSecurityInfo;
+ true ->
+ #{security_parameters :=
+ #security_parameters{application_traffic_secret = AppTrafSecretWrite}} =
+ ssl_record:current_connection_state(ConnectionStates, write),
+ BaseSecurityInfo ++
+ if Role == server ->
+ [{server_traffic_secret_0, AppTrafSecretWrite}, {client_traffic_secret_0, AppTrafSecretRead}];
+ true ->
+ [{client_traffic_secret_0, AppTrafSecretWrite}, {server_traffic_secret_0, AppTrafSecretRead}]
+ end ++
+ case ReadState of
+ #{client_handshake_traffic_secret := ClientHSTrafficSecret,
+ server_handshake_traffic_secret := ServerHSTrafficSecret} ->
+ [{client_handshake_traffic_secret, ClientHSTrafficSecret},
+ {server_handshake_traffic_secret, ServerHSTrafficSecret}];
+ _ ->
+ []
+ end
+ end.
+
+record_cb(tls) ->
+ tls_record;
+record_cb(dtls) ->
+ dtls_record.
+
+get_socket_opts(_, _,_,[], _, Acc) ->
+ {ok, Acc};
+get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{mode, SockOpts#socket_options.mode} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) ->
+ case SockOpts#socket_options.packet of
+ {Type, headers} ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
+ Type ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
+ end;
+get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{header, SockOpts#socket_options.header} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{active, SockOpts#socket_options.active} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
+ case Connection:getopts(Transport, Socket, [Tag]) of
+ {ok, [Opt]} ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]);
+ {error, Reason} ->
+ {error, {options, {socket_options, Tag, Reason}}}
+ end;
+get_socket_opts(_,_, _,Opts, _,_) ->
+ {error, {options, {socket_options, Opts, function_clause}}}.
+
+set_socket_opts(_,_,_, [], SockOpts, []) ->
+ {ok, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) ->
+ %% Set non emulated options
+ try ConnectionCb:setopts(Transport, Socket, Other) of
+ ok ->
+ {ok, SockOpts};
+ {error, InetError} ->
+ {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
+ catch
+ _:Error ->
+ %% So that inet behavior does not crash our process
+ {{error, {options, {socket_options, Other, Error}}}, SockOpts}
+ end;
+
+set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
+ when Mode == list; Mode == binary ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{mode = Mode}, Other);
+set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
+ when Packet == raw;
+ Packet == 0;
+ Packet == 1;
+ Packet == 2;
+ Packet == 4;
+ Packet == asn1;
+ Packet == cdr;
+ Packet == sunrm;
+ Packet == fcgi;
+ Packet == tpkt;
+ Packet == line;
+ Packet == http;
+ Packet == httph;
+ Packet == http_bin;
+ Packet == httph_bin ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{packet = Packet}, Other);
+set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
+ when is_integer(Header) ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{header = Header}, Other);
+set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) ->
+ {{error,{options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
+ when Active == once;
+ Active == true;
+ Active == false ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
+ SockOpts=#socket_options{active = Active0}, Other)
+ when Active1 >= -32768, Active1 =< 32767 ->
+ Active = if
+ is_integer(Active0), Active0 + Active1 < -32768 ->
+ error;
+ is_integer(Active0), Active0 + Active1 =< 0 ->
+ false;
+ is_integer(Active0), Active0 + Active1 > 32767 ->
+ error;
+ Active1 =< 0 ->
+ false;
+ is_integer(Active0) ->
+ Active0 + Active1;
+ true ->
+ Active1
+ end,
+ case Active of
+ error ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+ _ ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other)
+ end;
+set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]).
+ssl_options_list(SslOptions) ->
+ L = maps:to_list(SslOptions),
+ ssl_options_list(L, []).
+
+new_emulated([], EmOpts) ->
+ EmOpts;
+new_emulated(NewEmOpts, _) ->
+ NewEmOpts.
+
+ssl_options_list([], Acc) ->
+ lists:reverse(Acc);
+%% Skip internal options, only return user options
+ssl_options_list([{protocol, _}| T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{erl_dist, _}|T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{renegotiate_at, _}|T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{max_fragment_length, _}|T], Acc) ->
+ %% skip max_fragment_length from options since it is taken above from connection_states
+ ssl_options_list(T, Acc);
+ssl_options_list([{ciphers = Key, Value}|T], Acc) ->
+ ssl_options_list(T,
+ [{Key, lists:map(
+ fun(Suite) ->
+ ssl_cipher_format:suite_bin_to_map(Suite)
+ end, Value)}
+ | Acc]);
+ssl_options_list([{Key, Value}|T], Acc) ->
+ ssl_options_list(T, [{Key, Value} | Acc]).
+
+%% Maybe add NSS keylog info according to
+%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+maybe_add_keylog(Info) ->
+ maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info).
+
+maybe_add_keylog({_, 'tlsv1.2'}, Info) ->
+ try
+ {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
+ {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info),
+ ClientRandom = binary:decode_unsigned(ClientRandomBin),
+ MasterSecret = binary:decode_unsigned(MasterSecretBin),
+ Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])],
+ Info ++ [{keylog,Keylog}]
+ catch
+ _Cxx:_Exx ->
+ Info
+ end;
+maybe_add_keylog({_, 'tlsv1.3'}, Info) ->
+ try
+ {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
+ {client_traffic_secret_0, ClientTrafficSecret0Bin} = lists:keyfind(client_traffic_secret_0, 1, Info),
+ {server_traffic_secret_0, ServerTrafficSecret0Bin} = lists:keyfind(server_traffic_secret_0, 1, Info),
+ {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info),
+ {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info),
+ {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info),
+ ClientRandom = binary:decode_unsigned(ClientRandomBin),
+ ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf),
+ ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf),
+ ClientHSecret = keylog_secret(ClientHSecretBin, Prf),
+ ServerHSecret = keylog_secret(ServerHSecretBin, Prf),
+ Keylog = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret,
+ io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret,
+ io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0,
+ io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0],
+ Info ++ [{keylog,Keylog}]
+ catch
+ _Cxx:_Exx ->
+ Info
+ end;
+maybe_add_keylog(_, Info) ->
+ Info.
+
+keylog_secret(SecretBin, sha256) ->
+ io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]);
+keylog_secret(SecretBin, sha384) ->
+ io_lib:format("~96.16.0B", [binary:decode_unsigned(SecretBin)]);
+keylog_secret(SecretBin, sha512) ->
+ io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]).
+
+maybe_generate_client_shares(#{versions := [Version|_],
+ supported_groups :=
+ #supported_groups{
+ supported_groups = [Group|_]}})
+ when Version =:= {3,4} ->
+ %% Generate only key_share entry for the most preferred group
+ ssl_cipher:generate_client_shares([Group]);
+maybe_generate_client_shares(_) ->
+ undefined.
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 33d2b90138..fd5c202c04 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -29,6 +29,9 @@
-behaviour(gen_statem).
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
-include("tls_connection.hrl").
-include("tls_handshake.hrl").
-include("tls_handshake_1_3.hrl").
@@ -38,40 +41,62 @@
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
%% Internal application API
%% Setup
--export([start_fsm/8, init/1, pids/1, initialize_tls_sender/1]).
+-export([start_fsm/8,
+ init/1,
+ pids/1,
+ initialize_tls_sender/1]).
%% State transition handling
--export([next_event/3, next_event/4,
+-export([next_event/3,
+ next_event/4,
handle_protocol_record/3]).
%% Handshake handling
--export([renegotiation/2, renegotiate/2, send_handshake/2,
+-export([renegotiation/2,
+ renegotiate/2,
+ send_handshake/2,
send_handshake_flight/1,
- queue_handshake/2, queue_change_cipher/2,
- reinit/1, reinit_handshake_data/1, select_sni_extension/1,
- empty_connection_state/2, maybe_generate_client_shares/1]).
+ queue_handshake/2,
+ queue_change_cipher/2,
+ reinit/1,
+ reinit_handshake_data/1,
+ select_sni_extension/1,
+ empty_connection_state/2]).
%% Alert and close handling
--export([send_alert/2, send_alert_in_connection/2,
+-export([send_alert/2,
+ send_alert_in_connection/2,
send_sync_alert/2,
- close/5, protocol_name/0]).
+ close/5,
+ protocol_name/0]).
%% Data handling
--export([socket/4, setopts/3, getopts/3, handle_info/3]).
+-export([socket/4,
+ setopts/3,
+ getopts/3,
+ handle_info/3]).
%% gen_statem state functions
--export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, user_hello/3, wait_ocsp_stapling/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+-export([initial_hello/3,
+ config_error/3,
+ downgrade/3,
+ hello/3,
+ user_hello/3,
+ wait_ocsp_stapling/3,
+ certify/3,
+ cipher/3,
+ abbreviated/3,
connection/3]).
%% gen_statem callbacks
--export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
+-export([callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
-export([encode_handshake/4]).
@@ -90,8 +115,8 @@ start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts,
{ok, Sender} = tls_sender:start(),
{ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_connection:handshake(SslSocket, Timeout)
+ {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
@@ -104,8 +129,8 @@ start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts,
{ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]),
{ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_connection:handshake(SslSocket, Timeout)
+ {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
@@ -119,7 +144,7 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
session_cache_cb = CacheCb
},
ssl_options = SslOptions,
- session = Session0} = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
+ session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
State = case Role of
client ->
Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0),
@@ -128,10 +153,10 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
State1
end,
initialize_tls_sender(State),
- gen_statem:enter_loop(?MODULE, [], init, State)
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch throw:Error ->
EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], error, EState)
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
pids(#state{protocol_specific = #{sender := Sender}}) ->
@@ -291,11 +316,11 @@ next_event(StateName, Record, State) ->
next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) ->
case next_record(StateName, State0) of
{no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
+ ssl_gen_statem:hibernate_after(StateName, State, Actions);
{Record, State} ->
next_event(StateName, Record, State, Actions);
#alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
{stop, {shutdown, own_alert}, State0}
end;
next_event(StateName, #ssl_tls{} = Record, State, Actions) ->
@@ -307,7 +332,7 @@ next_event(StateName, #alert{} = Alert, State, Actions) ->
handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
#state{start_or_recv_from = From,
socket_options = #socket_options{active = false}} = State0) when From =/= undefined ->
- case ssl_connection:read_application_data(Data, State0) of
+ case ssl_gen_statem:read_application_data(Data, State0) of
{stop, _, _} = Stop->
Stop;
{Record, #state{start_or_recv_from = Caller} = State} ->
@@ -320,7 +345,7 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat
next_event(StateName, Record, State, TimerAction)
end;
handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
- case ssl_connection:read_application_data(Data, State0) of
+ case ssl_gen_statem:read_application_data(Data, State0) of
{stop, _, _} = Stop->
Stop;
{Record, State} ->
@@ -349,7 +374,7 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
Events = tls_handshake_events(Packets),
case StateName of
connection ->
- ssl_connection:hibernate_after(StateName, State, Events);
+ ssl_gen_statem:hibernate_after(StateName, State, Events);
_ ->
HsEnv = State#state.handshake_env,
{next_state, StateName,
@@ -359,7 +384,7 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
end
end
catch throw:#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0)
end;
%%% TLS record protocol level change cipher messages
handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
@@ -371,13 +396,13 @@ handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
Alerts = [_|_] ->
handle_alerts(Alerts, {next_state, StateName, State});
[] ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
Version, StateName, State);
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State)
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
Version, StateName, State)
end;
@@ -565,93 +590,20 @@ getopts(Transport, Socket, Tag) ->
%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-init({call, From}, {start, Timeout},
- #state{static_env = #static_env{role = client,
- host = Host,
- port = Port,
- transport_cb = Transport,
- socket = Socket},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
- ocsp_stapling_state = OcspState0} = HsEnv,
- connection_env = CEnv,
- ssl_options = #{log_level := LogLevel,
- %% Use highest version in initial ClientHello.
- %% Versions is a descending list of supported versions.
- versions := [HelloVersion|_] = Versions,
- session_tickets := SessionTickets,
- ocsp_stapling := OcspStaplingOpt,
- ocsp_nonce := OcspNonceOpt} = SslOpts,
- session = Session,
- connection_states = ConnectionStates0
- } = State0) ->
-
- KeyShare = maybe_generate_client_shares(SslOpts),
- %% Update UseTicket in case of automatic session resumption
- {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
- TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
- OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
- Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Session#session.session_id,
- Renegotiation,
- Session#session.own_certificates,
- KeyShare,
- TicketData,
- OcspNonce),
-
- Handshake0 = ssl_handshake:init_handshake_history(),
-
- %% Update pre_shared_key extension with binders (TLS 1.3)
- Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion),
-
- MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined),
- ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
-
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0),
-
- tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
-
- %% RequestedVersion is used as the legacy record protocol version and shall be
- %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the
- %% lowest supported protocol version.
- %%
- %% negotiated_version is also used by the TLS 1.3 state machine and is set after
- %% ServerHello is processed.
- RequestedVersion = tls_record:hello_version(Versions),
- State = State1#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{
- negotiated_version = RequestedVersion},
- session = Session,
- handshake_env = HsEnv#handshake_env{
- tls_handshake_history = Handshake,
- ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}},
- start_or_recv_from = From,
- key_share = KeyShare},
- next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]);
-
-init(Type, Event, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State, ?MODULE).
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
+-spec config_error(gen_statem:event_type(),
{start, timeout()} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-error({call, From}, {start, _Timeout},
- #state{protocol_specific = #{error := Error}} = State) ->
- {stop_and_reply, {shutdown, normal},
- [{reply, From, {error, Error}}], State};
-
-error({call, _} = Call, Msg, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Call, Msg, State, ?MODULE);
-error(_, _, _) ->
- {keep_state_and_data, [postpone]}.
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
@@ -674,64 +626,21 @@ hello(internal, #server_hello{extensions = Extensions} = Hello,
{next_state, user_hello,
State#state{start_or_recv_from = undefined,
handshake_env = HsEnv#handshake_env{
- hello = Hello}}, [{reply, From, {ok, Extensions}}]};
-hello(internal, #client_hello{client_version = ClientVersion} = Hello,
- #state{ssl_options = SslOpts0} = State0) ->
-
+ hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{ssl_options = SslOpts0,
+ connection_env = CEnv} = State0) ->
case choose_tls_fsm(SslOpts0, Hello) of
tls_1_3_fsm ->
%% Continue in TLS 1.3 'start' state
{next_state, start, State0, [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]};
tls_1_0_to_1_2_fsm ->
- case ssl_connection:handle_sni_extension(State0, Hello) of
- #state{connection_states = ConnectionStates0,
- static_env = #static_env{trackers = Trackers},
- handshake_env = #handshake_env{
- kex_algorithm = KeyExAlg,
- renegotiation = {Renegotiation, _},
- negotiated_protocol = CurrentProtocol,
- sni_guided_cert_selection = SNICertSelection} = HsEnv,
- connection_env = CEnv,
- session = #session{own_certificates = OwnCerts} = Session0,
- ssl_options = SslOpts} = State ->
- SessionTracker =
- proplists:get_value(session_id_tracker, Trackers),
- case tls_handshake:hello(Hello,
- SslOpts,
- {SessionTracker, Session0,
- ConnectionStates0, OwnCerts, KeyExAlg},
- Renegotiation) of
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, ClientVersion, hello,
- State#state{connection_env = CEnv#connection_env{negotiated_version
- = ClientVersion}});
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt0, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
- ServerHelloExt =
- case SNICertSelection of
- true ->
- ServerHelloExt0#{sni => #sni{hostname = ""}};
- false ->
- ServerHelloExt0
- end,
- ssl_connection:gen_handshake(?FUNCTION_NAME,
- internal,
- {common_client_hello, Type, ServerHelloExt},
- State#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = Version},
- handshake_env = HsEnv#handshake_env{
- hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
- negotiated_protocol = Protocol},
- session = Session
- }, ?MODULE)
- end;
+ case handle_client_hello(Hello, State0) of
+ {ServerHelloExt, Type, State} ->
+ {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]};
Alert ->
- Alert
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello,
+ State0#state{connection_env = CEnv#connection_env{negotiated_version
+ = ClientVersion}})
end
end;
hello(internal, #server_hello{} = Hello,
@@ -745,7 +654,7 @@ hello(internal, #server_hello{} = Hello,
ssl_options = SslOptions} = State) ->
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
+ ssl_gen_statem:handle_own_alert(Alert, ReqVersion, hello,
State#state{connection_env =
CEnv#connection_env{negotiated_version = ReqVersion}
});
@@ -767,10 +676,10 @@ hello(internal, #server_hello{} = Hello,
hello(info, Event, State) ->
handle_info(Event, ?FUNCTION_NAME, State);
hello(Type, Event, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State, ?MODULE).
+ ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
user_hello(Type, Event, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State, ?MODULE).
+ ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
@@ -779,7 +688,7 @@ user_hello(Type, Event, State) ->
abbreviated(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
abbreviated(Type, Event, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State, ?MODULE).
+ ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) ->
@@ -788,7 +697,7 @@ abbreviated(Type, Event, State) ->
wait_ocsp_stapling(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
wait_ocsp_stapling(Type, Event, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State, ?MODULE).
+ ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(), term(), #state{}) ->
@@ -797,7 +706,7 @@ wait_ocsp_stapling(Type, Event, State) ->
certify(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
certify(Type, Event, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State, ?MODULE).
+ ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
@@ -806,7 +715,7 @@ certify(Type, Event, State) ->
cipher(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State, ?MODULE).
+ ssl_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -819,42 +728,6 @@ connection({call, From}, {user_renegotiate, WriteState},
#state{connection_states = ConnectionStates} = State) ->
{next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}},
[{next_event,{call, From}, renegotiate}]};
-connection({call, From},
- {close, {Pid, _Timeout}},
- #state{connection_env = #connection_env{terminated = closed} = CEnv,
- protocol_specific = PS} = State) ->
- {next_state, downgrade, State#state{connection_env =
- CEnv#connection_env{terminated = true,
- downgrade = {Pid, From}},
- protocol_specific = PS#{active_n_toggle => true,
- active_n => 1}
- },
- [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
-connection({call, From},
- {close,{Pid, Timeout}},
- #state{connection_states = ConnectionStates,
- protocol_specific = #{sender := Sender} = PS,
- connection_env = CEnv
- } = State0) ->
- case tls_sender:downgrade(Sender, Timeout) of
- {ok, Write} ->
- %% User downgrades connection
- %% When downgrading an TLS connection to a transport connection
- %% we must recive the close alert from the peer before releasing the
- %% transport socket.
- State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- State0#state{connection_states =
- ConnectionStates#{current_write => Write}}),
- {next_state, downgrade, State#state{connection_env =
- CEnv#connection_env{downgrade = {Pid, From},
- terminated = true},
- protocol_specific = PS#{active_n_toggle => true,
- active_n => 1}
- },
- [{timeout, Timeout, downgrade}]};
- {error, timeout} ->
- {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
- end;
connection(internal, #hello_request{},
#state{static_env = #static_env{role = client,
host = Host,
@@ -924,7 +797,7 @@ connection(internal, #client_hello{},
State = reinit_handshake_data(State0),
next_event(?FUNCTION_NAME, no_record, State);
connection(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
@@ -948,7 +821,7 @@ downgrade(info, {CloseTag, Socket},
downgrade(info, Info, State) ->
handle_info(Info, ?FUNCTION_NAME, State);
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State).
%--------------------------------------------------------------------
%% gen_statem callbacks
@@ -960,14 +833,14 @@ terminate({shutdown, {sender_died, Reason}}, _StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport}}
= State) ->
- ssl_connection:handle_trusted_certs_db(State),
+ ssl_gen_statem:handle_trusted_certs_db(State),
close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, StateName, State) ->
- catch ssl_connection:terminate(Reason, StateName, State),
+ catch ssl_gen_statem:terminate(Reason, StateName, State),
ensure_sender_terminate(Reason, State).
format_status(Type, Data) ->
- ssl_connection:format_status(Type, Data).
+ ssl_gen_statem:format_status(Type, Data).
code_change(_OldVsn, StateName, State, _) ->
{ok, StateName, State}.
@@ -1106,7 +979,7 @@ handle_info({Protocol, _, Data}, StateName,
{Record, State} ->
next_event(StateName, Record, State);
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0)
end;
handle_info({PassiveTag, Socket}, StateName,
#state{static_env = #static_env{socket = Socket,
@@ -1131,11 +1004,10 @@ handle_info({CloseTag, Socket}, StateName,
socket = Socket,
close_tag = CloseTag},
handshake_env = #handshake_env{renegotiation = Type},
- connection_env = #connection_env{negotiated_version = Version},
session = Session} = State) when StateName =/= connection ->
- ssl_connection:maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
+ ssl_gen_statem:maybe_invalidate_session(Type, Role, Host, Port, Session),
Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
{stop, {shutdown, transport_closed}, State};
handle_info({CloseTag, Socket}, StateName,
#state{static_env = #static_env{
@@ -1164,7 +1036,7 @@ handle_info({CloseTag, Socket}, StateName,
%% ok
%% end,
Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
{stop, {shutdown, transport_closed}, State};
true ->
%% Wait for next socket operation (most probably
@@ -1185,7 +1057,53 @@ handle_info({'EXIT', Sender, Reason}, _,
#state{protocol_specific = #{sender := Sender}} = State) ->
{stop, {shutdown, {sender_died, Reason}}, State};
handle_info(Msg, StateName, State) ->
- ssl_connection:StateName(info, Msg, State, ?MODULE).
+ ssl_gen_statem:handle_info(Msg, StateName, State).
+
+handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) ->
+ case ssl_connection:handle_sni_extension(State0, Hello) of
+ #state{connection_states = ConnectionStates0,
+ static_env = #static_env{trackers = Trackers},
+ handshake_env = #handshake_env{
+ kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol,
+ sni_guided_cert_selection = SNICertSelection} = HsEnv,
+ connection_env = CEnv,
+ session = #session{own_certificates = OwnCerts} = Session0,
+ ssl_options = SslOpts} = State ->
+ SessionTracker = proplists:get_value(session_id_tracker, Trackers),
+ case tls_handshake:hello(Hello,
+ SslOpts,
+ {SessionTracker, Session0,
+ ConnectionStates0, OwnCerts, KeyExAlg},
+ Renegotiation) of
+ #alert{} = Alert ->
+ Alert;
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt0, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+ ServerHelloExt =
+ case SNICertSelection of
+ true ->
+ ServerHelloExt0#{sni => #sni{hostname = ""}};
+ false ->
+ ServerHelloExt0
+ end,
+ {ServerHelloExt, Type, State#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
+ client_hello_version = ClientVersion,
+ negotiated_protocol = Protocol},
+ session = Session
+ }}
+ end;
+ #alert{} = Alert ->
+ Alert
+ end.
handle_alerts([], Result) ->
Result;
@@ -1197,9 +1115,9 @@ handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
start_or_recv_from = From} = State}) when From == undefined ->
{next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}};
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State));
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)).
encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
Frag = tls_handshake:encode_handshake(Handshake, Version),
@@ -1220,7 +1138,7 @@ gen_info(Event, connection = StateName, #state{connection_env = #connection_env
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
malformed_data),
Version, StateName, State)
end;
@@ -1231,7 +1149,7 @@ gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_ve
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
@@ -1280,16 +1198,6 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) ->
end,
spawn(Kill).
-maybe_generate_client_shares(#{versions := [Version|_],
- supported_groups :=
- #supported_groups{
- supported_groups = [Group|_]}})
- when Version =:= {3,4} ->
- %% Generate only key_share entry for the most preferred group
- ssl_cipher:generate_client_shares([Group]);
-maybe_generate_client_shares(_) ->
- undefined.
-
choose_tls_fsm(#{versions := Versions},
#client_hello{
extensions = #{client_hello_versions :=
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 899f0635ef..59dea1c035 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -22,8 +22,12 @@
%%----------------------------------------------------------------------
%% Purpose: TLS-1.3 FSM
%%----------------------------------------------------------------------
-%% INIT (OTP specific)
-%% | ---> Error
+%% INITIAL_HELLO
+%% Client send
+%% first ClientHello
+%% | ---> CONFIG_ERROR
+%% | Send error to user
+%% | and shutdown
%% |
%% V
%% RFC 8446
@@ -117,8 +121,9 @@
-export([init/1, callback_mode/0, terminate/3, code_change/4, format_status/2]).
%% gen_statem state functions
--export([init/3,
- error/3,
+-export([initial_hello/3,
+ config_error/3,
+ user_hello/3,
start/3,
negotiated/3,
wait_cert/3,
@@ -131,7 +136,6 @@
downgrade/3
]).
-
%% Internal API
-export([setopts/3,
getopts/3,
@@ -184,25 +188,25 @@ 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
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
+ State = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
tls_connection:initialize_tls_sender(State),
- gen_statem:enter_loop(?MODULE, [], init, State)
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch throw:Error ->
EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], error, EState)
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
terminate({shutdown, {sender_died, Reason}}, _StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport}}
= State) ->
- ssl_connection:handle_trusted_certs_db(State),
+ ssl_gen_statem:handle_trusted_certs_db(State),
tls_connection:close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, StateName, State) ->
tls_connection:terminate(Reason, StateName, State).
format_status(Type, Data) ->
- ssl_connection:format_status(Type, Data).
+ ssl_gen_statem:format_status(Type, Data).
code_change(_OldVsn, StateName, State, _) ->
{ok, StateName, State}.
@@ -211,27 +215,58 @@ code_change(_OldVsn, StateName, State, _) ->
%% state callbacks
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
+-spec initial_hello(gen_statem:event_type(),
{start, timeout()} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-init(Type, Event, State) ->
+initial_hello(Type, Event, State) ->
tls_connection:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
+-spec config_error(gen_statem:event_type(),
{start, timeout()} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-error(Type, Event, State) ->
- tls_connection:?FUNCTION_NAME(Type, Event, State).
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+
+user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}}
+ = State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ Version, ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{hello = Hello},
+ ssl_options = Options0} = State0) ->
+ Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
+ 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}]};
+user_hello(_, _, _) ->
+ {keep_state_and_data, [postpone]}.
start(internal, #change_cipher_spec{}, State) ->
tls_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) ->
+ {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_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0);
{State, start} ->
{next_state, start, State, []};
{State, negotiated} ->
@@ -239,24 +274,33 @@ start(internal, #client_hello{} = Hello, State0) ->
{State, negotiated, PSK} -> %% Session Resumption with PSK
{next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
end;
+start(internal, #server_hello{extensions = Extensions} = ServerHello,
+ #state{ssl_options = #{handshake := hello},
+ handshake_env = HsEnv,
+ start_or_recv_from = From}
+ = 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_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0);
{State, NextState} ->
{next_state, NextState, State, []}
end;
start(info, Msg, State) ->
tls_connection:handle_info(Msg, ?FUNCTION_NAME, State);
start(Type, Msg, State) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, tls_connection).
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
negotiated(internal, #change_cipher_spec{}, State) ->
tls_connection:next_event(?FUNCTION_NAME, no_record, State);
negotiated(internal, Message, State0) ->
case tls_handshake_1_3:do_negotiated(Message, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, negotiated, State0);
{State, NextState} ->
{next_state, NextState, State, []}
end;
@@ -269,14 +313,14 @@ wait_cert(internal,
#certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert, State);
{State, NextState} ->
tls_connection:next_event(NextState, no_record, State)
end;
wait_cert(info, Msg, State) ->
tls_connection:handle_info(Msg, ?FUNCTION_NAME, State);
wait_cert(Type, Msg, State) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, tls_connection).
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
wait_cv(internal, #change_cipher_spec{}, State) ->
tls_connection:next_event(?FUNCTION_NAME, no_record, State);
@@ -284,14 +328,14 @@ wait_cv(internal,
#certificate_verify_1_3{} = CertificateVerify, State0) ->
case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cv, State);
{State, NextState} ->
tls_connection:next_event(NextState, no_record, State)
end;
wait_cv(info, Msg, State) ->
tls_connection:handle_info(Msg, ?FUNCTION_NAME, State);
wait_cv(Type, Msg, State) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, tls_connection).
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
wait_finished(internal, #change_cipher_spec{}, State) ->
@@ -300,24 +344,31 @@ wait_finished(internal,
#finished{} = Finished, State0) ->
case tls_handshake_1_3:do_wait_finished(Finished, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, finished, State0);
State1 ->
- {Record, State} = ssl_connection:prepare_connection(State1, tls_connection),
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, tls_connection),
tls_connection:next_event(connection, Record, State,
[{{timeout, handshake}, cancel}])
end;
wait_finished(info, Msg, State) ->
tls_connection:handle_info(Msg, ?FUNCTION_NAME, State);
wait_finished(Type, Msg, State) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, tls_connection).
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
wait_sh(internal, #change_cipher_spec{}, State) ->
tls_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) ->
+ {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) ->
case tls_handshake_1_3:do_wait_sh(Hello, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_sh, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_sh, State0);
{State1, start, ServerHello} ->
%% hello_retry_request: go to start
{next_state, start, State1, [{next_event, internal, ServerHello}]};
@@ -327,7 +378,7 @@ wait_sh(internal, #server_hello{} = Hello, State0) ->
wait_sh(info, Msg, State) ->
tls_connection:handle_info(Msg, ?FUNCTION_NAME, State);
wait_sh(Type, Msg, State) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, tls_connection).
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
wait_ee(internal, #change_cipher_spec{}, State) ->
@@ -335,14 +386,14 @@ wait_ee(internal, #change_cipher_spec{}, State) ->
wait_ee(internal, #encrypted_extensions{} = EE, State0) ->
case tls_handshake_1_3:do_wait_ee(EE, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_ee, State0);
{State1, NextState} ->
tls_connection:next_event(NextState, no_record, State1)
end;
wait_ee(info, Msg, State) ->
tls_connection:handle_info(Msg, ?FUNCTION_NAME, State);
wait_ee(Type, Msg, State) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, tls_connection).
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
wait_cert_cr(internal, #change_cipher_spec{}, State) ->
@@ -350,21 +401,21 @@ wait_cert_cr(internal, #change_cipher_spec{}, 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} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State);
{State1, NextState} ->
tls_connection:next_event(NextState, no_record, State1)
end;
wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0) ->
case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0);
{State1, NextState} ->
tls_connection:next_event(NextState, no_record, State1)
end;
wait_cert_cr(info, Msg, State) ->
tls_connection:handle_info(Msg, ?FUNCTION_NAME, State);
wait_cert_cr(Type, Msg, State) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, tls_connection).
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
handle_new_session_ticket(NewSessionTicket, State),
@@ -375,11 +426,19 @@ connection(internal, #key_update{} = KeyUpdate, State0) ->
{ok, State} ->
tls_connection:next_event(?FUNCTION_NAME, no_record, State);
{error, State, Alert} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, connection, State),
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, connection, State),
tls_connection:next_event(?FUNCTION_NAME, no_record, State)
end;
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
connection(Type, Event, State) ->
- tls_connection:?FUNCTION_NAME(Type, Event, State).
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
downgrade(Type, Event, State) ->
tls_connection:?FUNCTION_NAME(Type, Event, State).
@@ -402,7 +461,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
InitStatEnv = #static_env{
role = Role,
transport_cb = CbModule,
- protocol_cb = ?MODULE,
+ protocol_cb = tls_connection,
data_tag = DataTag,
close_tag = CloseTag,
error_tag = ErrorTag,
diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl
index 63b82dd3be..9813250a46 100644
--- a/lib/ssl/src/tls_connection_sup.erl
+++ b/lib/ssl/src/tls_connection_sup.erl
@@ -57,10 +57,10 @@ init(_O) ->
MaxT = 3600,
Name = undefined, % As simple_one_for_one is used.
- StartFunc = {tls_gen_connection, start_link, []},
+ StartFunc = {ssl_gen_statem, start_link, []},
Restart = temporary, % E.g. should not be restarted
Shutdown = 4000,
- Modules = [tls_gen_connection, tls_connection, tls_connection_1_3],
+ Modules = [ssl_gen_statem, tls_connection, tls_connection_1_3],
Type = worker,
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
deleted file mode 100644
index ae7c1a15b0..0000000000
--- a/lib/ssl/src/tls_gen_connection.erl
+++ /dev/null
@@ -1,63 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2020. 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
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% 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%
-%%
-%%
-%%----------------------------------------------------------------------
-%% Purpose: Provid help function to handle generic parts of TLS
-%% connection fsms
-%%----------------------------------------------------------------------
-
--module(tls_gen_connection).
-
--include("ssl_internal.hrl").
-
-%% Initial setup
--export([start_link/8, init/1]).
-
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
--spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), 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]])}.
-
-%%--------------------------------------------------------------------
--spec init(list()) -> no_return().
-%% Description: Initialization
-%%--------------------------------------------------------------------
-init([_Role, Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
- process_flag(trap_exit, true),
- link(Sender),
- case ErlDist of
- true ->
- process_flag(priority, max);
- _ ->
- ok
- end,
- ConnectionFsm = connection_fsm(TLSOpts),
- ConnectionFsm:init(InitArgs).
-
-connection_fsm(#{versions := ['tlsv1.3']}) ->
- tls_connection_1_3;
-connection_fsm(_) ->
- tls_connection.
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index d608f17884..442b2bf600 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -598,31 +598,30 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
honor_cipher_order := HonorCipherOrder}} = State0) ->
SNI = maps:get(sni, Extensions, undefined),
ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
- ClientGroups = get_supported_groups(ClientGroups0),
- ServerGroups = get_supported_groups(ServerGroups0),
-
- ClientShares0 = maps:get(key_share, Extensions, undefined),
- ClientShares = get_key_shares(ClientShares0),
-
- OfferedPSKs = get_offered_psks(Extensions),
-
- ClientALPN0 = maps:get(alpn, Extensions, undefined),
- ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
-
- ClientSignAlgs = get_signature_scheme_list(
- maps:get(signature_algs, Extensions, undefined)),
- ClientSignAlgsCert = get_signature_scheme_list(
- maps:get(signature_algs_cert, Extensions, undefined)),
-
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
{Ref,Maybe} = maybe(),
-
try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ ServerGroups = Maybe(get_supported_groups(ServerGroups0)),
+
+ ClientShares0 = maps:get(key_share, Extensions, undefined),
+ ClientShares = get_key_shares(ClientShares0),
+
+ OfferedPSKs = get_offered_psks(Extensions),
+
+ ClientALPN0 = maps:get(alpn, Extensions, undefined),
+ ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
+
+ ClientSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ClientSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+
+ CookieExt = maps:get(cookie, Extensions, undefined),
+ Cookie = get_cookie(CookieExt),
+
#state{connection_states = ConnectionStates0,
session = #session{own_certificates = [Cert | _]}} = State1 =
- Maybe(ssl_connection:handle_sni_extension_tls13(SNI, State0)),
+ Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
Maybe(validate_cookie(Cookie, State1)),
@@ -716,12 +715,12 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
session = #session{own_certificates = OwnCerts} = Session0,
connection_states = ConnectionStates0
} = State0) ->
- ClientGroups = get_supported_groups(ClientGroups0),
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
{Ref,Maybe} = maybe(),
try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ CookieExt = maps:get(cookie, Extensions, undefined),
+ Cookie = get_cookie(CookieExt),
+
ServerKeyShare = maps:get(key_share, Extensions, undefined),
SelectedGroup = get_selected_group(ServerKeyShare),
@@ -931,14 +930,15 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
supported_groups := ClientGroups0,
session_tickets := SessionTickets,
use_ticket := UseTicket}} = State0) ->
- ClientGroups = get_supported_groups(ClientGroups0),
- ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
- ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
- SelectedIdentity = get_selected_identity(ServerPreSharedKey),
- ClientKeyShare = get_key_shares(ClientKeyShare0),
-
+
{Ref,Maybe} = maybe(),
try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
+ ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
+ SelectedIdentity = get_selected_identity(ServerPreSharedKey),
+ ClientKeyShare = get_key_shares(ClientKeyShare0),
+
%% Go to state 'start' if server replies with 'HelloRetryRequest'.
Maybe(maybe_hello_retry_request(ServerHello, State0)),
@@ -2269,8 +2269,10 @@ get_signature_scheme_list(#signature_algorithms{
lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end,
ClientSignatureSchemes).
+get_supported_groups(undefined = Groups) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})};
get_supported_groups(#supported_groups{supported_groups = Groups}) ->
- Groups.
+ {ok, Groups}.
get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
ClientShares;
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index f1ed8bb101..3fc9019c05 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -278,7 +278,7 @@ 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),
ok = erlang:dist_ctrl_input_handler(DHandle, Pid),
- ok = ssl_connection:dist_handshake_complete(Pid, 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}},
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index e2ec4e2f0a..66740832b0 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -108,7 +108,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo,
{SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo],
case tls_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
- ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers);
+ ssl_gen_statem:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers);
{error, Reason} ->
{error, Reason}
end;
@@ -122,7 +122,7 @@ upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo,
ok = setopts(Transport, Socket, tls_socket:internal_inet_values()),
case peername(Transport, Socket) of
{ok, {Address, Port}} ->
- ssl_connection:connect(ConnectionCb, Address, Port, Socket,
+ ssl_gen_statem:connect(ConnectionCb, Address, Port, Socket,
{SslOptions,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
@@ -137,7 +137,7 @@ connect(Address, Port,
{Transport, _, _, _, _} = CbInfo,
try Transport:connect(Address, Port, SocketOpts, Timeout) of
{ok, Socket} ->
- ssl_connection:connect(ConnetionCb, Address, Port, Socket,
+ ssl_gen_statem:connect(ConnetionCb, Address, Port, Socket,
{SslOpts,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
diff --git a/lib/ssl/test/ssl_alpn_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl
index 9cc3303604..e3c10caa43 100644
--- a/lib/ssl/test/ssl_alpn_SUITE.erl
+++ b/lib/ssl/test/ssl_alpn_SUITE.erl
@@ -326,7 +326,7 @@ ssl_receive(Socket, Data) ->
ssl_receive(Socket, Data, Buffer) ->
ct:log("Connection info: ~p~n",
- [ssl:connection_information(Socket)]),
+ [ssl:connection_information(Socket)]),
receive
{ssl, Socket, MoreData} ->
ct:log("Received ~p~n",[MoreData]),
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index f2cbba5d44..d58ba777cc 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -267,16 +267,16 @@ get_client_opts(Config) ->
ssl_options(COpts, Config).
%% Default callback functions
-init_per_group(GroupName, Config) ->
+init_per_group(GroupName, Config0) ->
case is_protocol_version(GroupName) andalso sufficient_crypto_support(GroupName) of
true ->
- clean_protocol_version(Config),
+ Config = clean_protocol_version(Config0),
init_protocol_version(GroupName, Config);
_ ->
case sufficient_crypto_support(GroupName) of
true ->
ssl:start(),
- Config;
+ Config0;
false ->
{skip, "Missing crypto support"}
end
diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl
index 51d9ffaa36..26f086f11c 100644
--- a/lib/ssl/test/tls_api_SUITE.erl
+++ b/lib/ssl/test/tls_api_SUITE.erl
@@ -505,32 +505,35 @@ tls_dont_crash_on_handshake_garbage() ->
tls_dont_crash_on_handshake_garbage(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
-
+ Version = ssl_test_lib:protocol_version(Config),
{_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ServerOpts}]),
- unlink(Server), monitor(process, Server),
+ {from, self()},
+ {mfa, ssl_test_lib, no_result},
+ {options, [{versions, [Version]} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
-
+
{ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]),
- % Send hello and garbage record
+ %% Send hello and garbage record
ok = gen_tcp:send(Socket,
[<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello
16#deadbeef:256, % 32 'random' bytes = 256 bits
0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values
-
<<22, 3,3, 5:16, 92,64,37,228,209>> % garbage
]),
- % Send unexpected change_cipher_spec
+ %% Send unexpected change_cipher_spec
ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>),
-
+ gen_tcp:close(Socket),
% Ensure we receive an alert, not sudden disconnect
- {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000).
-
+ case Version of
+ 'tlsv1.3' ->
+ ssl_test_lib:check_server_alert(Server, illegal_parameter);
+ _ ->
+ ssl_test_lib:check_server_alert(Server, handshake_failure)
+ end.
+
%%--------------------------------------------------------------------
tls_tcp_error_propagation_in_active_mode() ->
[{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}].
@@ -871,14 +874,6 @@ tls_closed_in_active_once_loop(Socket) ->
{error, ssl_setopt_failed}
end.
-drop_handshakes(Socket, Timeout) ->
- {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout),
- {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout),
- case RecType of
- 22 -> drop_handshakes(Socket, Timeout);
- _ -> {ok, <<Header/binary, Frag/binary>>}
- end.
-
receive_msg(_) ->
receive
Msg ->
@@ -908,3 +903,4 @@ active_tcp_recv(Socket, N, Acc) ->
{tcp, Socket, Bytes} ->
active_tcp_recv(Socket, N-size(Bytes), Acc ++ Bytes)
end.
+