aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/dtls_connection.erl310
-rw-r--r--lib/ssl/src/dtls_handshake.erl23
-rw-r--r--lib/ssl/src/dtls_handshake.hrl1
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl4
-rw-r--r--lib/ssl/src/dtls_record.erl35
-rw-r--r--lib/ssl/src/inet_tls_dist.erl25
-rw-r--r--lib/ssl/src/ssl.erl441
-rw-r--r--lib/ssl/src/ssl_alert.erl77
-rw-r--r--lib/ssl/src/ssl_api.hrl49
-rw-r--r--lib/ssl/src/ssl_cipher.erl14
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl35
-rw-r--r--lib/ssl/src/ssl_connection.erl879
-rw-r--r--lib/ssl/src/ssl_connection.hrl142
-rw-r--r--lib/ssl/src/ssl_crl_cache.erl4
-rw-r--r--lib/ssl/src/ssl_crl_cache_api.erl15
-rw-r--r--lib/ssl/src/ssl_handshake.erl104
-rw-r--r--lib/ssl/src/ssl_internal.hrl8
-rw-r--r--lib/ssl/src/ssl_manager.erl48
-rw-r--r--lib/ssl/src/ssl_session.erl17
-rw-r--r--lib/ssl/src/ssl_session_cache_api.erl24
-rw-r--r--lib/ssl/src/tls_connection.erl384
-rw-r--r--lib/ssl/src/tls_handshake.erl19
-rw-r--r--lib/ssl/src/tls_record.erl86
-rw-r--r--lib/ssl/src/tls_sender.erl29
24 files changed, 1702 insertions, 1071 deletions
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 37719ad439..70dae4c677 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -39,7 +39,7 @@
-export([start_fsm/8, start_link/7, init/1, pids/1]).
%% State transition handling
--export([next_event/3, next_event/4, handle_common_event/4]).
+-export([next_event/3, next_event/4, handle_protocol_record/3]).
%% Handshake handling
-export([renegotiate/2, send_handshake/2,
@@ -80,7 +80,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker}
end.
%%--------------------------------------------------------------------
--spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+-spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
{ok, pid()} | ignore | {error, reason()}.
%%
%% Description: Creates a gen_statem process which calls Module:init/1 to
@@ -107,9 +107,11 @@ pids(_) ->
%%====================================================================
%% State transition handling
%%====================================================================
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
next_record(#state{protocol_buffers =
#protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]}
= Buffers,
@@ -141,14 +143,14 @@ next_record(#state{protocol_buffers =
next_record(State#state{protocol_buffers =
Buffers#protocol_buffers{dtls_cipher_texts = Rest},
connection_states = ConnectionStates});
-next_record(#state{role = server,
- socket = {Listener, {Client, _}}} = State) ->
+next_record(#state{static_env = #static_env{role = server,
+ socket = {Listener, {Client, _}}}} = State) ->
dtls_packet_demux:active_once(Listener, Client, self()),
{no_record, State};
-next_record(#state{role = client,
- socket = {_Server, Socket} = DTLSSocket,
- close_tag = CloseTag,
- transport_cb = Transport} = State) ->
+next_record(#state{static_env = #static_env{role = client,
+ socket = {_Server, Socket} = DTLSSocket,
+ close_tag = CloseTag,
+ transport_cb = Transport}} = State) ->
case dtls_socket:setopts(Transport, Socket, [{active,once}]) of
ok ->
{no_record, State};
@@ -234,36 +236,43 @@ next_event(StateName, Record,
{next_state, StateName, State0, [{next_event, internal, Alert} | Actions]}
end.
-handle_common_event(internal, #alert{} = Alert, StateName,
- #state{negotiated_version = Version} = State) ->
- handle_own_alert(Alert, Version, StateName, State);
+%%% 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
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State1} ->
+ {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1),
+ ssl_connection:hibernate_after(StateName, State, Actions)
+ end;
%%% DTLS record protocol level handshake messages
-handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE,
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
fragment = Data},
StateName,
#state{protocol_buffers = Buffers0,
- negotiated_version = Version} = State0) ->
+ negotiated_version = Version} = State) ->
try
case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of
{[], Buffers} ->
- next_event(StateName, no_record, State0#state{protocol_buffers = Buffers});
+ next_event(StateName, no_record, State#state{protocol_buffers = Buffers});
{Packets, Buffers} ->
- State = State0#state{protocol_buffers = Buffers},
+ HsEnv = State#state.handshake_env,
Events = dtls_handshake_events(Packets),
{next_state, StateName,
- State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
end
catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State0)
+ handle_own_alert(Alert, Version, StateName, State)
end;
-%%% DTLS record protocol level application data messages
-handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]};
%%% DTLS record protocol level change cipher messages
-handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
{next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
%%% DTLS record protocol level Alert messages
-handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
#state{negotiated_version = Version} = State) ->
case decode_alerts(EncAlerts) of
Alerts = [_|_] ->
@@ -272,18 +281,20 @@ handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, Sta
handle_own_alert(Alert, Version, StateName, State)
end;
%% Ignore unknown TLS record level protocol messages
-handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State}.
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
%%====================================================================
%% Handshake handling
%%====================================================================
-renegotiate(#state{role = client} = State, Actions) ->
+renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
- next_event(connection, no_record, State, [{next_event, internal, #hello_request{}} | Actions]);
-renegotiate(#state{role = server} = State0, Actions) ->
+ {next_state, connection, State,
+ [{next_event, internal, #hello_request{}} | Actions]};
+
+renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
State1 = prepare_flight(State0),
{State, MoreActions} = send_handshake(HelloRequest, State1),
@@ -293,7 +304,7 @@ send_handshake(Handshake, #state{connection_states = ConnectionStates} = State)
#{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
send_handshake_flight(queue_handshake(Handshake, State), Epoch).
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
negotiated_version = Version,
flight_buffer = #{handshakes := HsBuffer0,
change_cipher_spec := undefined,
@@ -302,9 +313,9 @@ queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist};
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
negotiated_version = Version,
flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0,
next_sequence := Seq} = Flight0} = State) ->
@@ -312,7 +323,7 @@ queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist}.
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}.
queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
connection_states = ConnectionStates0} = State) ->
@@ -324,10 +335,11 @@ queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
reinit(State) ->
%% To be API compatible with TLS NOOP here
reinit_handshake_data(State).
-reinit_handshake_data(#state{protocol_buffers = Buffers} = State) ->
+reinit_handshake_data(#state{protocol_buffers = Buffers,
+ handshake_env = HsEnv} = State) ->
State#state{premaster_secret = undefined,
public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history(),
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history()},
flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
flight_buffer = new_flight(),
protocol_buffers =
@@ -353,8 +365,8 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
send_alert(Alert, #state{negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
+ static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
connection_states = ConnectionStates0} = State0) ->
{BinMsg, ConnectionStates} =
encode_alert(Alert, Version, ConnectionStates0),
@@ -406,13 +418,15 @@ getopts(Transport, Socket, Tag) ->
init(enter, _, State) ->
{keep_state, State};
init({call, From}, {start, Timeout},
- #state{host = Host, port = Port, role = client,
+ #state{static_env = #static_env{host = Host,
+ port = Port,
+ role = client,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
ssl_options = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
@@ -422,15 +436,17 @@ init({call, From}, {start, Timeout},
HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),
State1 = prepare_flight(State0#state{negotiated_version = Version}),
{State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
- State = State2#state{negotiated_version = Version, %% Requested version
- session =
- Session0#session{session_id = Hello#client_hello.session_id},
- start_or_recv_from = From,
- timer = Timer,
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
- },
- next_event(hello, no_record, State, Actions);
-init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->
+ State3 = State2#state{negotiated_version = Version, %% Requested version
+ session =
+ Session0#session{session_id = Hello#client_hello.session_id},
+ start_or_recv_from = From,
+ timer = Timer,
+ flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
+ },
+ {Record, State} = next_record(State3),
+ next_event(hello, Record, State, Actions);
+init({call, _} = Type, Event, #state{static_env = #static_env{role = server,
+ data_tag = udp}} = State) ->
Result = gen_handshake(?FUNCTION_NAME, Type, Event,
State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(),
@@ -439,7 +455,7 @@ init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->
max_ignored_alerts => 10}}),
erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
Result;
-init({call, _} = Type, Event, #state{role = server} = State) ->
+init({call, _} = Type, Event, #state{static_env = #static_env{role = server}} = State) ->
%% I.E. DTLS over sctp
gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable});
init(Type, Event, State) ->
@@ -454,8 +470,8 @@ error(enter, _, State) ->
{keep_state, State};
error({call, From}, {start, _Timeout},
#state{protocol_specific = #{error := Error}} = State) ->
- ssl_connection:stop_and_reply(
- normal, {reply, From, {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(_, _, _) ->
@@ -467,16 +483,17 @@ error(_, _, _) ->
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-hello(enter, _, #state{role = server} = State) ->
+hello(enter, _, #state{static_env = #static_env{role = server}} = State) ->
{keep_state, State};
-hello(enter, _, #state{role = client} = State0) ->
+hello(enter, _, #state{static_env = #static_env{role = client}} = State0) ->
{State, Actions} = handle_flight_timer(State0),
{keep_state, State, Actions};
hello(internal, #client_hello{cookie = <<>>,
client_version = Version} = Hello,
- #state{role = server,
- transport_cb = Transport,
- socket = Socket,
+ #state{static_env = #static_env{role = server,
+ transport_cb = Transport,
+ socket = Socket},
+ handshake_env = HsEnv,
protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
@@ -488,25 +505,32 @@ hello(internal, #client_hello{cookie = <<>>,
%% negotiated.
VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
State1 = prepare_flight(State0#state{negotiated_version = Version}),
- {State, Actions} = send_handshake(VerifyRequest, State1),
- next_event(?FUNCTION_NAME, no_record,
- State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);
-hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,
- host = Host, port = Port,
+ {State2, Actions} = send_handshake(VerifyRequest, State1),
+ {Record, State} = next_record(State2),
+ next_event(?FUNCTION_NAME, Record,
+ State#state{handshake_env = HsEnv#handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history()}},
+ Actions);
+hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
ssl_options = SslOpts,
session = #session{own_certificate = OwnCert}
= Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
SslOpts,
Cache, CacheCb, Renegotiation, OwnCert),
Version = Hello#client_hello.client_version,
- State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}),
+ State1 = prepare_flight(State0#state{handshake_env =
+ HsEnv#handshake_env{tls_handshake_history
+ = ssl_handshake:init_handshake_history()}}),
{State2, Actions} = send_handshake(Hello, State1),
State = State2#state{negotiated_version = Version, %% Requested version
@@ -526,12 +550,13 @@ hello(internal, #server_hello{extensions = Extensions} = Hello,
{next_state, user_hello, State#state{start_or_recv_from = undefined,
hello = Hello},
[{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
-hello(internal, #client_hello{cookie = Cookie} = Hello,
- #state{role = server,
- transport_cb = Transport,
- socket = Socket,
- protocol_specific = #{current_cookie_secret := Secret,
- previous_cookie_secret := PSecret}} = State0) ->
+
+hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server,
+ transport_cb = Transport,
+ socket = Socket},
+ protocol_specific = #{current_cookie_secret := Secret,
+ previous_cookie_secret := PSecret}
+ } = State0) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
case dtls_handshake:cookie(Secret, IP, Port, Hello) of
Cookie ->
@@ -546,11 +571,12 @@ hello(internal, #client_hello{cookie = Cookie} = Hello,
end
end;
hello(internal, #server_hello{} = Hello,
- #state{connection_states = ConnectionStates0,
- negotiated_version = ReqVersion,
- role = client,
- renegotiation = {Renegotiation, _},
- ssl_options = SslOptions} = State) ->
+ #state{
+ static_env = #static_env{role = client},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ connection_states = ConnectionStates0,
+ negotiated_version = ReqVersion,
+ ssl_options = SslOptions} = State) ->
case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
@@ -658,33 +684,43 @@ connection(enter, _, State) ->
{keep_state, State};
connection(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
-connection(internal, #hello_request{}, #state{host = Host, port = Port,
+connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb
+ },
+ handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}},
session = #session{own_certificate = Cert} = Session0,
- session_cache = Cache, session_cache_cb = CacheCb,
+
ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}} = State0) ->
+ connection_states = ConnectionStates0
+ } = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),
State1 = prepare_flight(State0),
- {State, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
- next_event(hello, no_record, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
- session = Session0#session{session_id
- = Hello#client_hello.session_id}},
- Actions);
-connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) ->
+ {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
+ {Record, State} =
+ next_record(
+ State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
+ session = Session0#session{session_id
+ = Hello#client_hello.session_id}}),
+ next_event(hello, Record, State, Actions);
+connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server},
+ allow_renegotiate = true} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {next_state, hello, State#state{allow_renegotiate = false, renegotiation = {true, peer}},
+ {next_state, hello, State#state{allow_renegotiate = false,
+ handshake_env = #handshake_env{renegotiation = {true, peer}}},
[{next_event, internal, Hello}]};
-connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false} = State0) ->
+connection(internal, #client_hello{}, #state{static_env = #static_env{role = server},
+ allow_renegotiate = false} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
State1 = send_alert(Alert, State0),
{Record, State} = ssl_connection:prepare_connection(State1, ?MODULE),
@@ -739,29 +775,35 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
end,
Monitor = erlang:monitor(process, User),
-
- #state{socket_options = SocketOptions,
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = ?MODULE,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ session_cache_cb = SessionCacheCb
+ },
+
+ #state{static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first}
+ },
+ socket_options = SocketOptions,
%% We do not want to save the password in the state so that
%% could be written in the clear into error logs.
ssl_options = SSLOptions#ssl_options{password = undefined},
session = #session{is_resumable = new},
- transport_cb = CbModule,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
user_application = {Monitor, User},
user_data_buffer = <<>>,
- session_cache_cb = SessionCacheCb,
- renegotiation = {false, first},
allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
start_or_recv_from = undefined,
- protocol_cb = ?MODULE,
flight_buffer = new_flight(),
flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
}.
@@ -802,17 +844,18 @@ decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts
{Alert, State}
end.
-dtls_version(hello, Version, #state{role = server} = State) ->
+dtls_version(hello, Version, #state{static_env = #static_env{role = server}} = State) ->
State#state{negotiated_version = Version}; %%Inital version
dtls_version(_,_, State) ->
State.
handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
+ static_env = #static_env{port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ session = #session{own_certificate = Cert} = Session0,
negotiated_protocol = CurrentProtocol,
key_algorithm = KeyExAlg,
ssl_options = SslOpts} = State0) ->
@@ -831,7 +874,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
State = prepare_flight(State0#state{connection_states = ConnectionStates,
negotiated_version = Version,
hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
+ handshake_env = HsEnv#handshake_env{client_hello_version = ClientVersion},
session = Session,
negotiated_protocol = Protocol}),
@@ -842,19 +885,19 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
%% raw data from socket, unpack records
handle_info({Protocol, _, _, _, Data}, StateName,
- #state{data_tag = Protocol} = State0) ->
+ #state{static_env = #static_env{data_tag = Protocol}} = State0) ->
case next_dtls_record(Data, StateName, State0) of
{Record, State} ->
next_event(StateName, Record, State);
#alert{} = Alert ->
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- ssl_connection:stop({shutdown, own_alert}, State0)
+ {stop, {shutdown, own_alert}, State0}
end;
handle_info({CloseTag, Socket}, StateName,
- #state{socket = Socket,
+ #state{static_env = #static_env{socket = Socket,
+ close_tag = CloseTag},
socket_options = #socket_options{active = Active},
protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
- close_tag = CloseTag,
negotiated_version = Version} = State) ->
%% Note that as of DTLS 1.2 (TLS 1.1),
%% failure to properly close a connection no longer requires that a
@@ -873,7 +916,7 @@ handle_info({CloseTag, Socket}, StateName,
ok
end,
ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- ssl_connection:stop({shutdown, transport_closed}, State);
+ {stop, {shutdown, transport_closed}, State};
true ->
%% Fixes non-delivery of final DTLS record in {active, once}.
%% Basically allows the application the opportunity to set {active, once} again
@@ -907,8 +950,8 @@ handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
-handle_own_alert(Alert, Version, StateName, #state{data_tag = udp,
- role = Role,
+handle_own_alert(Alert, Version, StateName, #state{static_env = #static_env{data_tag = udp,
+ role = Role},
ssl_options = Options} = State0) ->
case ignore_alert(Alert, State0) of
{true, State} ->
@@ -998,10 +1041,10 @@ next_flight(Flight) ->
change_cipher_spec => undefined,
handshakes_after_change_cipher_spec => []}.
-handle_flight_timer(#state{data_tag = udp,
+handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
flight_state = {retransmit, Timeout}} = State) ->
start_retransmision_timer(Timeout, State);
-handle_flight_timer(#state{data_tag = udp,
+handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
flight_state = connection} = State) ->
{State, []};
handle_flight_timer(State) ->
@@ -1017,8 +1060,8 @@ new_timeout(N) when N =< 30 ->
new_timeout(_) ->
60.
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := Flight,
change_cipher_spec := undefined},
negotiated_version = Version,
@@ -1029,8 +1072,8 @@ send_handshake_flight(#state{socket = Socket,
send(Transport, Socket, Encoded),
{State0#state{connection_states = ConnectionStates}, []};
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := [_|_] = Flight0,
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := []},
@@ -1043,8 +1086,8 @@ send_handshake_flight(#state{socket = Socket,
send(Transport, Socket, [HsBefore, EncChangeCipher]),
{State0#state{connection_states = ConnectionStates}, []};
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := [_|_] = Flight0,
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := Flight1},
@@ -1059,8 +1102,8 @@ send_handshake_flight(#state{socket = Socket,
send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]),
{State0#state{connection_states = ConnectionStates}, []};
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := [],
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := Flight1},
@@ -1117,16 +1160,17 @@ log_ignore_alert(false, _, _,_) ->
ok.
send_application_data(Data, From, _StateName,
- #state{socket = Socket,
+ #state{static_env = #static_env{socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport},
+ handshake_env = HsEnv,
negotiated_version = Version,
- protocol_cb = Connection,
- transport_cb = Transport,
connection_states = ConnectionStates0,
ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) ->
case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
- renegotiate(State0#state{renegotiation = {true, internal}},
+ renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}},
[{next_event, {call, From}, {application_data, Data}}]);
false ->
{Msgs, ConnectionStates} =
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 3f70eaec8a..6e9bf99e52 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -46,7 +46,7 @@
%% Handshake handling
%%====================================================================
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), ssl_record:connection_states(),
+-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
#ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
#client_hello{}.
%%
@@ -59,7 +59,7 @@ client_hello(Host, Port, ConnectionStates, SslOpts,
Cache, CacheCb, Renegotiation, OwnCert).
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), term(), ssl_record:connection_states(),
+-spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(),
#ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
#client_hello{}.
%%
@@ -123,7 +123,7 @@ cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor},
Random, SessionId, CipherSuites, CompressionMethods],
crypto:hmac(sha, Key, CookieData).
%%--------------------------------------------------------------------
--spec hello_verify_request(binary(), dtls_record:dtls_version()) -> #hello_verify_request{}.
+-spec hello_verify_request(binary(), ssl_record:ssl_version()) -> #hello_verify_request{}.
%%
%% Description: Creates a hello verify request message sent by server to
%% verify client
@@ -151,7 +151,7 @@ encode_handshake(Handshake, Version, Seq) ->
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec get_dtls_handshake(dtls_record:dtls_version(), binary(), #protocol_buffers{}) ->
+-spec get_dtls_handshake(ssl_record:ssl_version(), binary(), #protocol_buffers{}) ->
{[dtls_handshake()], #protocol_buffers{}}.
%%
%% Description: Given buffered and new data from dtls_record, collects
@@ -215,8 +215,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, dtls_v1:corresponding_tls_version(Version),
SslOpts, Session0,
ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
catch throw:Alert ->
@@ -225,17 +223,16 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
- Compression, HelloExt,
- dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
+ Compression, HelloExt,
+ dtls_v1:corresponding_tls_version(Version),
+ SslOpt, ConnectionStates0, Renegotiation) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ catch throw:Alert ->
+ Alert
end.
-
%%--------------------------------------------------------------------
enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl
index 50e92027d2..41da8e5c8c 100644
--- a/lib/ssl/src/dtls_handshake.hrl
+++ b/lib/ssl/src/dtls_handshake.hrl
@@ -27,6 +27,7 @@
-define(dtls_handshake, true).
-include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes
+-include("ssl_api.hrl").
-define(HELLO_VERIFY_REQUEST, 3).
-define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}).
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index 1497c77cf3..dccc22a448 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -144,11 +144,11 @@ handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket
%% UDP socket does not have a connection and should not receive an econnreset
%% This does however happens on some windows versions. Just ignoring it
%% appears to make things work as expected!
-handle_info({Error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) ->
+handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) ->
Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]),
error_logger:info_report(Report),
{noreply, State};
-handle_info({Error, Socket, Error}, #state{listener = Socket, transport = {_,_,_, Error}} = State) ->
+handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag}} = State) ->
Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]),
error_logger:info_report(Report),
{noreply, State#state{close=true}};
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index b7346d3ec8..dd33edfd77 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -49,9 +49,8 @@
is_acceptable_version/2, hello_version/2]).
--export_type([dtls_version/0, dtls_atom_version/0]).
+-export_type([dtls_atom_version/0]).
--type dtls_version() :: ssl_record:ssl_version().
-type dtls_atom_version() :: dtlsv1 | 'dtlsv1.2'.
-define(REPLAY_WINDOW_SIZE, 64).
@@ -135,7 +134,7 @@ set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch
States#{saved_read := ReadState}.
%%--------------------------------------------------------------------
--spec init_connection_state_seq(dtls_version(), ssl_record:connection_states()) ->
+-spec init_connection_state_seq(ssl_record:ssl_version(), ssl_record:connection_states()) ->
ssl_record:connection_state().
%%
%% Description: Copy the read sequence number to the write sequence number
@@ -163,7 +162,7 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}},
Epoch.
%%--------------------------------------------------------------------
--spec get_dtls_records(binary(), [dtls_version()], binary()) -> {[binary()], binary()} | #alert{}.
+-spec get_dtls_records(binary(), [ssl_record:ssl_version()], binary()) -> {[binary()], binary()} | #alert{}.
%%
%% Description: Given old buffer and new data from UDP/SCTP, packs up a records
%% and returns it as a list of tls_compressed binaries also returns leftover
@@ -188,7 +187,7 @@ get_dtls_records(Data, Versions, Buffer) ->
%%====================================================================
%%--------------------------------------------------------------------
--spec encode_handshake(iolist(), dtls_version(), integer(), ssl_record:connection_states()) ->
+-spec encode_handshake(iolist(), ssl_record:ssl_version(), integer(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%
%% Description: Encodes a handshake message to send on the ssl-socket.
@@ -198,7 +197,7 @@ encode_handshake(Frag, Version, Epoch, ConnectionStates) ->
%%--------------------------------------------------------------------
--spec encode_alert_record(#alert{}, dtls_version(), ssl_record:connection_states()) ->
+-spec encode_alert_record(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%%
%% Description: Encodes an alert message to send on the ssl-socket.
@@ -210,7 +209,7 @@ encode_alert_record(#alert{level = Level, description = Description},
ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_change_cipher_spec(dtls_version(), integer(), ssl_record:connection_states()) ->
+-spec encode_change_cipher_spec(ssl_record:ssl_version(), integer(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%%
%% Description: Encodes a change_cipher_spec-message to send on the ssl socket.
@@ -219,7 +218,7 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) ->
encode_plain_text(?CHANGE_CIPHER_SPEC, Version, Epoch, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_data(binary(), dtls_version(), ssl_record:connection_states()) ->
+-spec encode_data(binary(), ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(),ssl_record:connection_states()}.
%%
%% Description: Encodes data to send on the ssl-socket.
@@ -248,8 +247,8 @@ decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) ->
%%====================================================================
%%--------------------------------------------------------------------
--spec protocol_version(dtls_atom_version() | dtls_version()) ->
- dtls_version() | dtls_atom_version().
+-spec protocol_version(dtls_atom_version() | ssl_record:ssl_version()) ->
+ ssl_record:ssl_version() | dtls_atom_version().
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
@@ -263,7 +262,7 @@ protocol_version({254, 253}) ->
protocol_version({254, 255}) ->
dtlsv1.
%%--------------------------------------------------------------------
--spec lowest_protocol_version(dtls_version(), dtls_version()) -> dtls_version().
+-spec lowest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
@@ -277,7 +276,7 @@ lowest_protocol_version(_,Version) ->
Version.
%%--------------------------------------------------------------------
--spec lowest_protocol_version([dtls_version()]) -> dtls_version().
+-spec lowest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Lowest protocol version present in a list
%%--------------------------------------------------------------------
@@ -288,7 +287,7 @@ lowest_protocol_version(Versions) ->
lowest_list_protocol_version(Ver, Vers).
%%--------------------------------------------------------------------
--spec highest_protocol_version([dtls_version()]) -> dtls_version().
+-spec highest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
@@ -299,7 +298,7 @@ highest_protocol_version(Versions) ->
highest_list_protocol_version(Ver, Vers).
%%--------------------------------------------------------------------
--spec highest_protocol_version(dtls_version(), dtls_version()) -> dtls_version().
+-spec highest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version of two given versions
%%--------------------------------------------------------------------
@@ -315,7 +314,7 @@ highest_protocol_version(_,Version) ->
Version.
%%--------------------------------------------------------------------
--spec is_higher(V1 :: dtls_version(), V2::dtls_version()) -> boolean().
+-spec is_higher(V1 :: ssl_record:ssl_version(), V2::ssl_record:ssl_version()) -> boolean().
%%
%% Description: Is V1 > V2
%%--------------------------------------------------------------------
@@ -327,7 +326,7 @@ is_higher(_, _) ->
false.
%%--------------------------------------------------------------------
--spec supported_protocol_versions() -> [dtls_version()].
+-spec supported_protocol_versions() -> [ssl_record:ssl_version()].
%%
%% Description: Protocol versions supported
%%--------------------------------------------------------------------
@@ -370,7 +369,7 @@ supported_protocol_versions([_|_] = Vsns) ->
end.
%%--------------------------------------------------------------------
--spec is_acceptable_version(dtls_version(), Supported :: [dtls_version()]) -> boolean().
+-spec is_acceptable_version(ssl_record:ssl_version(), Supported :: [ssl_record:ssl_version()]) -> boolean().
%%
%% Description: ssl version 2 is not acceptable security risks are too big.
%%
@@ -378,7 +377,7 @@ supported_protocol_versions([_|_] = Vsns) ->
is_acceptable_version(Version, Versions) ->
lists:member(Version, Versions).
--spec hello_version(dtls_version(), [dtls_version()]) -> dtls_version().
+-spec hello_version(ssl_record:ssl_version(), [ssl_record:ssl_version()]) -> ssl_record:ssl_version().
hello_version(Version, Versions) ->
case dtls_v1:corresponding_tls_version(Version) of
TLSVersion when TLSVersion >= {3, 3} ->
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index a4f8bb7562..5cab35fd4b 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -480,22 +480,25 @@ allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host) ->
allowed_nodes(PeerCert, Allowed, PeerIP)
end.
-
-
setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime).
gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
Kernel = self(),
monitor_pid(
- spawn_opt(
- fun() ->
- do_setup(
- Driver, Kernel, Node, Type,
- MyNode, LongOrShortNames, SetupTime)
- end,
- [link, {priority, max}])).
+ spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime),
+ [link, {priority, max}])).
+
+-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
+setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+ fun() ->
+ do_setup(
+ Driver, Kernel, Node, Type,
+ MyNode, LongOrShortNames, SetupTime)
+ end.
+
+-spec do_setup(_,_,_,_,_,_,_) -> no_return().
do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
{Name, Address} = split_node(Driver, Node, LongOrShortNames),
ErlEpmd = net_kernel:epmd_module(),
@@ -520,6 +523,8 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
trace({getaddr_failed, Driver, Address, Other}))
end.
+-spec do_setup_connect(_,_,_,_,_,_,_,_,_,_) -> no_return().
+
do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer) ->
Opts = trace(connect_options(get_ssl_options(client))),
dist_util:reset_timer(Timer),
@@ -564,7 +569,7 @@ gen_close(Driver, Socket) ->
%% Determine if EPMD module supports address resolving. Default
%% is to use inet_tcp:getaddr/2.
%% ------------------------------------------------------------
-get_address_resolver(EpmdModule, Driver) ->
+get_address_resolver(EpmdModule, _Driver) ->
case erlang:function_exported(EpmdModule, address_please, 3) of
true -> {EpmdModule, address_please};
_ -> {erl_epmd, address_please}
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 03a1e40bfc..a7d6f28c7a 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -61,16 +61,321 @@
-deprecated({ssl_accept, 2, eventually}).
-deprecated({ssl_accept, 3, eventually}).
+-export_type([socket/0,
+ sslsocket/0,
+ socket_option/0,
+ tls_client_option/0,
+ tls_option/0,
+ tls_server_option/0,
+ active_msgs/0,
+ erl_cipher_suite/0,
+ protocol_version/0,
+ dtls_version/0,
+ tls_version/0,
+ prf_random/0,
+ hello_extensions/0,
+ error_alert/0,
+ session_id/0,
+ path/0,
+ hostname/0,
+ host/0,
+ prf/0,
+ srp_param_type/0,
+ cipher_filters/0,
+ ssl_imp/0,
+ private_key_type/0,
+ cipher/0,
+ hash/0,
+ key_algo/0,
+ sign_algo/0
+ ]).
+%% -------------------------------------------------------------------------------------------------------
+-type socket() :: gen_tcp:socket().
+-type socket_option() :: socket_connect_option() | socket_listen_option().
+-type socket_connect_option() :: gen_tcp:connect_option() | gen_udp:option().
+-type socket_listen_option() :: gen_tcp:listen_option() | gen_udp:option().
+-opaque sslsocket() :: #sslsocket{}.
+-type tls_option() :: tls_client_option() | tls_server_option().
+-type tls_client_option() :: client_option() | socket_connect_option() | transport_option().
+-type tls_server_option() :: server_option() | socket_listen_option() | transport_option().
+-type active_msgs() :: {ssl, sslsocket(), Data::binary() | list()} | {ssl_closed, sslsocket()} |
+ {ssl_error, sslsocket(), Reason::term()}.
+-type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
+ ClosedTag::atom(), ErrTag::atom()}}.
+-type path() :: file:filename().
+-type host() :: hostname() | ip_address().
+-type hostname() :: string().
+-type ip_address() :: inet:ip_address().
+-type session_id() :: binary().
+-type protocol_version() :: tls_version() | dtls_version().
+-type tls_version() :: tlsv1 | 'tlsv1.1' | 'tlsv1.2' | 'tlsv1.3' | legacy_version().
+-type dtls_version() :: 'dtlsv1' | 'dtlsv1.2'.
+-type legacy_version() :: sslv3.
+-type verify_type() :: verify_none | verify_peer.
+-type cipher() :: aes_128_cbc |
+ aes_256_cbc |
+ aes_128_gcm |
+ aes_256_gcm |
+ chacha20_poly1305 |
+ legacy_cipher().
+-type legacy_cipher() :: rc4_128 |
+ des_cbc |
+ '3des_ede_cbc'.
+
+-type hash() :: sha |
+ sha2() |
+ legacy_hash().
+
+-type sha2() :: sha224 |
+ sha256 |
+ sha384 |
+ sha512.
+
+-type legacy_hash() :: md5.
+
+-type sign_algo() :: rsa | dsa | ecdsa.
+-type key_algo() :: rsa |
+ dhe_rsa | dhe_dss |
+ ecdhe_ecdsa | ecdh_ecdsa | ecdh_rsa |
+ srp_rsa| srp_dss |
+ psk | dhe_psk | rsa_psk |
+ dh_anon | ecdh_anon | srp_anon |
+ any. %% TLS 1.3
+-type prf() :: hash() | default_prf.
+-type erl_cipher_suite() :: #{key_exchange := key_algo(),
+ cipher := cipher(),
+ mac := hash() | aead,
+ prf := hash() | default_prf %% Old cipher suites, version dependent
+ }.
+
+-type named_curve() :: sect571r1 |
+ sect571k1 |
+ secp521r1 |
+ brainpoolP512r1 |
+ sect409k1 |
+ sect409r1 |
+ brainpoolP384r1 |
+ secp384r1 |
+ sect283k1 |
+ sect283r1 |
+ brainpoolP256r1 |
+ secp256k1 |
+ secp256r1 |
+ sect239k1 |
+ sect233k1 |
+ sect233r1 |
+ secp224k1 |
+ secp224r1 |
+ sect193r1 |
+ sect193r2 |
+ secp192k1 |
+ secp192r1 |
+ sect163k1 |
+ sect163r1 |
+ sect163r2 |
+ secp160k1 |
+ secp160r1 |
+ secp160r2.
+
+-type srp_param_type() :: srp_1024 |
+ srp_1536 |
+ srp_2048 |
+ srp_3072 |
+ srp_4096 |
+ srp_6144 |
+ srp_8192.
+
+-type error_alert() :: {tls_alert, {tls_alert(), Description::string()}}.
+
+-type tls_alert() ::
+ close_notify |
+ unexpected_message |
+ bad_record_mac |
+ record_overflow |
+ handshake_failure |
+ bad_certificate |
+ unsupported_certificate |
+ certificate_revoked |
+ certificate_expired |
+ certificate_unknown |
+ illegal_parameter |
+ unknown_ca |
+ access_denied |
+ decode_error |
+ decrypt_error |
+ export_restriction|
+ protocol_version |
+ insufficient_security |
+ internal_error |
+ inappropriate_fallback |
+ user_canceled |
+ no_renegotiation |
+ unsupported_extension |
+ certificate_unobtainable |
+ unrecognized_name |
+ bad_certificate_status_response |
+ bad_certificate_hash_value |
+ unknown_psk_identity |
+ no_application_protocol.
+%% -------------------------------------------------------------------------------------------------------
+-type common_option() :: {protocol, protocol()} |
+ {handshake, handshake_completion()} |
+ {cert, cert()} |
+ {certfile, cert_pem()} |
+ {key, key()} |
+ {keyfile, key_pem()} |
+ {password, key_password()} |
+ {ciphers, cipher_suites()} |
+ {eccs, eccs()} |
+ {secure_renegotiate, secure_renegotiation()} |
+ {depth, allowed_cert_chain_length()} |
+ {verify_fun, custom_verify()} |
+ {crl_check, crl_check()} |
+ {crl_cache, crl_cache_opts()} |
+ {max_handshake_size, handshake_size()} |
+ {partial_chain, root_fun()} |
+ {versions, protocol_versions()} |
+ {user_lookup_fun, custom_user_lookup()} |
+ {log_alert, log_alert()} |
+ {hibernate_after, hibernate_after()} |
+ {padding_check, padding_check()} |
+ {beast_mitigation, beast_mitigation()}.
+
+-type protocol() :: tls | dtls.
+-type handshake_completion() :: hello | full.
+-type cert() :: public_key:der_encoded().
+-type cert_pem() :: ssl:path().
+-type key() :: {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo',
+ public_key:der_encoded()} |
+ #{algorithm := rsa | dss | ecdsa,
+ engine := crypto:engine_ref(),
+ key_id := crypto:key_id(),
+ password => crypto:password()}.
+-type key_pem() :: ssl:path().
+-type key_password() :: string().
+-type cipher_suites() :: ciphers().
+-type ciphers() :: [erl_cipher_suite()] |
+ string(). % (according to old API)
+-type cipher_filters() :: list({key_exchange | cipher | mac | prf,
+ algo_filter()}).
+-type algo_filter() :: fun((key_algo()|cipher()|hash()|aead|default_prf) -> true | false).
+-type eccs() :: [named_curve()].
+-type secure_renegotiation() :: boolean().
+-type allowed_cert_chain_length() :: integer().
+-type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: term()}.
+-type crl_check() :: boolean() | peer | best_effort.
+-type crl_cache_opts() :: [term()].
+-type handshake_size() :: integer().
+-type hibernate_after() :: timeout().
+-type root_fun() :: fun().
+-type protocol_versions() :: [protocol_version()].
+-type signature_algs() :: [{hash(), sign_algo()}].
+-type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: term()}.
+-type padding_check() :: boolean().
+-type beast_mitigation() :: one_n_minus_one | zero_n | disabled.
+-type srp_identity() :: {Username :: string(), Password :: string()}.
+-type psk_identity() :: string().
+-type log_alert() :: boolean().
+
+%% -------------------------------------------------------------------------------------------------------
+
+-type client_option() :: {verify, client_verify_type()} |
+ {reuse_session, client_reuse_session()} |
+ {reuse_sessions, client_reuse_sessions()} |
+ {cacerts, client_cacerts()} |
+ {cacertfile, client_cafile()} |
+ {alpn_advertised_protocols, client_alpn()} |
+ {client_preferred_next_protocols, client_preferred_next_protocols()} |
+ {psk_identity, client_psk_identity()} |
+ {srp_identity, client_srp_identity()} |
+ {server_name_indication, sni()} |
+ {customize_hostname_check, customize_hostname_check()} |
+ {signature_algs, client_signature_algs()} |
+ {fallback, fallback()}.
+
+-type client_verify_type() :: verify_type().
+-type client_reuse_session() :: ssl:session_id().
+-type client_reuse_sessions() :: boolean() | save.
+-type client_cacerts() :: [public_key:der_encoded()].
+-type client_cafile() :: ssl:path().
+-type app_level_protocol() :: binary().
+-type client_alpn() :: [app_level_protocol()].
+-type client_preferred_next_protocols() :: {Precedence :: server | client,
+ ClientPrefs :: [app_level_protocol()]} |
+ {Precedence :: server | client,
+ ClientPrefs :: [app_level_protocol()],
+ Default::app_level_protocol()}.
+-type client_psk_identity() :: psk_identity().
+-type client_srp_identity() :: srp_identity().
+-type customize_hostname_check() :: list().
+-type sni() :: HostName :: ssl:hostname() | disable.
+-type client_signature_algs() :: signature_algs().
+-type fallback() :: boolean().
+
+%% -------------------------------------------------------------------------------------------------------
+
+-type server_option() :: {cacerts, server_cacerts()} |
+ {cacertfile, server_cafile()} |
+ {dh, dh_der()} |
+ {dhfile, dh_file()} |
+ {verify, server_verify_type()} |
+ {fail_if_no_peer_cert, fail_if_no_peer_cert()} |
+ {reuse_sessions, server_reuse_sessions()} |
+ {reuse_session, server_reuse_session()} |
+ {alpn_preferred_protocols, server_alpn()} |
+ {next_protocols_advertised, server_next_protocol()} |
+ {psk_identity, server_psk_identity()} |
+ {honor_cipher_order, boolean()} |
+ {sni_hosts, sni_hosts()} |
+ {sni_fun, sni_fun()} |
+ {honor_cipher_order, honor_cipher_order()} |
+ {honor_ecc_order, honor_ecc_order()} |
+ {client_renegotiation, client_renegotiation()}|
+ {signature_algs, server_signature_algs()}.
+
+-type server_cacerts() :: [public_key:der_encoded()].
+-type server_cafile() :: ssl:path().
+-type server_alpn() :: [app_level_protocol()].
+-type server_next_protocol() :: [app_level_protocol()].
+-type server_psk_identity() :: psk_identity().
+-type dh_der() :: binary().
+-type dh_file() :: ssl:path().
+-type server_verify_type() :: verify_type().
+-type fail_if_no_peer_cert() :: boolean().
+-type server_signature_algs() :: signature_algs().
+-type server_reuse_session() :: fun().
+-type server_reuse_sessions() :: boolean().
+-type sni_hosts() :: [{ssl:hostname(), [server_option() | common_option()]}].
+-type sni_fun() :: fun().
+-type honor_cipher_order() :: boolean().
+-type honor_ecc_order() :: boolean().
+-type client_renegotiation() :: boolean().
+%% -------------------------------------------------------------------------------------------------------
+
+-type ssl_imp() :: new | old.
+
+
+-type prf_random() :: client_random | server_random.
+
+-type private_key_type() :: rsa | %% Backwards compatibility
+ dsa | %% Backwards compatibility
+ 'RSAPrivateKey' |
+ 'DSAPrivateKey' |
+ 'ECPrivateKey' |
+ 'PrivateKeyInfo'.
+
+-type hello_extensions() :: #{signature_algs => sign_algo()}. %% TODO
+%% -------------------------------------------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec start() -> ok | {error, reason()}.
--spec start(permanent | transient | temporary) -> ok | {error, reason()}.
%%
%% Description: Utility function that starts the ssl and applications
%% that it depends on.
%% see application(3)
%%--------------------------------------------------------------------
+-spec start() -> ok | {error, reason()}.
start() ->
start(temporary).
+-spec start(permanent | transient | temporary) -> ok | {error, reason()}.
start(Type) ->
case application:ensure_all_started(ssl, Type) of
{ok, _} ->
@@ -87,20 +392,17 @@ stop() ->
application:stop(ssl).
%%--------------------------------------------------------------------
--spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
- {error, reason()}.
--spec connect(host() | port(), [connect_option()] | inet:port_number(),
- timeout() | list()) ->
- {ok, #sslsocket{}} | {error, reason()}.
--spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
%%
%% Description: Connect to an ssl server.
%%--------------------------------------------------------------------
+-spec connect(host() | port(), [client_option()]) -> {ok, #sslsocket{}} |
+ {error, reason()}.
connect(Socket, SslOptions) when is_port(Socket) ->
connect(Socket, SslOptions, infinity).
+-spec connect(host() | port(), [client_option()] | inet:port_number(),
+ timeout() | list()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
connect(Socket, SslOptions0, Timeout) when is_port(Socket),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
{Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0,
@@ -117,6 +419,8 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket),
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
+-spec connect(host() | port(), inet:port_number(), [client_option()], timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
try
{ok, Config} = handle_options(Options, client, Host),
@@ -132,7 +436,7 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout
end.
%%--------------------------------------------------------------------
--spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
+-spec listen(inet:port_number(), [tls_server_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Creates an ssl listen socket.
@@ -148,16 +452,16 @@ listen(Port, Options0) ->
Error
end.
%%--------------------------------------------------------------------
--spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
- {error, reason()}.
--spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {error, reason()}.
%%
%% Description: Performs transport accept on an ssl listen socket
%%--------------------------------------------------------------------
+-spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
+ {error, reason()}.
transport_accept(ListenSocket) ->
transport_accept(ListenSocket, infinity).
+-spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {error, reason()}.
transport_accept(#sslsocket{pid = {ListenSocket,
#config{connection_cb = ConnectionCb} = Config}}, Timeout)
when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
@@ -169,25 +473,25 @@ transport_accept(#sslsocket{pid = {ListenSocket,
end.
%%--------------------------------------------------------------------
--spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}.
--spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option()
- | transport_option()]) ->
- ok | {ok, #sslsocket{}} | {error, reason()}.
-
--spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) ->
- ok | {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
+-spec ssl_accept(#sslsocket{}) -> ok | {error, timeout | closed | {options, any()}| error_alert()}.
ssl_accept(ListenSocket) ->
ssl_accept(ListenSocket, [], infinity).
+
+-spec ssl_accept(#sslsocket{} | port(), timeout()| [tls_server_option()]) ->
+ ok | {ok, #sslsocket{}} | {error, timeout | closed | {options, any()}| error_alert()}.
ssl_accept(Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_accept(Socket, [], Timeout);
ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
ssl_accept(ListenSocket, SslOptions, infinity);
ssl_accept(Socket, Timeout) ->
ssl_accept(Socket, [], Timeout).
+
+-spec ssl_accept(#sslsocket{} | port(), [tls_server_option()], timeout()) ->
+ ok | {ok, #sslsocket{}} | {error, timeout | closed | {options, any()}| error_alert()}.
ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
handshake(Socket, SslOptions, Timeout);
ssl_accept(Socket, SslOptions, Timeout) ->
@@ -198,20 +502,18 @@ ssl_accept(Socket, SslOptions, Timeout) ->
Error
end.
%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}.
--spec handshake(#sslsocket{} | port(), timeout()| [ssl_option()
- | transport_option()]) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
--spec handshake(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
+
+%% Performs the SSL/TLS/DTLS server-side handshake.
+-spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}.
handshake(ListenSocket) ->
handshake(ListenSocket, infinity).
+-spec handshake(#sslsocket{} | port(), timeout()| [tls_server_option()]) ->
+ {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}.
handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity) ->
ssl_connection:handshake(Socket, Timeout);
@@ -219,6 +521,8 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim
handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
handshake(ListenSocket, SslOptions, infinity).
+-spec handshake(#sslsocket{} | port(), [tls_server_option()], timeout()) ->
+ {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}.
handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity)->
handshake(Socket, Timeout);
@@ -261,7 +565,7 @@ handshake(Socket, SslOptions, Timeout) when is_port(Socket),
%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl_option()]) ->
+-spec handshake_continue(#sslsocket{}, [tls_client_option() | tls_server_option()]) ->
{ok, #sslsocket{}} | {error, reason()}.
%%
%%
@@ -270,7 +574,7 @@ handshake(Socket, SslOptions, Timeout) when is_port(Socket),
handshake_continue(Socket, SSLOptions) ->
handshake_continue(Socket, SSLOptions, infinity).
%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl_option()], timeout()) ->
+-spec handshake_continue(#sslsocket{}, [tls_client_option() | tls_server_option()], timeout()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%
%%
@@ -331,13 +635,14 @@ send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}
Transport:send(ListenSocket, Data). %% {error,enotconn}
%%--------------------------------------------------------------------
--spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
--spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
%%
%% Description: Receives data when active = false
%%--------------------------------------------------------------------
+-spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
recv(Socket, Length) ->
recv(Socket, Length, infinity).
+
+-spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
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);
@@ -460,9 +765,9 @@ cipher_suites(all) ->
[ssl_cipher_format:erl_suite_definition(Suite) || Suite <- available_suites(all)].
%%--------------------------------------------------------------------
--spec cipher_suites(default | all | anonymous, tls_record:tls_version() | dtls_record:dtls_version() |
+-spec cipher_suites(default | all | anonymous, ssl_record:ssl_version() |
tls_record:tls_atom_version() | dtls_record:dtls_atom_version()) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()].
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
%%--------------------------------------------------------------------
@@ -478,9 +783,9 @@ cipher_suites(Base, Version) ->
[ssl_cipher_format:suite_definition(Suite) || Suite <- supported_suites(Base, Version)].
%%--------------------------------------------------------------------
--spec filter_cipher_suites([ssl_cipher_format:erl_cipher_suite()],
+-spec filter_cipher_suites([erl_cipher_suite()],
[{key_exchange | cipher | mac | prf, fun()}] | []) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()].
%% Description: Removes cipher suites if any of the filter functions returns false
%% for any part of the cipher suite. This function also calls default filter functions
%% to make sure the cipher suite are supported by crypto.
@@ -497,10 +802,10 @@ filter_cipher_suites(Suites, Filters0) ->
prf_filters => add_filter(proplists:get_value(prf, Filters0), PrfF)},
ssl_cipher:filter_suites(Suites, Filters).
%%--------------------------------------------------------------------
--spec prepend_cipher_suites([ssl_cipher_format:erl_cipher_suite()] |
+-spec prepend_cipher_suites([erl_cipher_suite()] |
[{key_exchange | cipher | mac | prf, fun()}],
- [ssl_cipher_format:erl_cipher_suite()]) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()]) ->
+ [erl_cipher_suite()].
%% Description: Make <Preferred> suites become the most prefered
%% suites that is put them at the head of the cipher suite list
%% and remove them from <Suites> if present. <Preferred> may be a
@@ -515,10 +820,10 @@ prepend_cipher_suites(Filters, Suites) ->
Preferred = filter_cipher_suites(Suites, Filters),
Preferred ++ (Suites -- Preferred).
%%--------------------------------------------------------------------
--spec append_cipher_suites(Deferred :: [ssl_cipher_format:erl_cipher_suite()] |
+-spec append_cipher_suites(Deferred :: [erl_cipher_suite()] |
[{key_exchange | cipher | mac | prf, fun()}],
- [ssl_cipher_format:erl_cipher_suite()]) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()]) ->
+ [erl_cipher_suite()].
%% Description: Make <Deferred> suites suites become the
%% least prefered suites that is put them at the end of the cipher suite list
%% and removed them from <Suites> if present.
@@ -540,8 +845,8 @@ eccs() ->
eccs_filter_supported(Curves).
%%--------------------------------------------------------------------
--spec eccs(tls_record:tls_version() | tls_record:tls_atom_version() |
- dtls_record:dtls_version() | dtls_record:dtls_atom_version()) ->
+-spec eccs(tls_record:tls_atom_version() |
+ ssl_record:ssl_version() | dtls_record:dtls_atom_version()) ->
tls_v1:curves().
%% Description: returns the curves supported for a given version of
%% ssl/tls.
@@ -723,7 +1028,7 @@ versions() ->
SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns],
AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS,
AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS,
- [{ssl_app, ?VSN}, {supported, SupportedTLSVsns},
+ [{ssl_app, "9.2"}, {supported, SupportedTLSVsns},
{supported_dtls, SupportedDTLSVsns},
{available, AvailableTLSVsns},
{available_dtls, AvailableDTLSVsns}].
@@ -783,8 +1088,8 @@ format_error(Reason) when is_list(Reason) ->
Reason;
format_error(closed) ->
"TLS connection is closed";
-format_error({tls_alert, Description}) ->
- "TLS Alert: " ++ Description;
+format_error({tls_alert, {_, Description}}) ->
+ Description;
format_error({options,{FileType, File, Reason}}) when FileType == cacertfile;
FileType == certfile;
FileType == keyfile;
@@ -813,7 +1118,7 @@ tls_version({254, _} = Version) ->
%%--------------------------------------------------------------------
--spec suite_to_str(ssl_cipher_format:erl_cipher_suite()) -> string().
+-spec suite_to_str(erl_cipher_suite()) -> string().
%%
%% Description: Return the string representation of a cipher suite.
%%--------------------------------------------------------------------
@@ -891,8 +1196,6 @@ handle_options(Opts0, Role, Host) ->
{list, [{mode, list}]}], Opts0),
assert_proplist(Opts),
RecordCb = record_cb(Opts),
-
- ReuseSessionFun = fun(_, _, _, _) -> true end,
CaCerts = handle_option(cacerts, Opts, undefined),
{Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} =
@@ -945,9 +1248,8 @@ handle_options(Opts0, Role, Host) ->
default_option_role(server,
tls_v1:default_signature_algs(Versions), Role)),
tls_version(RecordCb:highest_protocol_version(Versions))),
- %% Server side option
- reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
- reuse_sessions = handle_option(reuse_sessions, Opts, true),
+ reuse_sessions = handle_reuse_sessions_option(reuse_sessions, Opts, Role),
+ reuse_session = handle_reuse_session_option(reuse_session, Opts, Role),
secure_renegotiate = handle_option(secure_renegotiate, Opts, true),
client_renegotiation = handle_option(client_renegotiation, Opts,
default_option_role(server, true, Role),
@@ -1003,8 +1305,8 @@ handle_options(Opts0, Role, Host) ->
alpn_preferred_protocols, next_protocols_advertised,
client_preferred_next_protocols, log_alert,
server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache,
- fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation,
- max_handshake_size, handshake, customize_hostname_check],
+ fallback, signature_algs, eccs, honor_ecc_order,
+ beast_mitigation, max_handshake_size, handshake, customize_hostname_check],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
end, Opts, SslOptions),
@@ -1138,11 +1440,16 @@ validate_option(srp_identity, {Username, Password})
{unicode:characters_to_binary(Username),
unicode:characters_to_binary(Password)};
+validate_option(reuse_session, undefined) ->
+ undefined;
validate_option(reuse_session, Value) when is_function(Value) ->
Value;
+validate_option(reuse_session, Value) when is_binary(Value) ->
+ Value;
validate_option(reuse_sessions, Value) when is_boolean(Value) ->
Value;
-
+validate_option(reuse_sessions, save = Value) ->
+ Value;
validate_option(secure_renegotiate, Value) when is_boolean(Value) ->
Value;
validate_option(client_renegotiation, Value) when is_boolean(Value) ->
@@ -1265,6 +1572,26 @@ handle_hashsigns_option(_, Version) when Version >= {3, 3} ->
handle_hashsigns_option(_, _Version) ->
undefined.
+handle_reuse_sessions_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_sessions_option(Key, Opts0, server) ->
+ Opts = proplists:delete({Key, save}, Opts0),
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value.
+
+handle_reuse_session_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, undefined),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_session_option(Key, Opts, server) ->
+ ReuseSessionFun = fun(_, _, _, _) -> true end,
+ Value = proplists:get_value(Key, Opts, ReuseSessionFun),
+ validate_option(Key, Value),
+ Value.
+
validate_options([]) ->
[];
validate_options([{Opt, Value} | Tail]) ->
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 34e9797f1f..2a20d13cd5 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -48,8 +48,8 @@ decode(Bin) ->
decode(Bin, [], 0).
%%--------------------------------------------------------------------
--spec reason_code(#alert{}, client | server) ->
- closed | {tls_alert, unicode:chardata()}.
+%% -spec reason_code(#alert{}, client | server) ->
+%% {tls_alert, unicode:chardata()} | closed.
%-spec reason_code(#alert{}, client | server) -> closed | {essl, string()}.
%%
%% Description: Returns the error reason that will be returned to the
@@ -58,8 +58,10 @@ decode(Bin) ->
reason_code(#alert{description = ?CLOSE_NOTIFY}, _) ->
closed;
-reason_code(#alert{description = Description}, _) ->
- {tls_alert, string:casefold(description_txt(Description))}.
+reason_code(#alert{description = Description, role = Role} = Alert, Role) ->
+ {tls_alert, {description_atom(Description), own_alert_txt(Alert)}};
+reason_code(#alert{description = Description} = Alert, Role) ->
+ {tls_alert, {description_atom(Description), alert_txt(Alert#alert{role = Role})}}.
%%--------------------------------------------------------------------
-spec own_alert_txt(#alert{}) -> string().
@@ -181,3 +183,70 @@ description_txt(?NO_APPLICATION_PROTOCOL) ->
"No application protocol";
description_txt(Enum) ->
lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])).
+
+description_atom(?CLOSE_NOTIFY) ->
+ close_notify;
+description_atom(?UNEXPECTED_MESSAGE) ->
+ unexpected_message;
+description_atom(?BAD_RECORD_MAC) ->
+ bad_record_mac;
+description_atom(?DECRYPTION_FAILED_RESERVED) ->
+ decryption_failed_reserved;
+description_atom(?RECORD_OVERFLOW) ->
+ record_overflow;
+description_atom(?DECOMPRESSION_FAILURE) ->
+ decompression_failure;
+description_atom(?HANDSHAKE_FAILURE) ->
+ handshake_failure;
+description_atom(?NO_CERTIFICATE_RESERVED) ->
+ no_certificate_reserved;
+description_atom(?BAD_CERTIFICATE) ->
+ bad_certificate;
+description_atom(?UNSUPPORTED_CERTIFICATE) ->
+ unsupported_certificate;
+description_atom(?CERTIFICATE_REVOKED) ->
+ certificate_revoked;
+description_atom(?CERTIFICATE_EXPIRED) ->
+ certificate_expired;
+description_atom(?CERTIFICATE_UNKNOWN) ->
+ certificate_unknown;
+description_atom(?ILLEGAL_PARAMETER) ->
+ illegal_parameter;
+description_atom(?UNKNOWN_CA) ->
+ unknown_ca;
+description_atom(?ACCESS_DENIED) ->
+ access_denied;
+description_atom(?DECODE_ERROR) ->
+ decode_error;
+description_atom(?DECRYPT_ERROR) ->
+ decrypt_error;
+description_atom(?EXPORT_RESTRICTION) ->
+ export_restriction;
+description_atom(?PROTOCOL_VERSION) ->
+ protocol_version;
+description_atom(?INSUFFICIENT_SECURITY) ->
+ insufficient_security;
+description_atom(?INTERNAL_ERROR) ->
+ internal_error;
+description_atom(?USER_CANCELED) ->
+ user_canceled;
+description_atom(?NO_RENEGOTIATION) ->
+ no_renegotiation;
+description_atom(?UNSUPPORTED_EXTENSION) ->
+ unsupported_extension;
+description_atom(?CERTIFICATE_UNOBTAINABLE) ->
+ certificate_unobtainable;
+description_atom(?UNRECOGNISED_NAME) ->
+ unrecognised_name;
+description_atom(?BAD_CERTIFICATE_STATUS_RESPONSE) ->
+ bad_certificate_status_response;
+description_atom(?BAD_CERTIFICATE_HASH_VALUE) ->
+ bad_certificate_hash_value;
+description_atom(?UNKNOWN_PSK_IDENTITY) ->
+ unknown_psk_identity;
+description_atom(?INAPPROPRIATE_FALLBACK) ->
+ inappropriate_fallback;
+description_atom(?NO_APPLICATION_PROTOCOL) ->
+ no_application_protocol;
+description_atom(_) ->
+ 'unsupported/unkonwn_alert'.
diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl
index 7b7b1cbcd9..f4594912bd 100644
--- a/lib/ssl/src/ssl_api.hrl
+++ b/lib/ssl/src/ssl_api.hrl
@@ -21,56 +21,7 @@
-ifndef(ssl_api).
-define(ssl_api, true).
--include("ssl_cipher.hrl").
-
-%% Visible in API
--export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
- prf_random/0, sslsocket/0]).
-
-
%% Looks like it does for backwards compatibility reasons
-record(sslsocket, {fd = nil, pid = nil}).
-
--type sslsocket() :: #sslsocket{}.
--type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
--type socket_connect_option() :: gen_tcp:connect_option().
--type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
--type socket_listen_option() :: gen_tcp:listen_option().
-
--type ssl_option() :: {versions, ssl_record:ssl_atom_version()} |
- {verify, verify_type()} |
- {verify_fun, {fun(), InitialUserState::term()}} |
- {fail_if_no_peer_cert, boolean()} | {depth, integer()} |
- {cert, Der::binary()} | {certfile, path()} |
- {key, {private_key_type(), Der::binary()}} |
- {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
- {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
- {user_lookup_fun, {fun(), InitialUserState::term()}} |
- {psk_identity, string()} |
- {srp_identity, {string(), string()}} |
- {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
- {reuse_session, fun()} | {hibernate_after, integer()|undefined} |
- {alpn_advertised_protocols, [binary()]} |
- {alpn_preferred_protocols, [binary()]} |
- {next_protocols_advertised, list(binary())} |
- {client_preferred_next_protocols, binary(), client | server, list(binary())}.
-
--type verify_type() :: verify_none | verify_peer.
--type path() :: string().
--type ciphers() :: [ssl_cipher_format:erl_cipher_suite()] |
- string(). % (according to old API)
--type ssl_imp() :: new | old.
-
--type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
- ClosedTag::atom(), ErrTag::atom()}}.
--type prf_random() :: client_random | server_random.
-
--type private_key_type() :: rsa | %% Backwards compatibility
- dsa | %% Backwards compatibility
- 'RSAPrivateKey' |
- 'DSAPrivateKey' |
- 'ECPrivateKey' |
- 'PrivateKeyInfo'.
-
-endif. % -ifdef(ssl_api).
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 54c04c13e5..cf1bec6332 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -481,8 +481,8 @@ filter(DerCert, Ciphers0, Version) ->
filter_suites_signature(Sign, Ciphers, Version).
%%--------------------------------------------------------------------
--spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) ->
- [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
+-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) ->
+ [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
%%
%% Description: Filter suites using supplied filter funs
%%-------------------------------------------------------------------
@@ -508,8 +508,8 @@ filter_suite(Suite, Filters) ->
filter_suite(ssl_cipher_format:suite_definition(Suite), Filters).
%%--------------------------------------------------------------------
--spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) ->
- [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
+-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) ->
+ [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
%%
%% Description: Filter suites for algorithms supported by crypto.
%%-------------------------------------------------------------------
@@ -593,7 +593,7 @@ is_acceptable_cipher(rc4_128, Algos) ->
is_acceptable_cipher(des_cbc, Algos) ->
proplists:get_bool(des_cbc, Algos);
is_acceptable_cipher('3des_ede_cbc', Algos) ->
- proplists:get_bool(des3_cbc, Algos);
+ proplists:get_bool(des_ede3, Algos);
is_acceptable_cipher(aes_128_cbc, Algos) ->
proplists:get_bool(aes_cbc128, Algos);
is_acceptable_cipher(aes_256_cbc, Algos) ->
@@ -889,7 +889,7 @@ is_correct_padding(GenBlockCipher, {3, 1}, false) ->
is_correct_padding(#generic_block_cipher{padding_length = Len,
padding = Padding}, _, _) ->
Len == byte_size(Padding) andalso
- list_to_binary(lists:duplicate(Len, Len)) == Padding.
+ binary:copy(?byte(Len), Len) == Padding.
get_padding(Length, BlockSize) ->
get_padding_aux(BlockSize, Length rem BlockSize).
@@ -898,7 +898,7 @@ get_padding_aux(_, 0) ->
{0, <<>>};
get_padding_aux(BlockSize, PadLength) ->
N = BlockSize - PadLength,
- {N, list_to_binary(lists:duplicate(N, N))}.
+ {N, binary:copy(?byte(N), N)}.
random_iv(IV) ->
IVSz = byte_size(IV),
diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl
index c311c0d097..f7af96583f 100644
--- a/lib/ssl/src/ssl_cipher_format.erl
+++ b/lib/ssl/src/ssl_cipher_format.erl
@@ -25,26 +25,25 @@
%%----------------------------------------------------------------------
-module(ssl_cipher_format).
+-include("ssl_api.hrl").
-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
--export_type([cipher_suite/0,
- erl_cipher_suite/0, old_erl_cipher_suite/0, openssl_cipher_suite/0,
- hash/0, key_algo/0, sign_algo/0]).
+-export_type([old_erl_cipher_suite/0, openssl_cipher_suite/0, cipher_suite/0]).
--type cipher() :: null |rc4_128 | des_cbc | '3des_ede_cbc' | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305.
--type hash() :: null | md5 | sha | sha224 | sha256 | sha384 | sha512.
--type sign_algo() :: rsa | dsa | ecdsa.
--type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon.
--type erl_cipher_suite() :: #{key_exchange := key_algo(),
- cipher := cipher(),
- mac := hash() | aead,
- prf := hash() | default_prf %% Old cipher suites, version dependent
+-type internal_cipher() :: null | ssl:cipher().
+-type internal_hash() :: null | ssl:hash().
+-type internal_key_algo() :: null | ssl:key_algo().
+-type internal_erl_cipher_suite() :: #{key_exchange := internal_key_algo(),
+ cipher := internal_cipher(),
+ mac := internal_hash() | aead,
+ prf := internal_hash() | default_prf %% Old cipher suites, version dependent
}.
--type old_erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2
+-type old_erl_cipher_suite() :: {ssl:key_algo(), internal_cipher(), internal_hash()} % Pre TLS 1.2
%% TLS 1.2, internally PRE TLS 1.2 will use default_prf
- | {key_algo(), cipher(), hash(), hash() | default_prf}.
+ | {ssl:key_algo(), internal_cipher(), internal_hash(),
+ internal_hash() | default_prf}.
-type cipher_suite() :: binary().
-type openssl_cipher_suite() :: string().
@@ -53,7 +52,7 @@
openssl_suite/1, openssl_suite_name/1]).
%%--------------------------------------------------------------------
--spec suite_to_str(erl_cipher_suite()) -> string().
+-spec suite_to_str(internal_erl_cipher_suite()) -> string().
%%
%% Description: Return the string representation of a cipher suite.
%%--------------------------------------------------------------------
@@ -77,7 +76,7 @@ suite_to_str(#{key_exchange := Kex,
"_" ++ string:to_upper(atom_to_list(Mac)).
%%--------------------------------------------------------------------
--spec suite_definition(cipher_suite()) -> erl_cipher_suite().
+-spec suite_definition(cipher_suite()) -> internal_erl_cipher_suite().
%%
%% Description: Return erlang cipher suite definition.
%% Note: Currently not supported suites are commented away.
@@ -805,7 +804,7 @@ suite_definition(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
prf => sha256}.
%%--------------------------------------------------------------------
--spec erl_suite_definition(cipher_suite() | erl_cipher_suite()) -> old_erl_cipher_suite().
+-spec erl_suite_definition(cipher_suite() | internal_erl_cipher_suite()) -> old_erl_cipher_suite().
%%
%% Description: Return erlang cipher suite definition. Filters last value
%% for now (compatibility reasons).
@@ -822,7 +821,7 @@ erl_suite_definition(#{key_exchange := KeyExchange, cipher := Cipher,
end.
%%--------------------------------------------------------------------
--spec suite(erl_cipher_suite()) -> cipher_suite().
+-spec suite(internal_erl_cipher_suite()) -> cipher_suite().
%%
%% Description: Return TLS cipher suite definition.
%%--------------------------------------------------------------------
@@ -1585,7 +1584,7 @@ openssl_suite("ECDH-RSA-AES256-GCM-SHA384") ->
?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384.
%%--------------------------------------------------------------------
--spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | erl_cipher_suite().
+-spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | internal_erl_cipher_suite().
%%
%% Description: Return openssl cipher suite name if possible
%%-------------------------------------------------------------------
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 4b406b4c1e..41a45089d0 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -51,7 +51,7 @@
%% Alert and close handling
-export([handle_own_alert/4, handle_alert/3,
- handle_normal_shutdown/3, stop/2, stop_and_reply/3,
+ handle_normal_shutdown/3,
handle_trusted_certs_db/1]).
%% Data handling
@@ -77,7 +77,7 @@
%%====================================================================
%%--------------------------------------------------------------------
-spec connect(tls_connection | dtls_connection,
- host(), inet:port_number(),
+ ssl:host(), inet:port_number(),
port() | {tuple(), port()}, %% TLS | DTLS
{#ssl_options{}, #socket_options{},
%% Tracker only needed on server side
@@ -143,7 +143,7 @@ handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
end.
%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl_option()],
+-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
%%
%% Description: Continues handshake with new options
@@ -335,8 +335,8 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
%% Alert and close handling
%%====================================================================
handle_own_alert(Alert, _, StateName,
- #state{role = Role,
- protocol_cb = Connection,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
ssl_options = SslOpts} = State) ->
try %% Try to tell the other side
send_alert(Alert, StateName, State)
@@ -349,77 +349,95 @@ handle_own_alert(Alert, _, StateName,
catch _:_ ->
ok
end,
- stop({shutdown, own_alert}, State).
-
-handle_normal_shutdown(Alert, _, #state{socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- start_or_recv_from = StartFrom,
- tracker = Tracker,
- role = Role, renegotiation = {false, first}} = State) ->
+ {stop, {shutdown, own_alert}, State}.
+
+handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ tracker = Tracker},
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection);
-handle_normal_shutdown(Alert, StateName, #state{socket = Socket,
- socket_options = Opts,
- transport_cb = Transport,
- protocol_cb = Connection,
- user_application = {_Mon, Pid},
- tracker = Tracker,
- start_or_recv_from = RecvFrom, role = Role} = State) ->
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ tracker = Tracker},
+ socket_options = Opts,
+ user_application = {_Mon, Pid},
+ start_or_recv_from = RecvFrom} = State) ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection).
handle_alert(#alert{level = ?FATAL} = Alert, StateName,
- #state{socket = Socket, transport_cb = Transport,
- protocol_cb = Connection,
- ssl_options = SslOpts, start_or_recv_from = From, host = Host,
- port = Port, session = Session, user_application = {_Mon, Pid},
- role = Role, socket_options = Opts, tracker = Tracker} = State) ->
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ host = Host,
+ port = Port,
+ tracker = Tracker,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ ssl_options = SslOpts,
+ start_or_recv_from = From,
+ session = Session, user_application = {_Mon, Pid},
+ socket_options = Opts} = State) ->
invalidate_session(Role, Host, Port, Session),
log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(),
StateName, Alert#alert{role = opposite_role(Role)}),
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection),
- stop(normal, State);
+ {stop, {shutdown, normal}, State};
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- StateName, State) ->
+ downgrade= StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, Alert}]};
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ StateName, State) ->
handle_normal_shutdown(Alert, StateName, State),
- stop({shutdown, peer_close}, State);
-
+ {stop,{shutdown, peer_close}, State};
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection,
- renegotiation = {true, internal}} = State) ->
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, internal}},
+ ssl_options = SslOpts} = State) ->
log_alert(SslOpts#ssl_options.log_alert, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
handle_normal_shutdown(Alert, StateName, State),
- stop({shutdown, peer_close}, State);
+ {stop,{shutdown, peer_close}, State};
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
- #state{role = Role,
- ssl_options = SslOpts, renegotiation = {true, From},
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = SslOpts
+ } = State0) ->
log_alert(SslOpts#ssl_options.log_alert, 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{renegotiation = undefined});
+ 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{role = Role,
- ssl_options = SslOpts, renegotiation = {true, From},
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = SslOpts
+ } = State0) ->
log_alert(SslOpts#ssl_options.log_alert, 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{renegotiation = undefined}),
+ 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{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State) ->
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = SslOpts} = State) ->
log_alert(SslOpts#ssl_options.log_alert, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
Connection:next_event(StateName, no_record, State).
@@ -427,7 +445,6 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName,
%%====================================================================
%% Data handling
%%====================================================================
-
passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) ->
case Buffer of
<<>> ->
@@ -442,89 +459,106 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connectio
end
end.
-read_application_data(Data, #state{user_application = {_Mon, Pid},
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- socket_options = SOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- timer = Timer,
- user_data_buffer = Buffer0,
- tracker = Tracker} = State0) ->
- Buffer1 = if
- Buffer0 =:= <<>> -> Data;
- Data =:= <<>> -> Buffer0;
- true -> <<Buffer0/binary, Data/binary>>
- end,
- case get_data(SOpts, BytesToRead, Buffer1) of
+read_application_data(
+ Data,
+ #state{
+ user_data_buffer = Buffer0,
+ erl_dist_handle = DHandle} = State) ->
+ %%
+ Buffer = bincat(Buffer0, Data),
+ case DHandle of
+ undefined ->
+ #state{
+ socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ timer = Timer} = State,
+ read_application_data(
+ Buffer, State, SocketOpts, RecvFrom, Timer, BytesToRead);
+ _ ->
+ try read_application_dist_data(Buffer, State, DHandle)
+ catch error:_ ->
+ {stop,disconnect,
+ State#state{
+ user_data_buffer = Buffer,
+ bytes_to_read = undefined}}
+ end
+ end.
+
+read_application_dist_data(Buffer, State, DHandle) ->
+ case Buffer of
+ <<Size:32,Data:Size/binary>> ->
+ erlang:dist_ctrl_put_data(DHandle, Data),
+ {no_record,
+ State#state{
+ user_data_buffer = <<>>,
+ bytes_to_read = undefined}};
+ <<Size:32,Data:Size/binary,Rest/binary>> ->
+ erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(Rest, State, DHandle);
+ _ ->
+ {no_record,
+ State#state{
+ user_data_buffer = Buffer,
+ bytes_to_read = undefined}}
+ end.
+
+read_application_data(
+ Buffer0, State, SocketOpts0, RecvFrom, Timer, BytesToRead) ->
+ %%
+ case get_data(SocketOpts0, BytesToRead, Buffer0) of
{ok, ClientData, Buffer} -> % Send data
- #state{ssl_options = #ssl_options{erl_dist = Dist},
- erl_dist_data = DistData} = State0,
- case Dist andalso is_dist_up(DistData) of
- true ->
- dist_app_data(ClientData, State0#state{user_data_buffer = Buffer,
- bytes_to_read = undefined});
- _ ->
- SocketOpt =
- deliver_app_data(Connection:pids(State0),
- Transport, Socket, SOpts,
- ClientData, Pid, RecvFrom, Tracker, Connection),
- cancel_timer(Timer),
- State =
- State0#state{
- user_data_buffer = Buffer,
- start_or_recv_from = undefined,
- timer = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpt
- },
- if
- SocketOpt#socket_options.active =:= false;
- Buffer =:= <<>> ->
- %% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- {no_record, State};
- true -> %% We have more data
- read_application_data(<<>>, State)
- end
- end;
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ tracker = Tracker},
+ user_application = {_Mon, Pid}} = State,
+ SocketOpts =
+ deliver_app_data(
+ Connection:pids(State),
+ Transport, Socket, SocketOpts0,
+ ClientData, Pid, RecvFrom, Tracker, Connection),
+ cancel_timer(Timer),
+ if
+ SocketOpts#socket_options.active =:= false;
+ Buffer =:= <<>> ->
+ %% Passive mode, wait for active once or recv
+ %% Active and empty, get more data
+ {no_record,
+ State#state{
+ user_data_buffer = Buffer,
+ start_or_recv_from = undefined,
+ timer = undefined,
+ bytes_to_read = undefined,
+ socket_options = SocketOpts
+ }};
+ true -> %% We have more data
+ read_application_data(
+ Buffer, State, SocketOpts,
+ undefined, undefined, undefined)
+ end;
{more, Buffer} -> % no reply, we need more data
- {no_record, State0#state{user_data_buffer = Buffer}};
+ {no_record, State#state{user_data_buffer = Buffer}};
{passive, Buffer} ->
- {no_record, State0#state{user_data_buffer = Buffer}};
+ {no_record, State#state{user_data_buffer = Buffer}};
{error,_Reason} -> %% Invalid packet in packet mode
- deliver_packet_error(Connection:pids(State0),
- Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection),
- stop(normal, State0)
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ tracker = Tracker},
+ user_application = {_Mon, Pid}} = State,
+ deliver_packet_error(
+ Connection:pids(State), Transport, Socket, SocketOpts0,
+ Buffer0, Pid, RecvFrom, Tracker, Connection),
+ {stop, {shutdown, normal}, State}
end.
-dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := undefined,
- dist_buffer := DistBuff} = DistData} = State) ->
- {no_record, State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}};
-dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle,
- dist_buffer := DistBuff} = ErlDistData,
- user_data_buffer = Buffer,
- socket_options = SOpts} = State) ->
- Data = merge_dist_data(DistBuff, ClientData),
- try erlang:dist_ctrl_put_data(DHandle, Data) of
- _ when SOpts#socket_options.active =:= false;
- Buffer =:= <<>> ->
- %% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- {no_record, State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}};
- _ -> %% We have more data
- read_application_data(<<>>, State)
- catch error:_ ->
- stop(State, disconnect)
- end.
-
-merge_dist_data(<<>>, ClientData) ->
- ClientData;
-merge_dist_data(DistBuff, <<>>) ->
- DistBuff;
-merge_dist_data(DistBuff, ClientData) ->
- [DistBuff, ClientData].
%%====================================================================
%% Help functions for tls|dtls_connection.erl
%%====================================================================
@@ -573,7 +607,8 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
ssl_config(Opts, Role, State) ->
ssl_config(Opts, Role, State, new).
-ssl_config(Opts, Role, State0, Type) ->
+ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
+ handshake_env = HsEnv} = State0, Type) ->
{ok, #{cert_db_ref := Ref,
cert_db_handle := CertDbHandle,
fileref_db_handle := FileRefHandle,
@@ -585,20 +620,23 @@ ssl_config(Opts, Role, State0, Type) ->
ssl_config:init(Opts, Role),
TimeStamp = erlang:monotonic_time(),
Session = State0#state.session,
+
State = State0#state{session = Session#session{own_certificate = OwnCert,
time_stamp = TimeStamp},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
private_key = Key,
diffie_hellman_params = DHParams,
ssl_options = Opts},
case Type of
new ->
- Handshake = ssl_handshake:init_handshake_history(),
- State#state{tls_handshake_history = Handshake};
+ Hist = ssl_handshake:init_handshake_history(),
+ State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
continue ->
State
end.
@@ -618,7 +656,8 @@ init({call, From}, {start, Timeout}, State0, Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, From),
Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer});
init({call, From}, {start, {Opts, EmOpts}, Timeout},
- #state{role = Role, ssl_options = OrigSSLOptions,
+ #state{static_env = #static_env{role = Role},
+ ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0, Connection) ->
try
SslOpts = ssl:handle_options(Opts, OrigSSLOptions),
@@ -627,7 +666,7 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout},
State#state{ssl_options = SslOpts,
socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
catch throw:Error ->
- stop_and_reply(normal, {reply, From, {error, Error}}, State0)
+ {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);
@@ -643,7 +682,7 @@ init(_Type, _Event, _State, _Connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
error({call, From}, {close, _}, State, _Connection) ->
- stop_and_reply(normal, {reply, From, ok}, State);
+ {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
error({call, From}, _Msg, State, _Connection) ->
{next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}.
@@ -666,10 +705,11 @@ user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _
gen_statem:reply(From, ok),
handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
Version, ?FUNCTION_NAME, State);
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{hello = Hello,
- role = Role,
- start_or_recv_from = RecvFrom,
- ssl_options = Options0} = State0, _Connection) ->
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{hello = Hello,
+ static_env = #static_env{role = Role},
+ start_or_recv_from = RecvFrom,
+ ssl_options = Options0} = State0, _Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}),
State = ssl_config(Options, Role, State0, continue),
@@ -688,16 +728,16 @@ user_hello(_, _, _, _) ->
abbreviated({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{role = server,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
negotiated_version = Version,
expecting_finished = true,
- tls_handshake_history = Handshake,
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} =
State0, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
get_current_prf(ConnectionStates0, write),
- MasterSecret, Handshake) of
+ MasterSecret, Hist) of
verified ->
ConnectionStates =
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
@@ -708,13 +748,14 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{role = client, tls_handshake_history = Handshake0,
+ #state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{tls_handshake_history = Hist0},
session = #session{master_secret = MasterSecret},
negotiated_version = Version,
connection_states = ConnectionStates0} = State0, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
get_pending_prf(ConnectionStates0, write),
- MasterSecret, Handshake0) of
+ MasterSecret, Hist0) of
verified ->
ConnectionStates1 =
ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
@@ -729,7 +770,8 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,
%% 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{role = server, expecting_next_protocol_negotiation = true} = State,
+ #state{static_env = #static_env{role = server},
+ expecting_next_protocol_negotiation = true} = State,
Connection) ->
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{negotiated_protocol = SelectedProtocol,
@@ -759,32 +801,34 @@ certify({call, From}, Msg, State, Connection) ->
certify(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
- #state{role = server, negotiated_version = Version,
+ #state{static_env = #static_env{role = server},
+ negotiated_version = Version,
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = true}} =
State, _) ->
Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
- #state{role = server,
+ #state{static_env = #static_env{role = server},
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = false}} =
State0, Connection) ->
Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
certify(internal, #certificate{},
- #state{role = server,
+ #state{static_env = #static_env{role = server},
negotiated_version = Version,
ssl_options = #ssl_options{verify = verify_none}} =
State, _) ->
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{} = Cert,
- #state{negotiated_version = Version,
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbInfo,
+ #state{static_env = #static_env{
+ role = Role,
+ host = Host,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbInfo},
+ negotiated_version = Version,
ssl_options = Opts} = State, Connection) ->
case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
Opts, CRLDbInfo, Role, Host) of
@@ -795,7 +839,8 @@ certify(internal, #certificate{} = Cert,
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
certify(internal, #server_key_exchange{exchange_keys = Keys},
- #state{role = client, negotiated_version = Version,
+ #state{static_env = #static_env{role = client},
+ negotiated_version = Version,
key_algorithm = Alg,
public_key_info = PubKeyInfo,
session = Session,
@@ -829,7 +874,8 @@ certify(internal, #server_key_exchange{exchange_keys = Keys},
end
end;
certify(internal, #certificate_request{},
- #state{role = client, negotiated_version = Version,
+ #state{static_env = #static_env{role = client},
+ negotiated_version = Version,
key_algorithm = Alg} = State, _)
when Alg == dh_anon; Alg == ecdh_anon;
Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk;
@@ -837,17 +883,17 @@ certify(internal, #certificate_request{},
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
Version, ?FUNCTION_NAME, State);
certify(internal, #certificate_request{},
- #state{session = #session{own_certificate = undefined},
- role = client} = State, Connection) ->
+ #state{static_env = #static_env{role = client},
+ session = #session{own_certificate = undefined}} = State, Connection) ->
%% 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{session = #session{own_certificate = Cert},
- role = client,
- ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
- negotiated_version = Version} = State, Connection) ->
+ #state{static_env = #static_env{role = client},
+ session = #session{own_certificate = Cert},
+ ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
+ negotiated_version = Version} = State, Connection) ->
case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of
#alert {} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
@@ -858,12 +904,12 @@ certify(internal, #certificate_request{} = CertRequest,
end;
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(internal, #server_hello_done{},
- #state{session = #session{master_secret = undefined},
+ #state{static_env = #static_env{role = client},
+ session = #session{master_secret = undefined},
negotiated_version = Version,
psk_identity = PSKIdentity,
ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
premaster_secret = undefined,
- role = client,
key_algorithm = Alg} = State0, Connection)
when Alg == psk ->
case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of
@@ -875,12 +921,12 @@ certify(internal, #server_hello_done{},
client_certify_and_key_exchange(State, Connection)
end;
certify(internal, #server_hello_done{},
- #state{session = #session{master_secret = undefined},
+ #state{static_env = #static_env{role = client},
+ session = #session{master_secret = undefined},
ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
negotiated_version = {Major, Minor} = Version,
psk_identity = PSKIdentity,
premaster_secret = undefined,
- role = client,
key_algorithm = Alg} = State0, Connection)
when Alg == rsa_psk ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
@@ -896,11 +942,11 @@ certify(internal, #server_hello_done{},
end;
%% Master secret was determined with help of server-key exchange msg
certify(internal, #server_hello_done{},
- #state{session = #session{master_secret = MasterSecret} = Session,
+ #state{static_env = #static_env{role = client},
+ session = #session{master_secret = MasterSecret} = Session,
connection_states = ConnectionStates0,
negotiated_version = Version,
- premaster_secret = undefined,
- role = client} = State0, Connection) ->
+ premaster_secret = undefined} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
@@ -911,11 +957,11 @@ certify(internal, #server_hello_done{},
end;
%% Master secret is calculated from premaster_secret
certify(internal, #server_hello_done{},
- #state{session = Session0,
+ #state{static_env = #static_env{role = client},
+ session = Session0,
connection_states = ConnectionStates0,
negotiated_version = Version,
- premaster_secret = PremasterSecret,
- role = client} = State0, Connection) ->
+ premaster_secret = PremasterSecret} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
@@ -927,7 +973,7 @@ certify(internal, #server_hello_done{},
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
certify(internal = Type, #client_key_exchange{} = Msg,
- #state{role = server,
+ #state{static_env = #static_env{role = server},
client_certificate_requested = true,
ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State,
Connection) ->
@@ -957,19 +1003,19 @@ cipher(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
cipher(internal, #certificate_verify{signature = Signature,
hashsign_algorithm = CertHashSign},
- #state{role = server,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
key_algorithm = KexAlg,
public_key_info = PublicKeyInfo,
negotiated_version = Version,
- session = #session{master_secret = MasterSecret},
- tls_handshake_history = Handshake
+ session = #session{master_secret = MasterSecret}
} = State, Connection) ->
TLSVersion = ssl:tls_version(Version),
%% Use negotiated value if TLS-1.2 otherwhise return default
HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, TLSVersion),
case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
- TLSVersion, HashSign, MasterSecret, Handshake) of
+ TLSVersion, HashSign, MasterSecret, Hist) of
valid ->
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{cert_hashsign_algorithm = HashSign});
@@ -978,27 +1024,28 @@ cipher(internal, #certificate_verify{signature = Signature,
end;
%% client must send a next protocol message if we are expecting it
cipher(internal, #finished{},
- #state{role = server, expecting_next_protocol_negotiation = true,
+ #state{static_env = #static_env{role = server},
+ expecting_next_protocol_negotiation = true,
negotiated_protocol = undefined, negotiated_version = Version} = State0,
_Connection) ->
handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
cipher(internal, #finished{verify_data = Data} = Finished,
- #state{negotiated_version = Version,
- host = Host,
- port = Port,
- role = Role,
- expecting_finished = true,
+ #state{static_env = #static_env{role = Role,
+ host = Host,
+ port = Port},
+ negotiated_version = Version,
+ expecting_finished = true,
session = #session{master_secret = MasterSecret}
= Session0,
ssl_options = SslOpts,
connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ handshake_env = #handshake_env{tls_handshake_history = Hist}} = State, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
opposite_role(Role),
get_current_prf(ConnectionStates0, read),
- MasterSecret, Handshake0) of
+ MasterSecret, Hist) of
verified ->
- Session = register_session(Role, host_id(Role, Host, SslOpts), Port, Session0),
+ Session = handle_session(Role, SslOpts, Host, Port, Session0),
cipher_role(Role, Data, Session,
State#state{expecting_finished = false}, Connection);
#alert{} = Alert ->
@@ -1007,12 +1054,13 @@ cipher(internal, #finished{verify_data = Data} = Finished,
%% 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{role = server, expecting_next_protocol_negotiation = true,
- expecting_finished = true} = State, Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{expecting_next_protocol_negotiation = false,
- negotiated_protocol = SelectedProtocol
- });
+ #state{static_env = #static_env{role = server},
+ expecting_next_protocol_negotiation = true,
+ expecting_finished = true} = State0, Connection) ->
+ {Record, State} =
+ Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
+ Connection:next_event(?FUNCTION_NAME, Record,
+ State#state{expecting_next_protocol_negotiation = false});
cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} =
State, Connection) ->
ConnectionStates =
@@ -1029,15 +1077,18 @@ cipher(Type, Msg, State, Connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
connection({call, RecvFrom}, {recv, N, Timeout},
- #state{protocol_cb = Connection, socket_options =
- #socket_options{active = false}} = State0, Connection) ->
+ #state{static_env = #static_env{protocol_cb = Connection},
+ socket_options =
+ #socket_options{active = false}} = State0, Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
passive_receive(State0#state{bytes_to_read = N,
start_or_recv_from = RecvFrom,
timer = Timer}, ?FUNCTION_NAME, Connection);
-connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State,
+
+connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv} = State,
Connection) ->
- Connection:renegotiate(State#state{renegotiation = {true, From}}, []);
+ 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}}]);
@@ -1056,22 +1107,22 @@ connection({call, From}, negotiated_protocol,
[{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-connection(cast, {internal_renegotiate, WriteState}, #state{protocol_cb = Connection,
+connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv,
connection_states = ConnectionStates}
= State, Connection) ->
- Connection:renegotiate(State#state{renegotiation = {true, internal},
+ 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 = #ssl_options{erl_dist = true},
- erl_dist_data = ErlDistData,
socket_options = SockOpts} = State0, Connection) ->
process_flag(priority, normal),
State1 =
State0#state{
- socket_options =
- SockOpts#socket_options{active = true},
- erl_dist_data = ErlDistData#{dist_handle => DHandle}},
- {Record, State} = dist_app_data(<<>>, State1),
+ socket_options = SockOpts#socket_options{active = true},
+ 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);
@@ -1085,22 +1136,6 @@ connection(Type, Msg, State, Connection) ->
#state{}, tls_connection | dtls_connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
- #state{transport_cb = Transport, socket = Socket,
- downgrade = {Pid, From}} = State, _) ->
- tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
- Transport:controlling_process(Socket, Pid),
- gen_statem:reply(From, {ok, Socket}),
- stop(normal, State);
-downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) ->
- gen_statem:reply(From, {error, timeout}),
- stop(normal, State);
-downgrade(
- info, {CloseTag, Socket},
- #state{socket = Socket, close_tag = CloseTag, downgrade = {_, From}} =
- State, _) ->
- gen_statem:reply(From, {error, CloseTag}),
- stop(normal, State);
downgrade(Type, Event, State, Connection) ->
handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection).
@@ -1109,14 +1144,17 @@ downgrade(Type, Event, State, Connection) ->
%% common or unexpected events for the state.
%%--------------------------------------------------------------------
handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName,
- #state{role = client} = State, _) ->
+ #state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, _) ->
%% Should not be included in handshake history
- {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]};
-handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _)
+ {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};
+ keep_state_and_data;
handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
- #state{tls_handshake_history = Hs0} = State0,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv} = State0,
Connection) ->
PossibleSNI = Connection:select_sni_extension(Handshake),
@@ -1124,27 +1162,14 @@ handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
%% a client_hello, which needs to be determined by the connection callback.
%% In other cases this is a noop
State = handle_sni_extension(PossibleSNI, State0),
- HsHist = ssl_handshake:update_handshake_history(Hs0, iolist_to_binary(Raw)),
- {next_state, StateName, State#state{tls_handshake_history = HsHist},
+
+ Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
+ {next_state, StateName, State#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_common_event(internal, TLSorDTLSRecord, StateName, State);
+ Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
handle_common_event(timeout, hibernate, _, _, _) ->
{keep_state_and_data, [hibernate]};
-handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) ->
- case read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, State1} ->
- case Connection:next_event(StateName, Record, State1) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, []);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, Actions);
- {stop, _, _} = Stop ->
- Stop
- end
- end;
handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
#state{negotiated_version = Version} = State, _) ->
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version,
@@ -1157,47 +1182,37 @@ handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version}
handle_call({application_data, _Data}, _, _, _, _) ->
%% In renegotiation priorities handshake, send data when handshake is finished
{keep_state_and_data, [postpone]};
-handle_call({close, {Pid, Timeout}}, From, StateName, State0, Connection) when is_pid(Pid) ->
- %% terminate will send close alert to peer
- State = State0#state{downgrade = {Pid, From}},
- Connection:terminate(downgrade, StateName, State),
- %% 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.
- {next_state, downgrade, State#state{terminated = true}, [{timeout, Timeout, downgrade}]};
handle_call({close, _} = Close, From, StateName, 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{terminated = true});
+ {stop_and_reply,
+ {shutdown, normal},
+ {reply, From, Result}, State#state{terminated = true}};
handle_call({shutdown, read_write = How}, From, StateName,
- #state{transport_cb = Transport,
- socket = Socket} = State, _) ->
-
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket}} = State, _) ->
try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- StateName, State) of
+ StateName, State) of
_ ->
case Transport:shutdown(Socket, How) of
ok ->
{next_state, StateName, State#state{terminated = true}, [{reply, From, ok}]};
Error ->
- {stop, StateName, State#state{terminated = true}, [{reply, From, Error}]}
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State#state{terminated = true}}
end
catch
throw:Return ->
Return
end;
handle_call({shutdown, How0}, From, StateName,
- #state{transport_cb = Transport,
- socket = Socket} = State, _) ->
+ #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, StateName, State, [{reply, From, Error}]}
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
end;
handle_call({recv, _N, _Timeout}, From, _,
#state{socket_options =
@@ -1217,15 +1232,16 @@ handle_call({new_user, User}, From, StateName,
{next_state, StateName, State#state{user_application = {NewMon,User}},
[{reply, From, ok}]};
handle_call({get_opts, OptTags}, From, _,
- #state{socket = Socket,
- transport_cb = Transport,
+ #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{socket_options = Opts1,
- socket = Socket,
- transport_cb = Transport} = State0, Connection) ->
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ socket_options = Opts1
+ } = State0, Connection) ->
{Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
State = State0#state{socket_options = Opts},
handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
@@ -1266,22 +1282,25 @@ handle_call(_,_,_,_,_) ->
{keep_state_and_data, [postpone]}.
handle_info({ErrorTag, Socket, econnaborted}, StateName,
- #state{socket = Socket, transport_cb = Transport,
- protocol_cb = Connection,
- start_or_recv_from = StartFrom, role = Role,
- error_tag = ErrorTag,
- tracker = Tracker} = State) when StateName =/= connection ->
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ error_tag = ErrorTag,
+ tracker = Tracker,
+ protocol_cb = Connection},
+ start_or_recv_from = StartFrom
+ } = State) when StateName =/= connection ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker,Socket,
StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection),
- stop(normal, State);
+ {stop, {shutdown, normal}, State};
-handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
- error_tag = ErrorTag} = State) ->
+handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket,
+ error_tag = ErrorTag}} = State) ->
Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
error_logger:error_report(Report),
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- stop(normal, State);
+ {stop, {shutdown,normal}, State};
handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
#state{user_application = {MonitorRef, _Pid},
@@ -1289,7 +1308,7 @@ handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
{stop, {shutdown, Reason}};
handle_info({'DOWN', MonitorRef, _, _, _}, _,
#state{user_application = {MonitorRef, _Pid}}) ->
- {stop, normal};
+ {stop, {shutdown, normal}};
handle_info({'EXIT', Pid, _Reason}, StateName,
#state{user_application = {_MonitorRef, Pid}} = State) ->
%% It seems the user application has linked to us
@@ -1297,22 +1316,22 @@ handle_info({'EXIT', Pid, _Reason}, StateName,
{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{socket = Socket} = 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{socket = Socket} = State) ->
- stop({shutdown, Reason}, State);
+ {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) ->
{next_state, StateName, State#state{allow_renegotiate = true}};
handle_info({cancel_start_or_recv, StartFrom}, StateName,
- #state{renegotiation = {false, first}} = State) when StateName =/= connection ->
- stop_and_reply(
- {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}},
- State#state{timer = undefined});
+ #state{handshake_env = #handshake_env{renegotiation = {false, first}}} = State) when StateName =/= connection ->
+ {stop_and_reply,
+ {shutdown, user_timeout},
+ {reply, StartFrom, {error, timeout}},
+ State#state{timer = undefined}};
handle_info({cancel_start_or_recv, RecvFrom}, StateName,
#state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined ->
{next_state, StateName, State#state{start_or_recv_from = undefined,
@@ -1321,7 +1340,7 @@ handle_info({cancel_start_or_recv, RecvFrom}, StateName,
handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
{next_state, StateName, State#state{timer = undefined}};
-handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) ->
+handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) ->
Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]),
error_logger:info_report(Report),
{next_state, StateName, State}.
@@ -1338,14 +1357,15 @@ terminate(_, _, #state{terminated = true}) ->
%% before run by gen_statem which will end up here
ok;
terminate({shutdown, transport_closed} = Reason,
- _StateName, #state{protocol_cb = Connection,
- socket = Socket, transport_cb = Transport} = State) ->
+ _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{
- protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport} = 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) ->
@@ -1353,23 +1373,27 @@ terminate({shutdown, own_alert}, _StateName, #state{
_ ->
Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
end;
-terminate(downgrade = Reason, connection, #state{protocol_cb = Connection,
- transport_cb = Transport, socket = Socket
- } = State) ->
+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{protocol_cb = Connection,
+terminate(Reason, connection, #state{static_env = #static_env{
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
connection_states = ConnectionStates,
- ssl_options = #ssl_options{padding_check = Check},
- transport_cb = Transport, socket = Socket
+ ssl_options = #ssl_options{padding_check = Check}
} = State) ->
handle_trusted_certs_db(State),
Alert = terminate_alert(Reason),
%% Send the termination ALERT if possible
- catch (Connection:send_alert_in_connection(Alert, State)),
+ catch (ok = Connection:send_alert_in_connection(Alert, State)),
Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
-terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection,
- socket = Socket
+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).
@@ -1388,7 +1412,7 @@ format_status(terminate, [_, StateName, State]) ->
[{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
protocol_buffers = ?SECRET_PRINTOUT,
user_data_buffer = ?SECRET_PRINTOUT,
- tls_handshake_history = ?SECRET_PRINTOUT,
+ handshake_env = ?SECRET_PRINTOUT,
session = ?SECRET_PRINTOUT,
private_key = ?SECRET_PRINTOUT,
diffie_hellman_params = ?SECRET_PRINTOUT,
@@ -1403,15 +1427,15 @@ format_status(terminate, [_, StateName, State]) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-send_alert(Alert, connection, #state{protocol_cb = Connection} = State) ->
+send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
Connection:send_alert_in_connection(Alert, State);
-send_alert(Alert, _, #state{protocol_cb = Connection} = State) ->
+send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
Connection:send_alert(Alert, State).
-connection_info(#state{sni_hostname = SNIHostname,
- session = #session{session_id = SessionId,
+connection_info(#state{static_env = #static_env{protocol_cb = Connection},
+ sni_hostname = SNIHostname,
+ session = #session{session_id = SessionId,
cipher_suite = CipherSuite, ecc = ECCCurve},
- protocol_cb = Connection,
negotiated_version = {_,_} = Version,
ssl_options = Opts}) ->
RecordCB = record_cb(Connection),
@@ -1532,9 +1556,10 @@ handle_peer_cert_key(client, _,
handle_peer_cert_key(_, _, _, _, State) ->
State.
-certify_client(#state{client_certificate_requested = true, role = client,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+certify_client(#state{static_env = #static_env{role = client,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ client_certificate_requested = true,
session = #session{own_certificate = OwnCert}}
= State, Connection) ->
Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
@@ -1542,16 +1567,17 @@ certify_client(#state{client_certificate_requested = true, role = client,
certify_client(#state{client_certificate_requested = false} = State, _) ->
State.
-verify_client_cert(#state{client_certificate_requested = true, role = client,
+verify_client_cert(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
+ client_certificate_requested = true,
negotiated_version = Version,
private_key = PrivateKey,
session = #session{master_secret = MasterSecret,
own_certificate = OwnCert},
- cert_hashsign_algorithm = HashSign,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ cert_hashsign_algorithm = HashSign} = State, Connection) ->
case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
- ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of
+ ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
#certificate_verify{} = Verified ->
Connection:queue_handshake(Verified, State);
ignore ->
@@ -1587,7 +1613,9 @@ server_certify_and_key_exchange(State0, Connection) ->
request_client_cert(State2, Connection).
certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
- #state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) ->
+ #state{private_key = Key,
+ handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
+ = State, Connection) ->
FakeSecret = make_premaster_secret(Version, rsa),
%% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
%% and fail handshake later.RFC 5246 section 7.4.7.1.
@@ -1662,8 +1690,8 @@ certify_server(#state{key_algorithm = Algo} = State, _) when Algo == dh_anon;
Algo == ecdhe_psk;
Algo == srp_anon ->
State;
-certify_server(#state{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
session = #session{own_certificate = OwnCert}} = State, Connection) ->
case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
Cert = #certificate{} ->
@@ -1672,9 +1700,9 @@ certify_server(#state{cert_db = CertDbHandle,
throw(Alert)
end.
-key_exchange(#state{role = server, key_algorithm = rsa} = State,_) ->
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa} = State,_) ->
State;
-key_exchange(#state{role = server, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
hashsign_algorithm = HashSignAlgo,
diffie_hellman_params = #'DHParameter'{} = Params,
private_key = PrivateKey,
@@ -1695,12 +1723,14 @@ key_exchange(#state{role = server, key_algorithm = Algo,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
-key_exchange(#state{role = server, private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server},
+ private_key = #'ECPrivateKey'{parameters = ECCurve} = Key,
+ key_algorithm = Algo,
session = Session} = State, _)
when Algo == ecdh_ecdsa; Algo == ecdh_rsa ->
State#state{diffie_hellman_keys = Key,
session = Session#session{ecc = ECCurve}};
-key_exchange(#state{role = server, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
session = #session{ecc = ECCCurve},
@@ -1722,10 +1752,10 @@ key_exchange(#state{role = server, key_algorithm = Algo,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = ECDHKeys};
-key_exchange(#state{role = server, key_algorithm = psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk,
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{role = server, key_algorithm = psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
@@ -1742,7 +1772,7 @@ key_exchange(#state{role = server, key_algorithm = psk,
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = server, key_algorithm = dhe_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
diffie_hellman_params = #'DHParameter'{} = Params,
@@ -1763,7 +1793,7 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
-key_exchange(#state{role = server, key_algorithm = ecdhe_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecdhe_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
@@ -1784,10 +1814,10 @@ key_exchange(#state{role = server, key_algorithm = ecdhe_psk,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = ECDHKeys};
-key_exchange(#state{role = server, key_algorithm = rsa_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk,
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{role = server, key_algorithm = rsa_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
@@ -1804,7 +1834,7 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk,
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = server, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
ssl_options = #ssl_options{user_lookup_fun = LookupFun},
hashsign_algorithm = HashSignAlgo,
session = #session{srp_username = Username},
@@ -1834,14 +1864,14 @@ key_exchange(#state{role = server, key_algorithm = Algo,
State = Connection:queue_handshake(Msg, State0),
State#state{srp_params = SrpParams,
srp_keys = Keys};
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = rsa,
public_key_info = PublicKeyInfo,
negotiated_version = Version,
premaster_secret = PremasterSecret} = State0, Connection) ->
Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = Algorithm,
negotiated_version = Version,
diffie_hellman_keys = {DhPubKey, _}
@@ -1852,7 +1882,7 @@ key_exchange(#state{role = client,
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = Algorithm,
negotiated_version = Version,
session = Session,
@@ -1862,14 +1892,14 @@ key_exchange(#state{role = client,
Algorithm == ecdh_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = psk,
negotiated_version = Version} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{psk, SslOpts#ssl_options.psk_identity}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = dhe_psk,
negotiated_version = Version,
@@ -1879,7 +1909,7 @@ key_exchange(#state{role = client,
SslOpts#ssl_options.psk_identity, DhPubKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = ecdhe_psk,
negotiated_version = Version,
@@ -1889,7 +1919,7 @@ key_exchange(#state{role = client,
SslOpts#ssl_options.psk_identity, ECDHKeys}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = rsa_psk,
public_key_info = PublicKeyInfo,
@@ -1899,7 +1929,7 @@ key_exchange(#state{role = client,
Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity,
PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = Algorithm,
negotiated_version = Version,
srp_keys = {ClientPubKey, _}}
@@ -1949,11 +1979,11 @@ request_client_cert(#state{key_algorithm = Alg} = State, _)
Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
State;
-request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer,
- signature_algs = SupportedHashSigns},
- connection_states = ConnectionStates0,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ ssl_options = #ssl_options{verify = verify_peer,
+ signature_algs = SupportedHashSigns},
+ connection_states = ConnectionStates0,
negotiated_version = Version} = State0, Connection) ->
#{security_parameters :=
#security_parameters{cipher_suite = CipherSuite}} =
@@ -1998,7 +2028,7 @@ finalize_handshake(State0, StateName, Connection) ->
State = next_protocol(State2, Connection),
finished(State, StateName, Connection).
-next_protocol(#state{role = server} = State, _) ->
+next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
State;
next_protocol(#state{negotiated_protocol = undefined} = State, _) ->
State;
@@ -2011,14 +2041,16 @@ next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) -
cipher_protocol(State, Connection) ->
Connection:queue_change_cipher(#change_cipher_spec{}, State).
-finished(#state{role = Role, negotiated_version = Version,
+finished(#state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
+ negotiated_version = Version,
session = Session,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State0, StateName, Connection) ->
+ connection_states = ConnectionStates0} = State0,
+ StateName, Connection) ->
MasterSecret = Session#session.master_secret,
Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
get_current_prf(ConnectionStates0, write),
- MasterSecret, Handshake0),
+ MasterSecret, Hist),
ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
Connection:send_handshake(Finished, State0#state{connection_states =
ConnectionStates}).
@@ -2090,8 +2122,9 @@ calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKe
master_secret(#alert{} = Alert, _) ->
Alert;
-master_secret(PremasterSecret, #state{session = Session,
- negotiated_version = Version, role = Role,
+master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
+ session = Session,
+ negotiated_version = Version,
connection_states = ConnectionStates0} = State) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, Role) of
@@ -2325,18 +2358,18 @@ handle_trusted_certs_db(#state{ssl_options =
#ssl_options{cacertfile = <<>>, cacerts = []}}) ->
%% No trusted certs specified
ok;
-handle_trusted_certs_db(#state{cert_db_ref = Ref,
- cert_db = CertDb,
- ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined ->
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ cert_db = CertDb},
+ ssl_options = #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{file_ref_db = undefined}) ->
+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{cert_db_ref = Ref,
- file_ref_db = RefDb,
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ file_ref_db = RefDb},
ssl_options = #ssl_options{cacertfile = File}}) ->
case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
0 ->
@@ -2345,7 +2378,7 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref,
ok
end.
-prepare_connection(#state{renegotiation = Renegotiate,
+prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
start_or_recv_from = RecvFrom} = State0, Connection)
when Renegotiate =/= {false, first},
RecvFrom =/= undefined ->
@@ -2355,18 +2388,18 @@ prepare_connection(State0, Connection) ->
State = Connection:reinit(State0),
{no_record, ack_connection(State)}.
-ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == peer;
- Initiater == internal ->
- State#state{renegotiation = undefined};
-ack_connection(#state{renegotiation = {true, From}} = 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{renegotiation = undefined};
-ack_connection(#state{renegotiation = {false, first},
+ 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,
timer = Timer} = State) when StartFrom =/= undefined ->
gen_statem:reply(StartFrom, connected),
cancel_timer(Timer),
- State#state{renegotiation = undefined,
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
start_or_recv_from = undefined, timer = undefined};
ack_connection(State) ->
State.
@@ -2382,15 +2415,35 @@ session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
session_handle_params(_, Session) ->
Session.
-register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
+handle_session(Role = server, #ssl_options{reuse_sessions = true} = SslOpts,
+ Host, Port, Session0) ->
+ register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true);
+handle_session(Role = client, #ssl_options{verify = verify_peer,
+ reuse_sessions = Reuse} = SslOpts,
+ Host, Port, Session0) when Reuse =/= false ->
+ register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, reg_type(Reuse));
+handle_session(server, _, Host, Port, Session) ->
+ %% Remove "session of type new" entry from session DB
+ ssl_manager:invalidate_session(Host, Port, Session),
+ Session;
+handle_session(client, _,_,_, Session) ->
+ %% In client case there is no entry yet, so nothing to remove
+ Session.
+
+reg_type(save) ->
+ true;
+reg_type(true) ->
+ unique.
+
+register_session(client, Host, Port, #session{is_resumable = new} = Session0, Save) ->
Session = Session0#session{is_resumable = true},
- ssl_manager:register_session(Host, Port, Session),
+ ssl_manager:register_session(Host, Port, Session, Save),
Session;
-register_session(server, _, Port, #session{is_resumable = new} = Session0) ->
+register_session(server, _, Port, #session{is_resumable = new} = Session0, _) ->
Session = Session0#session{is_resumable = true},
ssl_manager:register_session(Port, Session),
Session;
-register_session(_, _, _, Session) ->
+register_session(_, _, _, Session, _) ->
Session. %% Already registered
host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) ->
@@ -2399,19 +2452,22 @@ host_id(_, Host, _) ->
Host.
handle_new_session(NewId, CipherSuite, Compression,
- #state{session = Session0,
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{protocol_cb = Connection},
+ session = Session0
+ } = State0) ->
Session = Session0#session{session_id = NewId,
cipher_suite = CipherSuite,
compression_method = Compression},
Connection:next_event(certify, no_record, State0#state{session = Session}).
-handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
- negotiated_version = Version,
- host = Host, port = Port,
- protocol_cb = Connection,
- session_cache = Cache,
- session_cache_cb = CacheCb} = State) ->
+handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
+ port = Port,
+ protocol_cb = Connection,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State) ->
Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
@@ -2468,8 +2524,8 @@ ssl_options_list([Key | Keys], [Value | Values], Acc) ->
handle_active_option(false, connection = StateName, To, Reply, State) ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
-handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection,
- user_data_buffer = <<>>} = State0) ->
+handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
+ user_data_buffer = <<>>} = State0) ->
case Connection:next_event(StateName0, no_record, State0) of
{next_state, StateName, State} ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
@@ -2483,7 +2539,8 @@ handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} =
{next_state, StateName, State, [{reply, To, Reply}]};
%% user_data_buffer =/= <<>>
-handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) ->
+handle_active_option(_, StateName0, To, Reply,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
case read_application_data(<<>>, State0) of
{stop, _, _} = Stop ->
Stop;
@@ -2545,21 +2602,28 @@ decode_packet(Type, Buffer, PacketOpts) ->
%% 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, SOpts = #socket_options{active=Active, packet=Type},
- Data, Pid, From, Tracker, Connection) ->
- send_or_reply(Active, Pid, From,
- format_reply(CPids, Transport, Socket, SOpts, Data, Tracker, 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,
+deliver_app_data(
+ CPids, Transport, Socket,
+ #socket_options{active=Active, packet=Type} = SOpts,
+ Data, Pid, From, Tracker, Connection) ->
+ %%
+ send_or_reply(
+ Active, Pid, From,
+ format_reply(
+ CPids, Transport, Socket, SOpts, Data, Tracker, 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};
@@ -2664,7 +2728,7 @@ invalidate_session(server, _, Port, Session) ->
handle_sni_extension(undefined, State) ->
State;
-handle_sni_extension(#sni{hostname = Hostname}, State0) ->
+handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0} = State0) ->
NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
case NewOptions of
undefined ->
@@ -2678,19 +2742,21 @@ handle_sni_extension(#sni{hostname = Hostname}, State0) ->
private_key := Key,
dh_params := DHParams,
own_certificate := OwnCert}} =
- ssl_config:init(NewOptions, State0#state.role),
+ ssl_config:init(NewOptions, Role),
State0#state{
session = State0#state.session#session{own_certificate = OwnCert},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
private_key = Key,
- diffie_hellman_params = DHParams,
- ssl_options = NewOptions,
- sni_hostname = Hostname
- }
+ diffie_hellman_params = DHParams,
+ ssl_options = NewOptions,
+ sni_hostname = Hostname
+ }
end.
update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) ->
@@ -2714,13 +2780,10 @@ new_emulated([], EmOpts) ->
new_emulated(NewEmOpts, _) ->
NewEmOpts.
-stop(Reason, State) ->
- {stop, Reason, State}.
-
-stop_and_reply(Reason, Replies, State) ->
- {stop_and_reply, Reason, Replies, State}.
-
-is_dist_up(#{dist_handle := Handle}) when Handle =/= undefined ->
- true;
-is_dist_up(_) ->
- false.
+-compile({inline, [bincat/2]}).
+bincat(<<>>, B) ->
+ B;
+bincat(A, <<>>) ->
+ A;
+bincat(A, B) ->
+ <<A/binary, B/binary>>.
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 66e3182313..a46407b27e 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -33,71 +33,85 @@
-include("ssl_cipher.hrl").
-include_lib("public_key/include/public_key.hrl").
+-record(static_env, {
+ role :: client | server,
+ transport_cb :: atom(), % callback module
+ protocol_cb :: tls_connection | dtls_connection,
+ data_tag :: atom(), % ex tcp.
+ close_tag :: atom(), % ex tcp_closed
+ error_tag :: atom(), % ex tcp_error
+ host :: string() | inet:ip_address(),
+ port :: integer(),
+ socket :: port() | tuple(), %% TODO: dtls socket
+ cert_db :: reference() | 'undefined',
+ session_cache :: db_handle(),
+ session_cache_cb :: atom(),
+ crl_db :: term(),
+ file_ref_db :: db_handle(),
+ cert_db_ref :: certdb_ref() | 'undefined',
+ tracker :: pid() | 'undefined' %% Tracker process for listen socket
+ }).
+
+-record(handshake_env, {
+ client_hello_version :: ssl_record:ssl_version() | 'undefined',
+ unprocessed_handshake_events = 0 :: integer(),
+ tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
+ | 'undefined',
+ renegotiation :: undefined | {boolean(), From::term() | internal | peer}
+ }).
+
-record(state, {
- role :: client | server,
- user_application :: {Monitor::reference(), User::pid()},
- transport_cb :: atom(), % callback module
- protocol_cb :: tls_connection | dtls_connection,
- data_tag :: atom(), % ex tcp.
- close_tag :: atom(), % ex tcp_closed
- error_tag :: atom(), % ex tcp_error
- host :: string() | inet:ip_address(),
- port :: integer(),
- socket :: port() | tuple(), %% TODO: dtls socket
- sender :: pid() | undefined,
- ssl_options :: #ssl_options{},
- socket_options :: #socket_options{},
- connection_states :: ssl_record:connection_states() | secret_printout(),
- protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl
- unprocessed_handshake_events = 0 :: integer(),
- tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
- | 'undefined',
- cert_db :: reference() | 'undefined',
- session :: #session{} | secret_printout(),
- session_cache :: db_handle(),
- session_cache_cb :: atom(),
- crl_db :: term(),
- negotiated_version :: ssl_record:ssl_version() | 'undefined',
- client_hello_version :: ssl_record:ssl_version() | 'undefined',
- client_certificate_requested = false :: boolean(),
- key_algorithm :: ssl_cipher_format:key_algo(),
- hashsign_algorithm = {undefined, undefined},
- cert_hashsign_algorithm = {undefined, undefined},
- public_key_info :: ssl_handshake:public_key_info() | 'undefined',
- private_key :: public_key:private_key() | secret_printout() | 'undefined',
- diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
- diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
- psk_identity :: binary() | 'undefined', % server psk identity hint
- srp_params :: #srp_user{} | secret_printout() | 'undefined',
- srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined',
- premaster_secret :: binary() | secret_printout() | 'undefined',
- file_ref_db :: db_handle(),
- cert_db_ref :: certdb_ref() | 'undefined',
- bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
- user_data_buffer :: undefined | binary() | secret_printout(),
- erl_dist_data = #{} :: map(),
- renegotiation :: undefined | {boolean(), From::term() | internal | peer},
- start_or_recv_from :: term(),
- timer :: undefined | reference(), % start_or_recive_timer
- %%send_queue :: queue:queue(),
- hello, %%:: #client_hello{} | #server_hello{},
- terminated = false ::boolean(),
- allow_renegotiate = true ::boolean(),
- expecting_next_protocol_negotiation = false ::boolean(),
- expecting_finished = false ::boolean(),
- next_protocol = undefined :: undefined | binary(),
- negotiated_protocol,
- tracker :: pid() | 'undefined', %% Tracker process for listen socket
- sni_hostname = undefined,
- downgrade,
- flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake
- %% to when possible pack more than one TLS record into the
- %% underlaying packet format. Introduced by DTLS - RFC 4347.
- %% The mecahnism is also usefull in TLS although we do not
- %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
- flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
- protocol_specific = #{} :: map()
- }).
+ static_env :: #static_env{},
+ handshake_env :: #handshake_env{} | secret_printout(),
+ %% Change seldome
+ user_application :: {Monitor::reference(), User::pid()},
+ ssl_options :: #ssl_options{},
+ socket_options :: #socket_options{},
+ session :: #session{} | secret_printout(),
+ allow_renegotiate = true ::boolean(),
+ terminated = false ::boolean() | closed,
+ negotiated_version :: ssl_record:ssl_version() | 'undefined',
+ bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
+ downgrade,
+
+ %% Changed often
+ connection_states :: ssl_record:connection_states() | secret_printout(),
+ protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr
+ user_data_buffer :: undefined | binary() | secret_printout(),
+
+ %% Used only in HS
+
+ client_certificate_requested = false :: boolean(),
+ key_algorithm :: ssl:key_algo(),
+ hashsign_algorithm = {undefined, undefined},
+ cert_hashsign_algorithm = {undefined, undefined},
+ public_key_info :: ssl_handshake:public_key_info() | 'undefined',
+ private_key :: public_key:private_key() | secret_printout() | 'undefined',
+ diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
+ diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
+ psk_identity :: binary() | 'undefined', % server psk identity hint
+ srp_params :: #srp_user{} | secret_printout() | 'undefined',
+ srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined',
+ premaster_secret :: binary() | secret_printout() | 'undefined',
+ start_or_recv_from :: term(),
+ timer :: undefined | reference(), % start_or_recive_timer
+ hello, %%:: #client_hello{} | #server_hello{},
+ expecting_next_protocol_negotiation = false ::boolean(),
+ expecting_finished = false ::boolean(),
+ next_protocol = undefined :: undefined | binary(),
+ negotiated_protocol,
+ sni_hostname = undefined,
+ flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake
+ %% to when possible pack more than one TLS record into the
+ %% underlaying packet format. Introduced by DTLS - RFC 4347.
+ %% The mecahnism is also usefull in TLS although we do not
+ %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
+ flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
+ erl_dist_handle = undefined :: erlang:dist_handle() | undefined,
+ protocol_specific = #{} :: map()
+ }).
+
+
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
#'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME,
base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}).
diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl
index 9c1af86eeb..841620ce57 100644
--- a/lib/ssl/src/ssl_crl_cache.erl
+++ b/lib/ssl/src/ssl_crl_cache.erl
@@ -28,6 +28,10 @@
-behaviour(ssl_crl_cache_api).
+-export_type([crl_src/0, uri/0]).
+-type crl_src() :: {file, file:filename()} | {der, public_key:der_encoded()}.
+-type uri() :: uri_string:uri_string().
+
-export([lookup/3, select/2, fresh_crl/2]).
-export([insert/1, insert/2, delete/1]).
diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl
index d5380583e7..8a750b3929 100644
--- a/lib/ssl/src/ssl_crl_cache_api.erl
+++ b/lib/ssl/src/ssl_crl_cache_api.erl
@@ -21,12 +21,15 @@
%%
-module(ssl_crl_cache_api).
-
-include_lib("public_key/include/public_key.hrl").
--type db_handle() :: term().
--type issuer_name() :: {rdnSequence, [#'AttributeTypeAndValue'{}]}.
+-export_type([dist_point/0, crl_cache_ref/0]).
+
+-type crl_cache_ref() :: any().
+-type issuer_name() :: {rdnSequence,[#'AttributeTypeAndValue'{}]}.
+-type dist_point() :: #'DistributionPoint'{}.
--callback lookup(#'DistributionPoint'{}, issuer_name(), db_handle()) -> not_available | [public_key:der_encoded()].
--callback select(issuer_name(), db_handle()) -> [public_key:der_encoded()].
--callback fresh_crl(#'DistributionPoint'{}, public_key:der_encoded()) -> public_key:der_encoded().
+
+-callback lookup(dist_point(), issuer_name(), crl_cache_ref()) -> not_available | [public_key:der_encoded()].
+-callback select(issuer_name(), crl_cache_ref()) -> [public_key:der_encoded()].
+-callback fresh_crl(dist_point(), public_key:der_encoded()) -> public_key:der_encoded().
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 14df1d2e02..27c071d6dd 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -610,7 +610,7 @@ encode_hello_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats
?UINT16(Len), ?BYTE(ListLen), ECPointFormatList/binary, Acc/binary>>);
encode_hello_extensions([#srp{username = UserName} | Rest], Acc) ->
SRPLen = byte_size(UserName),
- Len = SRPLen + 2,
+ Len = SRPLen + 1,
encode_hello_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
UserName/binary, Acc/binary>>);
encode_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) ->
@@ -729,7 +729,7 @@ decode_hello_extensions(Extensions) ->
dec_hello_extensions(Extensions, #hello_extensions{}).
%%--------------------------------------------------------------------
--spec decode_server_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) ->
+-spec decode_server_key(binary(), ssl:key_algo(), ssl_record:ssl_version()) ->
#server_key_params{}.
%%
%% Description: Decode server_key data and return appropriate type
@@ -738,7 +738,7 @@ decode_server_key(ServerKey, Type, Version) ->
dec_server_key(ServerKey, key_exchange_alg(Type), Version).
%%--------------------------------------------------------------------
--spec decode_client_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) ->
+-spec decode_client_key(binary(), ssl:key_algo(), ssl_record:ssl_version()) ->
#encrypted_premaster_secret{}
| #client_diffie_hellman_public{}
| #client_ec_diffie_hellman_public{}
@@ -972,34 +972,30 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
#session{cipher_suite = NegotiatedCipherSuite,
compression_method = Compression} = Session0,
ConnectionStates0, Renegotiation) ->
- Session = handle_srp_extension(SRP, Session0),
- ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info,
- Random, NegotiatedCipherSuite,
+ Session = handle_srp_extension(SRP, Session0),
+ ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info,
+ Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
- ConnectionStates0, Renegotiation, SecureRenegotation),
-
- ServerHelloExtensions = #hello_extensions{
- renegotiation_info = renegotiation_info(RecordCB, server,
- ConnectionStates, Renegotiation),
- ec_point_formats = server_ecc_extension(Version, ECCFormat)
- },
-
+ ConnectionStates0, Renegotiation, SecureRenegotation),
+
+ ServerHelloExtensions = #hello_extensions{
+ renegotiation_info = renegotiation_info(RecordCB, server,
+ ConnectionStates, Renegotiation),
+ ec_point_formats = server_ecc_extension(Version, ECCFormat)
+ },
+
%% If we receive an ALPN extension and have ALPN configured for this connection,
%% we handle it. Otherwise we check for the NPN extension.
if
ALPN =/= undefined, ALPNPreferredProtocols =/= undefined ->
- case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {Session, ConnectionStates, Protocol,
- ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}}
- end;
+ Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)),
+ {Session, ConnectionStates, Protocol,
+ ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}};
true ->
- ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
+ ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
{Session, ConnectionStates, undefined,
- ServerHelloExtensions#hello_extensions{next_protocol_negotiation=
- encode_protocols_advertised_on_server(ProtocolsToAdvertise)}}
+ ServerHelloExtensions#hello_extensions{next_protocol_negotiation=
+ encode_protocols_advertised_on_server(ProtocolsToAdvertise)}}
end.
handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
@@ -1022,12 +1018,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
[Protocol] when not Renegotiation ->
{ConnectionStates, alpn, Protocol};
undefined ->
- case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {ConnectionStates, npn, Protocol}
- end;
+ Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
+ {ConnectionStates, npn, Protocol};
{error, Reason} ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
[] ->
@@ -1941,7 +1933,7 @@ dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binar
RenegotiateInfo}});
dec_hello_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), SRP:SRPLen/binary, Rest/binary>>, Acc)
- when Len == SRPLen + 2 ->
+ when Len == SRPLen + 1 ->
dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}});
dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
@@ -2189,30 +2181,26 @@ filter_unavailable_ecc_suites(_, Suites) ->
handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
ConnectionStates0, Renegotiation, SecureRenegotation) ->
- case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
- Renegotiation, SecureRenegotation,
- ClientCipherSuites) of
- {ok, ConnectionStates} ->
- hello_pending_connection_states(RecordCB, Role,
- Version,
- NegotiatedCipherSuite,
- Random,
- Compression,
- ConnectionStates);
- #alert{} = Alert ->
- throw(Alert)
- end.
+ {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation,
+ ClientCipherSuites),
+ hello_pending_connection_states(RecordCB, Role,
+ Version,
+ NegotiatedCipherSuite,
+ Random,
+ Compression,
+ ConnectionStates).
%% Receive protocols, choose one from the list, return it.
handle_alpn_extension(_, {error, Reason}) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason));
handle_alpn_extension([], _) ->
- ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL);
+ throw(?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL));
handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) ->
- case lists:member(ServerProtocol, ClientProtocols) of
- true -> ServerProtocol;
- false -> handle_alpn_extension(Tail, ClientProtocols)
- end.
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true -> ServerProtocol;
+ false -> handle_alpn_extension(Tail, ClientProtocols)
+ end.
handle_next_protocol(undefined,
_NextProtocolSelector, _Renegotiating) ->
@@ -2225,14 +2213,14 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols,
true ->
select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector);
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension))
end.
handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)->
case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of
#alert{} = Alert ->
- Alert;
+ throw(Alert);
ProtocolsToAdvertise ->
ProtocolsToAdvertise
end.
@@ -2428,14 +2416,14 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation))
end;
handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
ConnectionStates, true, _, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
false ->
ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
Data = maps:get(client_verify_data, ConnectionState),
@@ -2443,7 +2431,7 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation))
end
end;
@@ -2453,7 +2441,7 @@ handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, S
handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
false ->
handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation)
end.
@@ -2462,9 +2450,9 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of
{_, true} ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure);
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure));
{true, false} ->
- ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION);
+ throw(?ALERT_REC(?FATAL, ?NO_RENEGOTIATION));
{false, false} ->
{ok, ConnectionStates}
end.
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 63e751440a..57c72aa122 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -31,8 +31,6 @@
-type reply() :: term().
-type msg() :: term().
-type from() :: term().
--type host() :: inet:ip_address() | inet:hostname().
--type session_id() :: 0 | binary().
-type certdb_ref() :: reference().
-type db_handle() :: term().
-type der_cert() :: binary().
@@ -111,10 +109,10 @@
%% Local policy for the server if it want's to reuse the session
%% or not. Defaluts to allways returning true.
%% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean()
- reuse_session,
+ reuse_session :: fun() | binary() | undefined, %% Server side is a fun()
%% If false sessions will never be reused, if true they
%% will be reused if possible.
- reuse_sessions :: boolean(),
+ reuse_sessions :: boolean() | save, %% Only client side can use value save
renegotiate_at,
secure_renegotiate,
client_renegotiation,
@@ -148,6 +146,8 @@
max_handshake_size :: integer(),
handshake,
customize_hostname_check
+ %% ,
+ %% save_session :: boolean()
}).
-record(socket_options,
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 4b735b2400..c56675b691 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -30,7 +30,7 @@
connection_init/3, cache_pem_file/2,
lookup_trusted_cert/4,
new_session_id/1, clean_cert_db/2,
- register_session/2, register_session/3, invalidate_session/2,
+ register_session/2, register_session/4, invalidate_session/2,
insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2,
invalidate_session/3, name/1]).
@@ -42,6 +42,8 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
+
-include_lib("kernel/include/file.hrl").
-record(state, {
@@ -148,7 +150,7 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) ->
ssl_pkix_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer).
%%--------------------------------------------------------------------
--spec new_session_id(integer()) -> session_id().
+-spec new_session_id(integer()) -> ssl:session_id().
%%
%% Description: Creates a session id for the server.
%%--------------------------------------------------------------------
@@ -170,9 +172,11 @@ clean_cert_db(Ref, File) ->
%%
%% Description: Make the session available for reuse.
%%--------------------------------------------------------------------
--spec register_session(host(), inet:port_number(), #session{}) -> ok.
-register_session(Host, Port, Session) ->
- cast({register_session, Host, Port, Session}).
+-spec register_session(ssl:host(), inet:port_number(), #session{}, unique | true) -> ok.
+register_session(Host, Port, Session, true) ->
+ call({register_session, Host, Port, Session});
+register_session(Host, Port, Session, unique = Save) ->
+ cast({register_session, Host, Port, Session, Save}).
-spec register_session(inet:port_number(), #session{}) -> ok.
register_session(Port, Session) ->
@@ -183,7 +187,7 @@ register_session(Port, Session) ->
%% a the session has been marked "is_resumable = false" for some while
%% it will be safe to remove the data from the session database.
%%--------------------------------------------------------------------
--spec invalidate_session(host(), inet:port_number(), #session{}) -> ok.
+-spec invalidate_session(ssl:host(), inet:port_number(), #session{}) -> ok.
invalidate_session(Host, Port, Session) ->
load_mitigation(),
cast({invalidate_session, Host, Port, Session}).
@@ -301,7 +305,10 @@ handle_call({{new_session_id, Port}, _},
_, #state{session_cache_cb = CacheCb,
session_cache_server = Cache} = State) ->
Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb),
- {reply, Id, State}.
+ {reply, Id, State};
+handle_call({{register_session, Host, Port, Session},_}, _, State0) ->
+ State = client_register_session(Host, Port, Session, State0),
+ {reply, ok, State}.
%%--------------------------------------------------------------------
-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}.
@@ -311,8 +318,12 @@ handle_call({{new_session_id, Port}, _},
%%
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast({register_session, Host, Port, Session}, State0) ->
- State = ssl_client_register_session(Host, Port, Session, State0),
+handle_cast({register_session, Host, Port, Session, unique}, State0) ->
+ State = client_register_unique_session(Host, Port, Session, State0),
+ {noreply, State};
+
+handle_cast({register_session, Host, Port, Session, true}, State0) ->
+ State = client_register_session(Host, Port, Session, State0),
{noreply, State};
handle_cast({register_session, Port, Session}, State0) ->
@@ -540,10 +551,10 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) ->
ok
end.
-ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
- session_cache_cb = CacheCb,
- session_cache_client_max = Max,
- session_client_invalidator = Pid0} = State) ->
+client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = State) ->
TimeStamp = erlang:monotonic_time(),
NewSession = Session#session{time_stamp = TimeStamp},
@@ -557,6 +568,17 @@ ssl_client_register_session(Host, Port, Session, #state{session_cache_client = C
register_unique_session(Sessions, NewSession, {Host, Port}, State)
end.
+client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = State) ->
+ TimeStamp = erlang:monotonic_time(),
+ NewSession = Session#session{time_stamp = TimeStamp},
+ Pid = do_register_session({{Host, Port},
+ NewSession#session.session_id},
+ NewSession, Max, Pid0, Cache, CacheCb),
+ State#state{session_client_invalidator = Pid}.
+
server_register_session(Port, Session, #state{session_cache_server_max = Max,
session_cache_server = Cache,
session_cache_cb = CacheCb,
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index c9607489e9..44305c65fe 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -27,6 +27,7 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
%% Internal application API
-export([is_new/2, client_id/4, server_id/6, valid_session/2]).
@@ -34,7 +35,7 @@
-type seconds() :: integer().
%%--------------------------------------------------------------------
--spec is_new(session_id(), session_id()) -> boolean().
+-spec is_new(ssl:session_id(), ssl:session_id()) -> boolean().
%%
%% Description: Checks if the session id decided by the server is a
%% new or resumed sesion id.
@@ -47,12 +48,19 @@ is_new(_ClientSuggestion, _ServerDecision) ->
true.
%%--------------------------------------------------------------------
--spec client_id({host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(),
+-spec client_id({ssl:host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(),
undefined | binary()) -> binary().
%%
%% Description: Should be called by the client side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
+client_id({Host, Port, #ssl_options{reuse_session = SessionId}}, Cache, CacheCb, _) when is_binary(SessionId)->
+ case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
+ undefined ->
+ <<>>;
+ #session{} ->
+ SessionId
+ end;
client_id(ClientInfo, Cache, CacheCb, OwnCert) ->
case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
no_session ->
@@ -91,7 +99,8 @@ server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-select_session({_, _, #ssl_options{reuse_sessions=false}}, _Cache, _CacheCb, _OwnCert) ->
+select_session({_, _, #ssl_options{reuse_sessions = Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true ->
+ %% If reuse_sessions == true | save a new session should be created
no_session;
select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
@@ -132,7 +141,7 @@ is_resumable(SuggestedSessionId, Port, #ssl_options{reuse_session = ReuseFun} =
false -> {false, undefined}
end;
undefined ->
- {false, undefined}
+ {false, undefined}
end.
resumable(new) ->
diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl
index b68c75a09b..5f96f905b1 100644
--- a/lib/ssl/src/ssl_session_cache_api.erl
+++ b/lib/ssl/src/ssl_session_cache_api.erl
@@ -23,14 +23,20 @@
-module(ssl_session_cache_api).
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
--type key() :: {{host(), inet:port_number()}, session_id()} | {inet:port_number(), session_id()}.
+-export_type([session_cache_key/0, session/0, partial_key/0, session_cache_ref/0]).
--callback init(list()) -> db_handle().
--callback terminate(db_handle()) -> any().
--callback lookup(db_handle(), key()) -> #session{} | undefined.
--callback update(db_handle(), key(), #session{}) -> any().
--callback delete(db_handle(), key()) -> any().
--callback foldl(fun(), term(), db_handle()) -> term().
--callback select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}].
--callback size(db_handle()) -> integer().
+-type session_cache_ref() :: any().
+-type session_cache_key() :: {partial_key(), ssl:session_id()}.
+-opaque session() :: #session{}.
+-opaque partial_key() :: {ssl:host(), inet:port_number()} | inet:port_number().
+
+-callback init(list()) -> session_cache_ref().
+-callback terminate(session_cache_ref()) -> any().
+-callback lookup(session_cache_ref(), session_cache_key()) -> #session{} | undefined.
+-callback update(session_cache_ref(), session_cache_key(), #session{}) -> any().
+-callback delete(session_cache_ref(), session_cache_key()) -> any().
+-callback foldl(fun(), term(), session_cache_ref()) -> term().
+-callback select_session(session_cache_ref(), {ssl:host(), inet:port_number()} | inet:port_number()) -> [#session{}].
+-callback size(session_cache_ref()) -> integer().
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 0f986b5e21..cee69a05a5 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -17,7 +17,6 @@
%%
%% %CopyrightEnd%
%%
-
%%
%%----------------------------------------------------------------------
%% Purpose: Handles an ssl connection, e.i. both the setup
@@ -47,7 +46,7 @@
%% State transition handling
-export([next_event/3, next_event/4,
- handle_common_event/4]).
+ handle_protocol_record/3]).
%% Handshake handling
-export([renegotiation/2, renegotiate/2, send_handshake/2,
@@ -109,7 +108,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} =
end.
%%--------------------------------------------------------------------
--spec start_link(atom(), pid(), host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+-spec start_link(atom(), pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
{ok, pid()} | ignore | {error, reason()}.
%%
%% Description: Creates a gen_statem process which calls Module:init/1 to
@@ -144,28 +143,31 @@ pids(#state{protocol_specific = #{sender := Sender}}) ->
%%====================================================================
%% State transition handling
%%====================================================================
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
next_record(#state{protocol_buffers =
- #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]}
- = Buffers,
- connection_states = ConnStates0,
- ssl_options = #ssl_options{padding_check = Check}} = State) ->
- case tls_record:decode_cipher_text(CT, ConnStates0, Check) of
- {Plain, ConnStates} ->
- {Plain, State#state{protocol_buffers =
- Buffers#protocol_buffers{tls_cipher_texts = Rest},
- connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end;
+ #protocol_buffers{tls_packets = [], tls_cipher_texts = [#ssl_tls{type = Type}| _] = CipherTexts0}
+ = Buffers,
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{padding_check = Check}} = State) ->
+ case decode_cipher_texts(Type, CipherTexts0, ConnectionStates0, Check, <<>>) of
+ {#ssl_tls{} = Record, ConnectionStates, CipherTexts} ->
+ {Record, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
+ connection_states = ConnectionStates}};
+ {#alert{} = Alert, ConnectionStates, CipherTexts} ->
+ {Alert, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
+ connection_states = ConnectionStates}}
+ end;
next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []},
protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
- socket = Socket,
- close_tag = CloseTag,
- transport_cb = Transport} = State) ->
- case tls_socket:setopts(Transport, Socket, [{active, N}]) of
+ static_env = #static_env{socket = Socket,
+ close_tag = CloseTag,
+ transport_cb = Transport}
+ } = State) ->
+ case tls_socket:setopts(Transport, Socket, [{active, N}]) of
ok ->
{no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
_ ->
@@ -196,11 +198,34 @@ next_event(StateName, Record, State, Actions) ->
{next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end.
-handle_common_event(internal, #alert{} = Alert, StateName,
- #state{negotiated_version = Version} = State) ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State);
+decode_cipher_texts(Type, [] = CipherTexts, ConnectionStates, _, Acc) ->
+ {#ssl_tls{type = Type, fragment = Acc}, ConnectionStates, CipherTexts};
+decode_cipher_texts(Type,
+ [#ssl_tls{type = Type} = CT | CipherTexts], ConnectionStates0, Check, Acc) ->
+ case tls_record:decode_cipher_text(CT, ConnectionStates0, Check) of
+ {#ssl_tls{type = ?APPLICATION_DATA, fragment = Plain}, ConnectionStates} ->
+ decode_cipher_texts(Type, CipherTexts,
+ ConnectionStates, Check, <<Acc/binary, Plain/binary>>);
+ {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates} ->
+ {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates, CipherTexts};
+ #alert{} = Alert ->
+ {Alert, ConnectionStates0, CipherTexts}
+ end;
+decode_cipher_texts(Type, CipherTexts, ConnectionStates, _, Acc) ->
+ {#ssl_tls{type = Type, fragment = Acc}, ConnectionStates, CipherTexts}.
+
+%%% TLS record protocol level application data messages
+
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
+ case ssl_connection:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State1} ->
+ {next_state, StateName, State, Actions} = next_event(StateName, Record, State1),
+ ssl_connection:hibernate_after(StateName, State, Actions)
+ end;
%%% TLS record protocol level handshake messages
-handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
StateName, #state{protocol_buffers =
#protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
negotiated_version = Version,
@@ -220,22 +245,23 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
connection ->
ssl_connection:hibernate_after(StateName, State, Events);
_ ->
+ HsEnv = State#state.handshake_env,
{next_state, StateName,
- State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
end
end
catch throw:#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
end;
-%%% TLS record protocol level application data messages
-handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]};
%%% TLS record protocol level change cipher messages
-handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
{next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
%%% TLS record protocol level Alert messages
-handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{negotiated_version = Version} = State) ->
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+ #state{negotiated_version = Version} = State) ->
try decode_alerts(EncAlerts) of
Alerts = [_|_] ->
handle_alerts(Alerts, {next_state, StateName, State});
@@ -251,23 +277,25 @@ handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, Sta
end;
%% Ignore unknown TLS record level protocol messages
-handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State}.
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
%%====================================================================
%% Handshake handling
%%====================================================================
renegotiation(Pid, WriteState) ->
gen_statem:call(Pid, {user_renegotiate, WriteState}).
-renegotiate(#state{role = client} = State, Actions) ->
+renegotiate(#state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
Hs0 = ssl_handshake:init_handshake_history(),
- {next_state, connection, State#state{tls_handshake_history = Hs0},
+ {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
[{next_event, internal, #hello_request{}} | Actions]};
-renegotiate(#state{role = server,
- socket = Socket,
- transport_cb = Transport,
+renegotiate(#state{static_env = #static_env{role = server,
+ socket = Socket,
+ transport_cb = Transport},
+ handshake_env = HsEnv,
negotiated_version = Version,
connection_states = ConnectionStates0} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
@@ -278,24 +306,24 @@ renegotiate(#state{role = server,
send(Transport, Socket, BinMsg),
State = State0#state{connection_states =
ConnectionStates,
- tls_handshake_history = Hs0},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
next_event(hello, no_record, State, Actions).
send_handshake(Handshake, State) ->
send_handshake_flight(queue_handshake(Handshake, State)).
queue_handshake(Handshake, #state{negotiated_version = Version,
- tls_handshake_history = Hist0,
+ handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
flight_buffer = Flight0,
connection_states = ConnectionStates0} = State0) ->
{BinHandshake, ConnectionStates, Hist} =
encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
State0#state{connection_states = ConnectionStates,
- tls_handshake_history = Hist,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
flight_buffer = Flight0 ++ [BinHandshake]}.
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = Flight} = State0) ->
send(Transport, Socket, Flight),
{State0#state{flight_buffer = []}, []}.
@@ -314,14 +342,14 @@ reinit(#state{protocol_specific = #{sender := Sender},
tls_sender:update_connection_state(Sender, Write, Version),
reinit_handshake_data(State).
-reinit_handshake_data(State) ->
+reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
%% premaster_secret, public_key_info and tls_handshake_info
%% are only needed during the handshake phase.
%% To reduce memory foot print of a connection reinitialize them.
State#state{
premaster_secret = undefined,
public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history()
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history()}
}.
select_sni_extension(#client_hello{extensions = HelloExtensions}) ->
@@ -345,8 +373,8 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
tls_record:encode_alert_record(Alert, Version, ConnectionStates).
send_alert(Alert, #state{negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
+ static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
connection_states = ConnectionStates0} = StateData0) ->
{BinMsg, ConnectionStates} =
encode_alert(Alert, Version, ConnectionStates0),
@@ -429,14 +457,17 @@ getopts(Transport, Socket, Tag) ->
%%--------------------------------------------------------------------
init({call, From}, {start, Timeout},
- #state{host = Host, port = Port, role = client,
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ transport_cb = Transport,
+ socket = Socket,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
ssl_options = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- transport_cb = Transport, socket = Socket,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
@@ -452,7 +483,7 @@ init({call, From}, {start, Timeout},
negotiated_version = Version, %% Requested version
session =
Session0#session{session_id = Hello#client_hello.session_id},
- tls_handshake_history = Handshake,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
start_or_recv_from = From,
timer = Timer},
next_event(hello, no_record, State);
@@ -466,8 +497,9 @@ init(Type, Event, State) ->
%%--------------------------------------------------------------------
error({call, From}, {start, _Timeout},
#state{protocol_specific = #{error := Error}} = State) ->
- ssl_connection:stop_and_reply(
- normal, {reply, From, {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(_, _, _) ->
@@ -493,10 +525,12 @@ hello(internal, #server_hello{extensions = Extensions} = Hello,
[{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
hello(internal, #client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
+ static_env = #static_env{
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ session = #session{own_certificate = Cert} = Session0,
negotiated_protocol = CurrentProtocol,
key_algorithm = KeyExAlg,
ssl_options = SslOpts} = State) ->
@@ -516,15 +550,15 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
State#state{connection_states = ConnectionStates,
negotiated_version = Version,
hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
+ handshake_env = HsEnv#handshake_env{client_hello_version = ClientVersion},
session = Session,
negotiated_protocol = Protocol})
end;
hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
negotiated_version = ReqVersion,
- role = client,
- renegotiation = {Renegotiation, _},
+ static_env = #static_env{role = client},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
ssl_options = SslOptions} = State) ->
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
@@ -580,22 +614,72 @@ 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{terminated = closed} = State) ->
+ {next_state, downgrade, State#state{terminated = true, downgrade = {Pid, From}},
+ [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
+connection({call, From},
+ {close,{Pid, Timeout}},
+ #state{connection_states = ConnectionStates,
+ protocol_specific = #{sender := Sender}
+ } = 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{downgrade = {Pid, From},
+ terminated = true}, [{timeout, Timeout, downgrade}]};
+ {error, timeout} ->
+ {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
+ end;
connection(internal, #hello_request{},
- #state{role = client,
- renegotiation = {Renegotiation, _},
- host = Host, port = Port,
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}},
session = #session{own_certificate = Cert} = Session0,
- session_cache = Cache, session_cache_cb = CacheCb,
- ssl_options = SslOpts,
+ ssl_options = SslOpts,
+ protocol_specific = #{sender := Pid},
+ connection_states = ConnectionStates} = State0) ->
+ try tls_sender:peer_renegotiate(Pid) of
+ {ok, Write} ->
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
+ Cache, CacheCb, Renegotiation, Cert),
+ {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}}),
+ next_event(hello, no_record, State#state{session = Session0#session{session_id
+ = Hello#client_hello.session_id}}, Actions)
+ catch
+ _:_ ->
+ {stop, {shutdown, sender_blocked}, State0}
+ end;
+connection(internal, #hello_request{},
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ session = #session{own_certificate = Cert} = Session0,
+ ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
+ Cache, CacheCb, Renegotiation, Cert),
{State, Actions} = send_handshake(Hello, State0),
next_event(hello, no_record, State#state{session = Session0#session{session_id
= Hello#client_hello.session_id}}, Actions);
connection(internal, #client_hello{} = Hello,
- #state{role = server, allow_renegotiate = true, connection_states = CS,
- %%protocol_cb = Connection,
+ #state{static_env = #static_env{role = server},
+ handshake_env = HsEnv,
+ allow_renegotiate = true,
+ connection_states = CS,
protocol_specific = #{sender := Sender}
} = State) ->
%% Mitigate Computational DoS attack
@@ -607,16 +691,18 @@ connection(internal, #client_hello{} = Hello,
{ok, Write} = tls_sender:renegotiate(Sender),
next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write},
allow_renegotiate = false,
- renegotiation = {true, peer}
+ handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}
},
[{next_event, internal, Hello}]);
connection(internal, #client_hello{},
- #state{role = server, allow_renegotiate = false,
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ allow_renegotiate = false} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
send_alert_in_connection(Alert, State0),
State = Connection:reinit_handshake_data(State0),
next_event(?FUNCTION_NAME, no_record, State);
+
connection(Type, Event, State) ->
ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
@@ -624,19 +710,34 @@ connection(Type, Event, State) ->
-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket},
+ downgrade = {Pid, From}} = State) ->
+ tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
+ Transport:controlling_process(Socket, Pid),
+ {stop_and_reply, {shutdown, downgrade},[{reply, From, {ok, Socket}}], State};
+downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State) ->
+ {stop_and_reply, {shutdown, normal},[{reply, From, {error, timeout}}], State};
+downgrade(info, {CloseTag, Socket},
+ #state{static_env = #static_env{socket = Socket,
+ close_tag = CloseTag}, downgrade = {_, From}} =
+ State) ->
+ {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State};
+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, ?MODULE).
%--------------------------------------------------------------------
%% gen_statem callbacks
%%--------------------------------------------------------------------
callback_mode() ->
state_functions.
-
-terminate(
- {shutdown, sender_died, Reason}, _StateName,
- #state{socket = Socket, transport_cb = Transport} = State) ->
+terminate({shutdown, sender_died, Reason}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
ssl_connection:handle_trusted_certs_db(State),
close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, StateName, State) ->
@@ -658,64 +759,62 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
erl_dist = IsErlDist} = SSLOptions,
ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation),
- ErlDistData = erl_dist_data(IsErlDist),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
Cb;
_ ->
ssl_session_cache
end,
-
InternalActiveN = case application:get_env(ssl, internal_active_n) of
{ok, N} when is_integer(N) andalso (not IsErlDist) ->
N;
_ ->
?INTERNAL_ACTIVE_N
end,
-
UserMonitor = erlang:monitor(process, User),
-
- #state{socket_options = SocketOptions,
- ssl_options = SSLOptions,
- session = #session{is_resumable = new},
- transport_cb = CbModule,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- erl_dist_data = ErlDistData,
- connection_states = ConnectionStates,
- protocol_buffers = #protocol_buffers{},
- user_application = {UserMonitor, User},
- user_data_buffer = <<>>,
- session_cache_cb = SessionCacheCb,
- renegotiation = {false, first},
- allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
- start_or_recv_from = undefined,
- protocol_cb = ?MODULE,
- tracker = Tracker,
- flight_buffer = [],
- protocol_specific = #{sender => Sender,
- active_n => InternalActiveN,
- active_n_toggle => true
- }
- }.
-
-erl_dist_data(true) ->
- #{dist_handle => undefined,
- dist_buffer => <<>>};
-erl_dist_data(false) ->
- #{}.
-
-initialize_tls_sender(#state{role = Role,
- socket = Socket,
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = ?MODULE,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ session_cache_cb = SessionCacheCb,
+ tracker = Tracker
+ },
+ #state{
+ static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first}
+ },
+ socket_options = SocketOptions,
+ ssl_options = SSLOptions,
+ session = #session{is_resumable = new},
+ connection_states = ConnectionStates,
+ protocol_buffers = #protocol_buffers{},
+ user_application = {UserMonitor, User},
+ user_data_buffer = <<>>,
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
+ start_or_recv_from = undefined,
+ flight_buffer = [],
+ protocol_specific = #{sender => Sender,
+ active_n => InternalActiveN,
+ active_n_toggle => true
+ }
+ }.
+
+initialize_tls_sender(#state{static_env = #static_env{
+ role = Role,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ socket = Socket,
+ tracker = Tracker
+ },
socket_options = SockOpts,
- tracker = Tracker,
- protocol_cb = Connection,
- transport_cb = Transport,
negotiated_version = Version,
ssl_options = #ssl_options{renegotiate_at = RenegotiateAt},
connection_states = #{current_write := ConnectionWriteState},
@@ -748,10 +847,11 @@ next_tls_record(Data, StateName, #state{protocol_buffers =
end.
+acceptable_record_versions(StateName, #state{negotiated_version = Version}) when StateName =/= hello->
+ Version;
acceptable_record_versions(hello, _) ->
- [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
-acceptable_record_versions(_, #state{negotiated_version = Version}) ->
- [Version].
+ [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS].
+
handle_record_alert(Alert, _) ->
Alert.
@@ -762,21 +862,22 @@ tls_handshake_events(Packets) ->
%% raw data from socket, upack records
handle_info({Protocol, _, Data}, StateName,
- #state{data_tag = Protocol
- } = State0) ->
+ #state{static_env = #static_env{data_tag = Protocol}} = State0) ->
case next_tls_record(Data, StateName, State0) of
{Record, State} ->
next_event(StateName, Record, State);
#alert{} = Alert ->
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- ssl_connection:stop({shutdown, own_alert}, State0)
+ {stop, {shutdown, own_alert}, State0}
end;
-handle_info({tcp_passive, Socket}, StateName, #state{socket = Socket,
- protocol_specific = PS
- } = State) ->
- next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}});
+handle_info({tcp_passive, Socket}, StateName,
+ #state{static_env = #static_env{socket = Socket},
+ protocol_specific = PS
+ } = State) ->
+ next_event(StateName, no_record,
+ State#state{protocol_specific = PS#{active_n_toggle => true}});
handle_info({CloseTag, Socket}, StateName,
- #state{socket = Socket, close_tag = CloseTag,
+ #state{static_env = #static_env{socket = Socket, close_tag = CloseTag},
socket_options = #socket_options{active = Active},
protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
user_data_buffer = Buffer,
@@ -802,7 +903,7 @@ handle_info({CloseTag, Socket}, StateName,
end,
ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- ssl_connection:stop({shutdown, transport_closed}, State);
+ {stop, {shutdown, transport_closed}, State};
true ->
%% Fixes non-delivery of final TLS record in {active, once}.
%% Basically allows the application the opportunity to set {active, once} again
@@ -820,6 +921,13 @@ handle_alerts([], Result) ->
Result;
handle_alerts(_, {stop, _, _} = Stop) ->
Stop;
+handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
+ {next_state, connection = StateName, #state{user_data_buffer = Buffer,
+ socket_options = #socket_options{active = false},
+ protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} =
+ State}) when (Buffer =/= <<>>) orelse
+ (CTs =/= []) ->
+ {next_state, StateName, State#state{terminated = true}};
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 19a5eb0348..fbb81f56fe 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -30,6 +30,7 @@
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_api.hrl").
-include_lib("public_key/include/public_key.hrl").
%% Handshake handling
@@ -47,7 +48,7 @@
%% Handshake handling
%%====================================================================
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), ssl_record:connection_states(),
+-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
#ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
#client_hello{}.
%%
@@ -81,13 +82,13 @@ client_hello(Host, Port, ConnectionStates,
-spec hello(#server_hello{} | #client_hello{}, #ssl_options{},
ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(),
atom(), ssl_record:connection_states(),
- binary() | undefined, ssl_cipher_format:key_algo()},
+ binary() | undefined, ssl:key_algo()},
boolean()) ->
- {tls_record:tls_version(), session_id(),
+ {tls_record:tls_version(), ssl:session_id(),
ssl_record:connection_states(), alpn | npn, binary() | undefined}|
{tls_record:tls_version(), {resumed | new, #session{}},
ssl_record:connection_states(), binary() | undefined,
- #hello_extensions{}, {ssl_cipher_format:hash(), ssl_cipher_format:sign_algo()} |
+ #hello_extensions{}, {ssl:hash(), ssl:sign_algo()} |
undefined} | #alert{}.
%%
%% Description: Handles a received hello message
@@ -223,8 +224,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, Version, SslOpts,
Session0, ConnectionStates0,
Renegotiation) of
- #alert{} = Alert ->
- Alert;
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol,
ServerHelloExt, HashSign}
@@ -235,14 +234,14 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
+ try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
Compression, HelloExt, Version,
SslOpt, ConnectionStates0,
- Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ Renegotiation) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ catch throw:Alert ->
+ Alert
end.
%%--------------------------------------------------------------------
enc_handshake(#hello_request{}, _Version) ->
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index cf0690f2a5..1776ec2627 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -75,26 +75,15 @@ init_connection_states(Role, BeastMitigation) ->
pending_write => Pending}.
%%--------------------------------------------------------------------
--spec get_tls_records(binary(), [tls_version()], binary()) -> {[binary()], binary()} | #alert{}.
+-spec get_tls_records(binary(), [tls_version()] | tls_version(), binary()) -> {[binary()], binary()} | #alert{}.
%%
%% and returns it as a list of tls_compressed binaries also returns leftover
%% Description: Given old buffer and new data from TCP, packs up a records
%% data
%%--------------------------------------------------------------------
-get_tls_records(Data, Versions, Buffer) ->
- BinData = list_to_binary([Buffer, Data]),
- case erlang:byte_size(BinData) of
- N when N >= 3 ->
- case assert_version(BinData, Versions) of
- true ->
- get_tls_records_aux(BinData, []);
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
- end;
- _ ->
- get_tls_records_aux(BinData, [])
- end.
-
+get_tls_records(Data, Version, Buffer) ->
+ get_tls_records_aux(Version, <<Buffer/binary, Data/binary>>, []).
+
%%====================================================================
%% Encoding
%%====================================================================
@@ -395,44 +384,51 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
server_verify_data => undefined
}.
-assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) ->
- is_acceptable_version({MajVer, MinVer}, Versions).
-
-get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
-get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length),
- Data:Length/binary, Rest/binary>>, Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
- version = {MajVer, MinVer},
+get_tls_records_aux({MajVer, MinVer} = Version, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>>,
+ Acc) when Type == ?APPLICATION_DATA;
+ Type == ?HANDSHAKE;
+ Type == ?ALERT;
+ Type == ?CHANGE_CIPHER_SPEC ->
+ get_tls_records_aux(Version, Rest, [#ssl_tls{type = Type,
+ version = Version,
fragment = Data} | Acc]);
-get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary,
- Rest/binary>>, Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
-get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
-get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
- ?UINT16(Length), _/binary>>,
+get_tls_records_aux(Versions, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>>,
+ Acc) when is_list(Versions) andalso
+ ((Type == ?APPLICATION_DATA)
+ orelse
+ (Type == ?HANDSHAKE)
+ orelse
+ (Type == ?ALERT)
+ orelse
+ (Type == ?CHANGE_CIPHER_SPEC)) ->
+ case is_acceptable_version({MajVer, MinVer}, Versions) of
+ true ->
+ get_tls_records_aux(Versions, Rest, [#ssl_tls{type = Type,
+ version = {MajVer, MinVer},
+ fragment = Data} | Acc]);
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+get_tls_records_aux(_, <<?BYTE(Type),?BYTE(_MajVer),?BYTE(_MinVer),
+ ?UINT16(Length), _:Length/binary, _Rest/binary>>,
+ _) when Type == ?APPLICATION_DATA;
+ Type == ?HANDSHAKE;
+ Type == ?ALERT;
+ Type == ?CHANGE_CIPHER_SPEC ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC);
+get_tls_records_aux(_, <<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
+ ?UINT16(Length), _/binary>>,
_Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-get_tls_records_aux(Data, Acc) ->
+get_tls_records_aux(_, Data, Acc) ->
case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
true ->
{lists:reverse(Acc), Data};
false ->
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
- end.
+ end.
%%--------------------------------------------------------------------
encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) ->
{CipherFragment, Write1} = do_encode_plain_text(Type, Version, Data, Write0),
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index ee2b7be4f4..11fcc6def0 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -29,7 +29,7 @@
%% API
-export([start/0, start/1, initialize/2, send_data/2, send_alert/2,
- send_and_ack_alert/2, setopts/2, renegotiate/1,
+ send_and_ack_alert/2, setopts/2, renegotiate/1, peer_renegotiate/1, downgrade/2,
update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]).
%% gen_statem callbacks
@@ -118,6 +118,15 @@ setopts(Pid, Opts) ->
renegotiate(Pid) ->
%% Needs error handling for external API
call(Pid, renegotiate).
+
+%%--------------------------------------------------------------------
+-spec peer_renegotiate(pid()) -> {ok, WriteState::map()} | {error, term()}.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when handshaking.
+%%--------------------------------------------------------------------
+peer_renegotiate(Pid) ->
+ gen_statem:call(Pid, renegotiate, ?DEFAULT_TIMEOUT).
+
%%--------------------------------------------------------------------
-spec update_connection_state(pid(), WriteState::map(), tls_record:tls_version()) -> ok.
%% Description: So TLS connection process can synchronize the
@@ -125,6 +134,21 @@ renegotiate(Pid) ->
%%--------------------------------------------------------------------
update_connection_state(Pid, NewState, Version) ->
gen_statem:cast(Pid, {new_write, NewState, Version}).
+
+%%--------------------------------------------------------------------
+-spec downgrade(pid(), integer()) -> {ok, ssl_record:connection_state()}
+ | {error, timeout}.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when sending application data.
+%%--------------------------------------------------------------------
+downgrade(Pid, Timeout) ->
+ try gen_statem:call(Pid, downgrade, Timeout) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ {error, timeout}
+ end.
%%--------------------------------------------------------------------
-spec dist_handshake_complete(pid(), node(), term()) -> ok.
%% Description: Erlang distribution callback
@@ -239,6 +263,9 @@ connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) ->
StateData = send_tls_alert(Alert, StateData0),
{next_state, ?FUNCTION_NAME, StateData,
[{reply,From,ok}]};
+connection({call, From}, downgrade, #data{connection_states =
+ #{current_write := Write}} = StateData) ->
+ {next_state, death_row, StateData, [{reply,From, {ok, Write}}]};
connection(internal, {application_packets, From, Data}, StateData) ->
send_application_data(Data, From, ?FUNCTION_NAME, StateData);
%%