Skip to content

Commit

Permalink
ssl: Refactor pre TLS-1.3 main statemachine into client/server FSMs
Browse files Browse the repository at this point in the history
This refactor is made to enhance eas of code understanding.
Also cleans up dead code and removes use of ?FUNCTION_NAME as it
is harder to read and messes it up for language server features.
  • Loading branch information
IngelaAndin committed Dec 19, 2023
1 parent 17640a0 commit 81bba96
Show file tree
Hide file tree
Showing 21 changed files with 4,450 additions and 3,340 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 81bba96

Please sign in to comment.