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