Skip to content

Commit

Permalink
ssl: Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
IngelaAndin committed Dec 19, 2023
1 parent 17640a0 commit 3844945
Show file tree
Hide file tree
Showing 21 changed files with 4,449 additions and 3,341 deletions.
10 changes: 7 additions & 3 deletions lib/ssl/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ BEHAVIOUR_MODULES= \


MODULES= \
dtls_connection \
dtls_server_connection \
dtls_client_connection \
dtls_connection_sup \
dtls_handshake \
dtls_gen_connection \
Expand Down Expand Up @@ -92,8 +93,11 @@ MODULES= \
ssl_srp_primes \
ssl_sup \
tls_bloom_filter \
tls_dtls_connection \
tls_connection \
tls_dtls_client_connection \
tls_dtls_server_connection \
tls_dtls_gen_connection \
tls_server_connection \
tls_client_connection \
tls_connection_sup \
tls_server_connection_1_3 \
tls_client_connection_1_3 \
Expand Down
559 changes: 559 additions & 0 deletions lib/ssl/src/dtls_client_connection.erl

Large diffs are not rendered by default.

887 changes: 0 additions & 887 deletions lib/ssl/src/dtls_connection.erl

This file was deleted.

212 changes: 208 additions & 4 deletions lib/ssl/src/dtls_gen_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

%% Setup
-export([start_fsm/8,
initial_state/7,
pids/1]).

%% Handshake handling
Expand All @@ -46,7 +47,16 @@
reinit/1,
reinit_handshake_data/1,
select_sni_extension/1,
empty_connection_state/2]).
empty_connection_state/2,
gen_info/3,
prepare_flight/1,
next_flight/1,
retransmit_epoch/2,
handle_flight_timer/1,
handle_state_timeout/3,
alert_or_reset_connection/3,
renegotiate/2
]).

%% State transition handling
-export([next_event/3,
Expand All @@ -61,7 +71,8 @@
socket/4,
setopts/3,
getopts/3,
handle_info/3]).
handle_info/3,
send_application_data/4]).

%% Alert and close handling
-export([send_alert/2,
Expand All @@ -81,6 +92,53 @@
%%====================================================================
%% Setup
%%====================================================================
initial_state(Role, Host, Port, Socket,
{SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
put(log_level, maps:get(log_level, SSLOptions)),
BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation),
#{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role),
InternalActiveN = ssl_config:get_internal_active_n(),
Monitor = erlang:monitor(process, User),
InitStatEnv = #static_env{
role = Role,
transport_cb = CbModule,
protocol_cb = dtls_gen_connection,
data_tag = DataTag,
close_tag = CloseTag,
error_tag = ErrorTag,
passive_tag = PassiveTag,
host = Host,
port = Port,
socket = Socket,
session_cache_cb = SessionCacheCb,
trackers = Trackers
},

#state{static_env = InitStatEnv,
handshake_env = #handshake_env{
tls_handshake_history = ssl_handshake:init_handshake_history(),
renegotiation = {false, first},
allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined)
},
connection_env = #connection_env{user_application = {Monitor, User}},
socket_options = SocketOptions,
ssl_options = SSLOptions,
session = #session{is_resumable = false},
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
user_data_buffer = {[],0,[]},
start_or_recv_from = undefined,
flight_buffer = new_flight(),
protocol_specific = #{active_n => InternalActiveN,
active_n_toggle => true,
flight_state => initial_flight_state(DataTag),
ignored_alerts => 0,
max_ignored_alerts => 10
}
}.

start_fsm(Role, Host, Port, Socket, {_,_, Tracker} = Opts,
User, {CbModule, _, _, _, _} = CbInfo,
Timeout) ->
Expand Down Expand Up @@ -397,6 +455,72 @@ handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
%%====================================================================
%% Handshake handling
%%====================================================================
gen_info(Event, connection = StateName, State) ->
try dtls_gen_connection:handle_info(Event, StateName, State)
catch error:Reason:ST ->
?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]),
Alert = ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data),
alert_or_reset_connection(Alert, StateName, State)
end;
gen_info(Event, StateName, State) ->
try dtls_gen_connection:handle_info(Event, StateName, State)
catch error:Reason:ST ->
?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]),
Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,{malformed_handshake_data, ST}),
alert_or_reset_connection(Alert, StateName, State)
end.

prepare_flight(#state{flight_buffer = Flight,
connection_states = ConnectionStates0,
protocol_buffers =
#protocol_buffers{} = Buffers} = State) ->
ConnectionStates = dtls_record:save_current_connection_state(ConnectionStates0, write),
State#state{flight_buffer = next_flight(Flight),
connection_states = ConnectionStates,
protocol_buffers = Buffers#protocol_buffers{
dtls_handshake_next_fragments = [],
dtls_handshake_later_fragments = []}}.

next_flight(Flight) ->
Flight#{handshakes => [],
change_cipher_spec => undefined,
handshakes_after_change_cipher_spec => []}.

retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) ->
#{epoch := Epoch} =
ssl_record:current_connection_state(ConnectionStates, write),
Epoch.

handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
protocol_specific = #{flight_state := {retransmit, Timeout}}} = State) ->
start_retransmision_timer(Timeout, State);
handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
protocol_specific = #{flight_state := connection}} = State) ->
{State, []};
handle_flight_timer(#state{protocol_specific = #{flight_state := reliable}} = State) ->
%% No retransmision needed i.e DTLS over SCTP
{State, []}.

start_retransmision_timer(Timeout, #state{protocol_specific = PS} = State) ->
{State#state{protocol_specific = PS#{flight_state => {retransmit, Timeout}}},
[{state_timeout, Timeout, flight_retransmission_timeout}]}.

new_timeout(N) when N =< 30000 ->
N * 2;
new_timeout(_) ->
60000.

handle_state_timeout(flight_retransmission_timeout, StateName,
#state{protocol_specific =
#{flight_state := {retransmit, CurrentTimeout}}} = State0) ->
{State1, Actions0} = send_handshake_flight(State0,
retransmit_epoch(StateName, State0)),
{next_state, StateName, #state{protocol_specific = PS} = State2, Actions} =
next_event(StateName, no_record, State1, Actions0),
State = State2#state{protocol_specific = PS#{flight_state => {retransmit, new_timeout(CurrentTimeout)}}},
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions}.

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).
Expand Down Expand Up @@ -462,6 +586,7 @@ select_sni_extension(_) ->
empty_connection_state(ConnectionEnd, BeastMitigation) ->
Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation),
dtls_record:empty_connection_state(Empty).

%%====================================================================
%% Alert and close handling
%%====================================================================
Expand Down Expand Up @@ -492,10 +617,89 @@ close(_, Socket, Transport, _) ->

protocol_name() ->
"DTLS".

alert_or_reset_connection(Alert, StateName, #state{connection_states = Cs} = State) ->
case maps:get(previous_cs, Cs, undefined) of
undefined ->
ssl_gen_statem:handle_own_alert(Alert, StateName, State);
PreviousConn ->
%% There exists an old connection and the new one failed,
%% reset to the old working one.
%% The next alert will be sent
HsEnv0 = State#state.handshake_env,
HsEnv = HsEnv0#handshake_env{renegotiation = undefined},
NewState = State#state{connection_states = PreviousConn,
handshake_env = HsEnv
},
{next_state, connection, NewState}
end.

%%====================================================================
%% Data handling
%%====================================================================

send_application_data(Data, From, _StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
connection_env = #connection_env{negotiated_version = Version},
handshake_env = HsEnv,
connection_states = ConnectionStates0,
ssl_options = #{renegotiate_at := RenegotiateAt,
log_level := LogLevel}} = State0) ->

case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}},
[{next_event, {call, From}, {application_data, Data}}]);
false ->
{Msgs, ConnectionStates} =
dtls_record:encode_data(Data, Version, ConnectionStates0),
State = State0#state{connection_states = ConnectionStates},
case send_msgs(Transport, Socket, Msgs) of
ok ->
ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]);
Result ->
ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}])
end
end.

time_to_renegotiate(_Data,
#{current_write := #{sequence_number := Num}},
RenegotiateAt) ->

%% We could do test:
%% is_time_to_renegotiate((erlang:byte_size(_Data) div
%% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to
%% have a some what lower renegotiateAt and a much cheaper test
is_time_to_renegotiate(Num, RenegotiateAt).

is_time_to_renegotiate(N, M) when N < M->
false;
is_time_to_renegotiate(_,_) ->
true.

renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
State = dtls_gen_connection:reinit_handshake_data(State0),
{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} = dtls_gen_connection:send_handshake(HelloRequest, State1),
dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions).

send_msgs(Transport, Socket, [Msg|Msgs]) ->
case send(Transport, Socket, Msg) of
ok -> send_msgs(Transport, Socket, Msgs);
Error -> Error
end;
send_msgs(_, _, []) ->
ok.

send(Transport, {Listener, Socket}, Data) when is_pid(Listener) ->
%% Server socket
dtls_socket:send(Transport, Socket, Data);
Expand Down Expand Up @@ -714,10 +918,10 @@ handle_own_alert(Alert, StateName,
log_ignore_alert(LogLevel, StateName, Alert, Role),
{next_state, StateName, State};
{false, State} ->
dtls_connection:alert_or_reset_connection(Alert, StateName, State)
alert_or_reset_connection(Alert, StateName, State)
end;
handle_own_alert(Alert, StateName, State) ->
dtls_connection:alert_or_reset_connection(Alert, StateName, State).
alert_or_reset_connection(Alert, StateName, State).

ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N,
max_ignored_alerts := N}} = State) ->
Expand Down
Loading

0 comments on commit 3844945

Please sign in to comment.