From 81bba968612c417948d7996a3fe69c4eac421bfe Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 10 Nov 2023 08:03:55 +0100 Subject: [PATCH] ssl: Refactor pre TLS-1.3 main statemachine into client/server FSMs This refactor is made to enhance eas of code understanding. Also cleans up dead code and removes use of ?FUNCTION_NAME as it is harder to read and messes it up for language server features. --- lib/ssl/src/Makefile | 10 +- lib/ssl/src/dtls_client_connection.erl | 559 ++++++ lib/ssl/src/dtls_connection.erl | 887 --------- lib/ssl/src/dtls_gen_connection.erl | 212 +- lib/ssl/src/dtls_server_connection.erl | 580 ++++++ lib/ssl/src/ssl.app.src | 10 +- lib/ssl/src/ssl.erl | 6 +- lib/ssl/src/ssl_gen_statem.erl | 219 +-- lib/ssl/src/ssl_handshake.erl | 7 +- ...nnection.erl => tls_client_connection.erl} | 522 ++--- lib/ssl/src/tls_client_connection_1_3.erl | 84 +- lib/ssl/src/tls_dtls_client_connection.erl | 825 ++++++++ lib/ssl/src/tls_dtls_connection.erl | 1747 ----------------- lib/ssl/src/tls_dtls_gen_connection.erl | 479 +++++ lib/ssl/src/tls_dtls_server_connection.erl | 799 ++++++++ lib/ssl/src/tls_dyn_connection_sup.erl | 3 +- lib/ssl/src/tls_gen_connection.erl | 175 +- lib/ssl/src/tls_gen_connection_1_3.erl | 34 +- lib/ssl/src/tls_sender.erl | 38 +- lib/ssl/src/tls_server_connection.erl | 476 +++++ lib/ssl/src/tls_server_connection_1_3.erl | 118 +- 21 files changed, 4450 insertions(+), 3340 deletions(-) create mode 100644 lib/ssl/src/dtls_client_connection.erl delete mode 100644 lib/ssl/src/dtls_connection.erl create mode 100644 lib/ssl/src/dtls_server_connection.erl rename lib/ssl/src/{tls_connection.erl => tls_client_connection.erl} (50%) create mode 100644 lib/ssl/src/tls_dtls_client_connection.erl delete mode 100644 lib/ssl/src/tls_dtls_connection.erl create mode 100644 lib/ssl/src/tls_dtls_gen_connection.erl create mode 100644 lib/ssl/src/tls_dtls_server_connection.erl create mode 100644 lib/ssl/src/tls_server_connection.erl diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 6395834fe92b..f6f2557b4f0a 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -45,7 +45,8 @@ BEHAVIOUR_MODULES= \ MODULES= \ - dtls_connection \ + dtls_server_connection \ + dtls_client_connection \ dtls_connection_sup \ dtls_handshake \ dtls_gen_connection \ @@ -92,8 +93,11 @@ MODULES= \ ssl_srp_primes \ ssl_sup \ tls_bloom_filter \ - tls_dtls_connection \ - tls_connection \ + tls_dtls_client_connection \ + tls_dtls_server_connection \ + tls_dtls_gen_connection \ + tls_server_connection \ + tls_client_connection \ tls_connection_sup \ tls_server_connection_1_3 \ tls_client_connection_1_3 \ diff --git a/lib/ssl/src/dtls_client_connection.erl b/lib/ssl/src/dtls_client_connection.erl new file mode 100644 index 000000000000..5938fd49b2e2 --- /dev/null +++ b/lib/ssl/src/dtls_client_connection.erl @@ -0,0 +1,559 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2023-2023. 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% +%% + +-module(dtls_client_connection). + +%%---------------------------------------------------------------------- +%% Purpose: DTLS-1-DTLS-1.2 FSM (* = optional) +%%---------------------------------------------------------------------- +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% For UDP transport the following flights are used as retransmission units +%% in case of package loss. Flight timers are handled in state entry functions. +%% +%% Client Server +%% ------ ------ +%% +%% ClientHello --------> Flight 1 +%% +%% <------- HelloVerifyRequest Flight 2 +%% +%% ClientHello --------> Flight 3 +%% +%% ServerHello \ +%% Certificate* \ +%% ServerKeyExchange* Flight 4 +%% CertificateRequest* / +%% <-------- ServerHelloDone / +%% +%% Certificate* \ +%% ClientKeyExchange \ +%% CertificateVerify* Flight 5 +%% [ChangeCipherSpec] / +%% NextProtocol* / +%% Finished --------> / +%% +%% [ChangeCipherSpec] \ Flight 6 +%% <-------- Finished / +%% +%% Message Flights for Full Handshake +%% +%% +%% Client Server +%% ------ ------ +%% +%% ClientHello --------> Abbrev Flight 1 +%% +%% ServerHello \ part 1 +%% [ChangeCipherSpec] Abbrev Flight 2 +%% <-------- Finished / part 2 +%% +%% [ChangeCipherSpec] \ Abbrev Flight 3 +%% NextProtocol* / +%% Finished --------> / +%% +%% +%% Message Flights for Abbbriviated Handshake +%%---------------------------------------------------------------------- +%% Start FSM ---> CONFIG_ERROR +%% Send error to user +%% | and shutdown +%% | +%% V +%% INITIAL_HELLO +%% +%% | Send/ Recv Flight 1 +%% | +%% | +%% USER_HELLO | +%% <- Possibly let user provide V +%% options after looking at hello ex -> HELLO +%% | Send Recv Flight 2 to Flight 4 or +%% | Abbrev Flight 1 to Abbrev Flight 2 part 1 +%% | +%% New session | Resumed session +%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED +%% +%% <- Possibly Receive -- | | +%% OCSP Stapel ------> | Send/ Recv Flight 5 | +%% | | +%% V | Send / Recv Abbrev +%% | Flight part 2 +%% | to Abbrev Flight 3 +%% CIPHER | +%% | | +%% | Send/ Recv Flight 6 | +%% | | +%% V V +%% ---------------------------------------------------- +%% | +%% | +%% V +%% CONNECTION +%% | +%% | Renegotiaton +%% V +%% GO BACK TO HELLO +%%---------------------------------------------------------------------- + +%% Internal application API + +-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"). +-include("dtls_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). + +%% Internal application API + +%% Setup +-export([init/1]). + +%% gen_statem state functions +-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]). + +%% Tracing +-export([handle_trace/3]). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%==================================================================== +%% Setup +%%==================================================================== +init([Role, Host, Port, Socket, Options, User, CbInfo]) -> + process_flag(trap_exit, true), + State0 = dtls_gen_connection:initial_state(Role, Host, Port, Socket, + Options, User, CbInfo), + try + State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, + Role, State0), + gen_statem:enter_loop(?MODULE, [], initial_hello, State) + catch + throw:Error -> + #state{protocol_specific = Map} = State0, + EState = State0#state{protocol_specific = Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], config_error, EState) + end. + +%%-------------------------------------------------------------------- +%% State functions +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +initial_hello(enter, _, State) -> + {keep_state, State}; +initial_hello({call, From}, {start, Timeout}, + #state{static_env = #static_env{host = Host, + port = Port, + socket = {_, Socket}, + transport_cb = Transport, + session_cache = Cache, + session_cache_cb = CacheCb}, + protocol_specific = PS, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, + connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, + ssl_options = #{versions := Versions} = SslOpts, + session = Session0, + connection_states = ConnectionStates0 + } = State0) -> + Packages = maps:get(active_n, PS), + dtls_socket:setopts(Transport, Socket, [{active,Packages}]), + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), + Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, + CacheCb, Session0, CertKeyPairs), + Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Session#session.session_id, Renegotiation), + + MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined), + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), + Version = Hello#client_hello.client_version, + HelloVersion = dtls_record:hello_version(Version, Versions), + State1 = dtls_gen_connection:prepare_flight( + State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}, + connection_states = ConnectionStates1}), + {State2, Actions} = + dtls_gen_connection:send_handshake(Hello, + State1#state{connection_env = + CEnv#connection_env{negotiated_version + = HelloVersion}}), + State = State2#state{connection_env = + CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion + session = Session, + start_or_recv_from = From, + protocol_specific = PS#{active_n_toggle := false} + }, + dtls_gen_connection:next_event(hello, no_record, State, + [{{timeout, handshake}, Timeout, close} | Actions]); +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:update_options(Opts, Role, OrigSSLOptions), + State = ssl_gen_statem: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(info, Event, State) -> + dtls_gen_connection:gen_info(Event, initial_hello, State); +initial_hello(Type, Event, State) -> + ssl_gen_statem:initial_hello(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +config_error(enter, _, State) -> + {keep_state, State}; +config_error(Type, Event, State) -> + ssl_gen_statem:config_error(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #client_hello{} | #server_hello{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello(enter, _, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +hello(internal, #hello_verify_request{cookie = Cookie}, + #state{static_env = #static_env{host = Host, + port = Port}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState0} = HsEnv, + connection_env = CEnv, + ssl_options = SslOpts, + session = #session{session_id = Id}, + connection_states = ConnectionStates0, + protocol_specific = PS + } = State0) -> + OcspNonce = tls_handshake:ocsp_nonce(SslOpts), + Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, + SslOpts, Id, Renegotiation, OcspNonce), + Version = Hello#client_hello.client_version, + State1 = + dtls_gen_connection:prepare_flight( + State0#state{handshake_env = + HsEnv#handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}}), + {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1), + State = State2#state{connection_env = + CEnv#connection_env{negotiated_version = Version}, % RequestedVersion + protocol_specific = PS#{current_cookie_secret => Cookie}}, + dtls_gen_connection:next_event(hello, no_record, State, Actions); +hello(internal, #server_hello{extensions = Extensions}, + #state{handshake_env = #handshake_env{continue_status = pause}, + start_or_recv_from = From} = State) -> + {next_state, user_hello, State#state{start_or_recv_from = undefined}, + [{postpone, true},{reply, From, {ok, Extensions}}]}; +hello(internal, #server_hello{} = Hello, + #state{handshake_env = #handshake_env{ + renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState0} = HsEnv, + connection_states = ConnectionStates0, + session = #session{session_id = OldId}, + ssl_options = SslOptions} = State) -> + try + {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} = + dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId), + tls_dtls_client_connection:handle_session( + Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, + State#state{handshake_env = + HsEnv#handshake_env{ + ocsp_stapling_state = maps:merge(OcspState0,OcspState)}}) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, hello, State) + end; +hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) -> + %% hello_verify should not be in handshake history + {next_state, hello, State, [{next_event, internal, Handshake}]}; +hello(internal, #change_cipher_spec{type = <<1>>}, State0) -> + Epoch = dtls_gen_connection:retransmit_epoch(hello, State0), + {State1, Actions0} = + dtls_gen_connection:send_handshake_flight(State0, Epoch), + {next_state, hello, State, Actions} = + dtls_gen_connection:next_event(hello, no_record, State1, Actions0), + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}; +hello(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, hello, State); +hello(info, Event, State) -> + dtls_gen_connection:gen_info(Event, hello, State); +hello(Type, Event, State) -> + gen_state(hello, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +user_hello(enter, _, State) -> + {keep_state, State}; +user_hello(info, Event, State) -> + dtls_gen_connection:gen_info(Event, user_hello, State); +user_hello(Type, Event, State) -> + gen_state(user_hello, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated(enter, _, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +abbreviated(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, abbreviated, State); +abbreviated(info, Event, State) -> + dtls_gen_connection:gen_info(Event, abbreviated, State); +abbreviated(internal = Type, #change_cipher_spec{} = Event, + #state{connection_states = ConnectionStates0} = State) -> + ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), + ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), + gen_state(abbreviated, Type, Event, + State#state{connection_states = ConnectionStates}); +abbreviated(Type, Event, State) -> + gen_state(abbreviated, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_ocsp_stapling(enter, _Event, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +wait_ocsp_stapling(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, wait_ocsp_stapling, State); +wait_ocsp_stapling(info, Event, State) -> + dtls_gen_connection:gen_info(Event, wait_ocsp_stapling, State); +wait_ocsp_stapling(Type, Event, State) -> + gen_state(wait_ocsp_stapling, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify(enter, _, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +certify(internal = Type, #server_hello_done{} = Event, State) -> + try tls_dtls_client_connection:certify(Type, Event, dtls_gen_connection:prepare_flight(State)) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, certify, State) + end; +certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> + Epoch = dtls_gen_connection:retransmit_epoch(certify, State0), + {State1, Actions0} = + dtls_gen_connection:send_handshake_flight(State0, Epoch), + {next_state, certify, State, Actions} = + dtls_gen_connection:next_event(certify, no_record, State1, Actions0), + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}; +certify(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, certify, State); +certify(info, Event, State) -> + dtls_gen_connection:gen_info(Event, certify, State); +certify(Type, Event, State) -> + gen_state(certify, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher(enter, _, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +cipher(info, Event, State) -> + dtls_gen_connection:gen_info(Event, cipher, State); +cipher(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, cipher, State); +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), + + try tls_dtls_server_connection:cipher(Type, Event, + State#state{connection_states = ConnectionStates}) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, cipher, State) + end; +cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates, + protocol_specific = PS} = State) -> + + try tls_dtls_client_connection:cipher(Type, Event, + dtls_gen_connection:prepare_flight( + State#state{connection_states = ConnectionStates, + protocol_specific = + PS#{flight_state => connection}})) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, cipher, State) + end; +cipher(Type, Event, State) -> + gen_state(cipher, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + #hello_request{} | #client_hello{}| term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(enter, _, State) -> + {keep_state, State}; +connection(internal, #hello_request{}, + #state{static_env = #static_env{host = Host, + port = Port, + data_tag = DataTag, + session_cache = Cache, + session_cache_cb = CacheCb + }, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, + connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, + session = Session0, + ssl_options = #{versions := Versions} = SslOpts, + connection_states = ConnectionStates0, + protocol_specific = PS + } = State0) -> + #{current_cookie_secret := Cookie} = PS, + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), + Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, + Session0, CertKeyPairs), + Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, + Session#session.session_id, Renegotiation, undefined), + Version = Hello#client_hello.client_version, + HelloVersion = dtls_record:hello_version(Version, Versions), + State1 = dtls_gen_connection:prepare_flight(State0), + {State2, Actions} = + dtls_gen_connection:send_handshake(Hello, + State1#state{connection_env = + CEnv#connection_env{negotiated_version + = HelloVersion}}), + State = State2#state{protocol_specific = + PS#{flight_state => dtls_gen_connection:initial_flight_state(DataTag)}, + session = Session}, + dtls_gen_connection:next_event(hello, no_record, State, Actions); +connection({call, From}, {application_data, Data}, State) -> + try + dtls_gen_connection:send_application_data(Data, From, connection, State) + catch throw:Error -> + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Error}]) + end; +connection({call, From}, {downgrade, Pid}, + #state{connection_env = CEnv, + static_env = #static_env{transport_cb = Transport, + socket = {_Server, Socket} = DTLSSocket}} = State) -> + %% For testing purposes, downgrades without noticing the server + dtls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), + Transport:controlling_process(Socket, Pid), + {stop_and_reply, {shutdown, normal}, {reply, From, {ok, DTLSSocket}}, + State#state{connection_env = CEnv#connection_env{socket_terminated = true}}}; +connection(info, Event, State) -> + dtls_gen_connection:gen_info(Event, connection, State); +connection(Type, Event, State) -> + gen_state(connection, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(enter, _, State) -> + {keep_state, State}; +downgrade(info, Event, State) -> + dtls_gen_connection:gen_info(Event, downgrade, State); +downgrade(Type, Event, State) -> + gen_state(downgrade, Type, Event, State). + +%%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- +callback_mode() -> + [state_functions, state_enter]. + +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_gen_statem:format_status(Type, Data). + +gen_state(StateName, Type, Event, State) -> + try tls_dtls_client_connection:StateName(Type, Event, State) + catch + throw:#alert{}=Alert -> + dtls_gen_connection:alert_or_reset_connection(Alert, StateName, State); + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + AlertInfo = case StateName of + connection -> + malformed_data; + _ -> + malformed_handshake_data + end, + Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, AlertInfo), + dtls_gen_connection:alert_or_reset_connection(Alert, StateName, State) + end. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. + +%%-------------------------------------------------------------------- +%% Tracing +%%-------------------------------------------------------------------- +handle_trace(hbn, + {call, {?MODULE, connection, + [_Type = info, Event, _State]}}, + Stack) -> + {io_lib:format("Type = info Event = ~W ", [Event, 10]), Stack}. diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl deleted file mode 100644 index aa93b5482abc..000000000000 --- a/lib/ssl/src/dtls_connection.erl +++ /dev/null @@ -1,887 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2013-2023. 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% -%% - --module(dtls_connection). - -%%---------------------------------------------------------------------- -%% Purpose: DTLS-1-DTLS-1.2 FSM (* = optional) -%%---------------------------------------------------------------------- -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% For UDP transport the following flights are used as retransmission units -%% in case of package loss. Flight timers are handled in state entry functions. -%% -%% Client Server -%% ------ ------ -%% -%% ClientHello --------> Flight 1 -%% -%% <------- HelloVerifyRequest Flight 2 -%% -%% ClientHello --------> Flight 3 -%% -%% ServerHello \ -%% Certificate* \ -%% ServerKeyExchange* Flight 4 -%% CertificateRequest* / -%% <-------- ServerHelloDone / -%% -%% Certificate* \ -%% ClientKeyExchange \ -%% CertificateVerify* Flight 5 -%% [ChangeCipherSpec] / -%% NextProtocol* / -%% Finished --------> / -%% -%% [ChangeCipherSpec] \ Flight 6 -%% <-------- Finished / -%% -%% Message Flights for Full Handshake -%% -%% -%% Client Server -%% ------ ------ -%% -%% ClientHello --------> Abbrev Flight 1 -%% -%% ServerHello \ part 1 -%% [ChangeCipherSpec] Abbrev Flight 2 -%% <-------- Finished / part 2 -%% -%% [ChangeCipherSpec] \ Abbrev Flight 3 -%% NextProtocol* / -%% Finished --------> / -%% -%% -%% Message Flights for Abbbriviated Handshake -%%---------------------------------------------------------------------- -%% Start FSM ---> CONFIG_ERROR -%% Send error to user -%% | and shutdown -%% | -%% V -%% INITIAL_HELLO -%% -%% | Send/ Recv Flight 1 -%% | -%% | -%% USER_HELLO | -%% <- Possibly let user provide V -%% options after looking at hello ex -> HELLO -%% | Send Recv Flight 2 to Flight 4 or -%% | Abbrev Flight 1 to Abbrev Flight 2 part 1 -%% | -%% New session | Resumed session -%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED -%% -%% <- Possibly Receive -- | | -%% OCSP Stapel ------> | Send/ Recv Flight 5 | -%% | | -%% V | Send / Recv Abbrev Flight part 2 -%% | to Abbrev Flight 3 -%% CIPHER | -%% | | -%% | Send/ Recv Flight 6 | -%% | | -%% V V -%% ---------------------------------------------------- -%% | -%% | -%% V -%% CONNECTION -%% | -%% | Renegotiaton -%% V -%% GO BACK TO HELLO -%%---------------------------------------------------------------------- - -%% Internal application API - --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"). --include("dtls_record.hrl"). --include("ssl_cipher.hrl"). --include("ssl_api.hrl"). --include("ssl_internal.hrl"). --include("ssl_srp.hrl"). - -%% Internal application API - -%% Setup --export([init/1]). - --export([renegotiate/2]). - --export([alert_or_reset_connection/3]). %% Code re-use from dtls_gen_connection. - -%% gen_statem state functions --export([initial_hello/3, - config_error/3, - downgrade/3, - hello/3, - user_hello/3, - wait_ocsp_stapling/3, - certify/3, - wait_cert_verify/3, - cipher/3, - abbreviated/3, - connection/3]). - -%% gen_statem callbacks --export([callback_mode/0, - terminate/3, - code_change/4, - format_status/2]). - -%% Tracing --export([handle_trace/3]). - -%%==================================================================== -%% Internal application API -%%==================================================================== -%%==================================================================== -%% Setup -%%==================================================================== -init([Role, Host, Port, Socket, Options, User, CbInfo]) -> - process_flag(trap_exit, true), - State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), - try - State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, - Role, State0), - gen_statem:enter_loop(?MODULE, [], initial_hello, State) - catch - throw:Error -> - #state{protocol_specific = Map} = State0, - EState = State0#state{protocol_specific = Map#{error => Error}}, - gen_statem:enter_loop(?MODULE, [], config_error, EState) - end. -%%==================================================================== -%% Handshake -%%==================================================================== -renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) -> - %% Handle same way as if server requested - %% the renegotiation - State = dtls_gen_connection:reinit_handshake_data(State0), - {next_state, connection, State, - [{next_event, internal, #hello_request{}} | Actions]}; - -renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -> - HelloRequest = ssl_handshake:hello_request(), - State1 = prepare_flight(State0), - {State, MoreActions} = dtls_gen_connection:send_handshake(HelloRequest, State1), - dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions). - -%%-------------------------------------------------------------------- -%% State functions -%%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- --spec initial_hello(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -initial_hello(enter, _, State) -> - {keep_state, State}; -initial_hello({call, From}, {start, Timeout}, - #state{static_env = #static_env{host = Host, - port = Port, - role = client, - socket = {_, Socket}, - transport_cb = Transport, - session_cache = Cache, - session_cache_cb = CacheCb}, - protocol_specific = PS, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, - ssl_options = #{versions := Versions} = SslOpts, - session = Session0, - connection_states = ConnectionStates0 - } = State0) -> - Packages = maps:get(active_n, PS), - dtls_socket:setopts(Transport, Socket, [{active,Packages}]), - CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), - Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation), - - MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined), - ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), - Version = Hello#client_hello.client_version, - HelloVersion = dtls_record:hello_version(Version, Versions), - State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}, - connection_states = ConnectionStates1}), - {State2, Actions} = - dtls_gen_connection:send_handshake(Hello, - State1#state{connection_env = - CEnv#connection_env{negotiated_version = HelloVersion}}), - State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion - session = Session, - start_or_recv_from = From, - protocol_specific = PS#{active_n_toggle := false} - }, - dtls_gen_connection:next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]); -initial_hello({call, _} = Type, Event, #state{static_env = #static_env{role = server}, - protocol_specific = PS0} = State) -> - PS = PS0#{current_cookie_secret => dtls_v1:cookie_secret(), previous_cookie_secret => <<>>}, - Result = ssl_gen_statem:?FUNCTION_NAME(Type, Event, State#state{protocol_specific = PS}), - erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), - Result; -initial_hello(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). - -%%-------------------------------------------------------------------- --spec config_error(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -config_error(enter, _, State) -> - {keep_state, State}; -config_error(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). - -%%-------------------------------------------------------------------- --spec hello(gen_statem:event_type(), - #hello_request{} | #client_hello{} | #server_hello{} | term(), - #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -hello(enter, _, #state{static_env = #static_env{role = server}} = State) -> - {keep_state, State}; -hello(enter, _, #state{static_env = #static_env{role = client}} = State0) -> - {State, Actions} = handle_flight_timer(State0), - {keep_state, State, Actions}; -hello(internal, #client_hello{cookie = <<>>, - client_version = Version} = Hello, - #state{static_env = #static_env{role = server, - transport_cb = Transport, - socket = Socket}, - handshake_env = HsEnv, - connection_env = CEnv, - protocol_specific = #{current_cookie_secret := Secret}} = State0) -> - try tls_dtls_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} = dtls_gen_connection:send_handshake(VerifyRequest, State2), - dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{ - tls_handshake_history = - ssl_handshake:init_handshake_history()}}, - Actions) - catch throw:#alert{} = Alert -> - alert_or_reset_connection(Alert, ?FUNCTION_NAME, State0) - end; -hello(internal, #hello_verify_request{cookie = Cookie}, - #state{static_env = #static_env{role = client, - host = Host, - port = Port}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState0} = HsEnv, - connection_env = CEnv, - ssl_options = SslOpts, - session = #session{session_id = Id}, - connection_states = ConnectionStates0, - protocol_specific = PS - } = State0) -> - OcspNonce = tls_handshake:ocsp_nonce(SslOpts), - Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, - SslOpts, Id, Renegotiation, OcspNonce), - Version = Hello#client_hello.client_version, - State1 = - prepare_flight( - State0#state{handshake_env = - HsEnv#handshake_env{ - tls_handshake_history = ssl_handshake:init_handshake_history(), - ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}}), - {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1), - State = State2#state{connection_env = - CEnv#connection_env{negotiated_version = Version}, % RequestedVersion - protocol_specific = PS#{current_cookie_secret => Cookie}}, - dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions); -hello(internal, #client_hello{extensions = Extensions} = Hello, - #state{handshake_env = #handshake_env{continue_status = pause}, - start_or_recv_from = From} = State0) -> - try tls_dtls_connection:handle_sni_extension(State0, Hello) of - #state{} = State -> - {next_state, user_hello, State#state{start_or_recv_from = undefined}, - [{postpone, true}, {reply, From, {ok, Extensions}}]} - catch throw:#alert{} = Alert -> - alert_or_reset_connection(Alert, ?FUNCTION_NAME, State0) - end; -hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server, - transport_cb = Transport, - socket = Socket}, - protocol_specific = #{current_cookie_secret := Secret, - previous_cookie_secret := PSecret} - } = State) -> - {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), - case dtls_handshake:cookie(Secret, IP, Port, Hello) of - Cookie -> - handle_client_hello(Hello, State); - _ -> - case dtls_handshake:cookie(PSecret, IP, Port, Hello) of - Cookie -> - handle_client_hello(Hello, State); - _ -> - %% Handle bad cookie as new cookie request RFC 6347 4.1.2 - hello(internal, Hello#client_hello{cookie = <<>>}, State) - end - end; -hello(internal, #server_hello{extensions = Extensions}, - #state{handshake_env = #handshake_env{continue_status = pause}, - start_or_recv_from = From} = State) -> - {next_state, user_hello, State#state{start_or_recv_from = undefined}, - [{postpone, true},{reply, From, {ok, Extensions}}]}; -hello(internal, #server_hello{} = Hello, - #state{ - static_env = #static_env{role = client}, - handshake_env = #handshake_env{ - renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState0} = HsEnv, - connection_states = ConnectionStates0, - session = #session{session_id = OldId}, - ssl_options = SslOptions} = State) -> - try - {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} = - dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId), - tls_dtls_connection:handle_session( - Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, - State#state{handshake_env = - HsEnv#handshake_env{ - ocsp_stapling_state = maps:merge(OcspState0,OcspState)}}) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end; -hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) -> - %% Initial hello should not be in handshake history - {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; -hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) -> - %% hello_verify should not be in handshake history - {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; -hello(internal, #change_cipher_spec{type = <<1>>}, State0) -> - {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {next_state, ?FUNCTION_NAME, State, Actions} = - dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0), - %% This will reset the retransmission timer by repeating the enter state event - {repeat_state, State, Actions}; -hello(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -hello(state_timeout, Event, State) -> - handle_state_timeout(Event, ?FUNCTION_NAME, State); -hello(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). - -user_hello(enter, _, State) -> - {keep_state, State}; -user_hello(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec abbreviated(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -abbreviated(enter, _, State0) -> - {State, Actions} = handle_flight_timer(State0), - {keep_state, State, Actions}; -abbreviated(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -abbreviated(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), - gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates}); -abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates, - protocol_specific = PS} = State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, - prepare_flight(State#state{connection_states = ConnectionStates, - protocol_specific = PS#{flight_state => connection}})); -abbreviated(state_timeout, Event, State) -> - handle_state_timeout(Event, ?FUNCTION_NAME, State); -abbreviated(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_ocsp_stapling(enter, _Event, State0) -> - {State, Actions} = handle_flight_timer(State0), - {keep_state, State, Actions}; -wait_ocsp_stapling(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -wait_ocsp_stapling(state_timeout, Event, State) -> - handle_state_timeout(Event, ?FUNCTION_NAME, State); -wait_ocsp_stapling(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec certify(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -certify(enter, _, State0) -> - {State, Actions} = handle_flight_timer(State0), - {keep_state, State, Actions}; -certify(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -certify(internal = Type, #server_hello_done{} = Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, prepare_flight(State)); -certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> - {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {next_state, ?FUNCTION_NAME, State, Actions} = - dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0), - %% This will reset the retransmission timer by repeating the enter state event - {repeat_state, State, Actions}; -certify(state_timeout, Event, State) -> - handle_state_timeout(Event, ?FUNCTION_NAME, State); -certify(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). - - -%%-------------------------------------------------------------------- --spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cert_verify(enter, _Event, State0) -> - {State, Actions} = handle_flight_timer(State0), - {keep_state, State, Actions}; -wait_cert_verify(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -wait_cert_verify(state_timeout, Event, State) -> - handle_state_timeout(Event, ?FUNCTION_NAME, State); -wait_cert_verify(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec cipher(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -cipher(enter, _, State0) -> - {State, Actions} = handle_flight_timer(State0), - {keep_state, State, Actions}; -cipher(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -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), - gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates}); -cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates, - protocol_specific = PS} = State) -> - 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) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec connection(gen_statem:event_type(), - #hello_request{} | #client_hello{}| term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -connection(enter, _, #state{connection_states = Cs0, - static_env = Env} = State0) -> - State = case Env of - #static_env{socket = {Listener, {Client, _}}} -> - dtls_packet_demux:connection_setup(Listener, Client), - case maps:is_key(previous_cs, Cs0) of - false -> - State0; - true -> - Cs = maps:remove(previous_cs, Cs0), - State0#state{connection_states = Cs} - end; - _ -> %% client - State0 - end, - {keep_state, State}; -connection(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host, - port = Port, - data_tag = DataTag, - session_cache = Cache, - session_cache_cb = CacheCb - }, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, - session = Session0, - ssl_options = #{versions := Versions} = SslOpts, - connection_states = ConnectionStates0, - protocol_specific = PS - } = State0) -> - #{current_cookie_secret := Cookie} = PS, - CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), - Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation, undefined), - Version = Hello#client_hello.client_version, - HelloVersion = dtls_record:hello_version(Version, Versions), - State1 = prepare_flight(State0), - {State2, Actions} = - dtls_gen_connection:send_handshake(Hello, - State1#state{connection_env = - CEnv#connection_env{negotiated_version = HelloVersion}}), - State = State2#state{protocol_specific = PS#{flight_state => dtls_gen_connection:initial_flight_state(DataTag)}, - session = Session}, - dtls_gen_connection:next_event(hello, no_record, State, Actions); -connection(internal, #client_hello{} = Hello, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) -> - %% Mitigate Computational DoS attack - %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html - %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client - %% initiated renegotiation we will disallow many client initiated - %% renegotiations immediately after each other. - erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}, - allow_renegotiate = false}}, - [{next_event, internal, Hello}]}; -connection(internal, #client_hello{}, #state{static_env = #static_env{role = server, - protocol_cb = Connection}, - handshake_env = #handshake_env{allow_renegotiate = false}} = State0) -> - Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - State1 = dtls_gen_connection:send_alert(Alert, State0), - {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection), - dtls_gen_connection:next_event(?FUNCTION_NAME, Record, State); -connection(internal, new_connection, #state{ssl_options=SSLOptions, - handshake_env=HsEnv, - static_env = #static_env{socket = {Listener, {Client, _}}}, - connection_states = OldCs} = State) -> - case maps:get(previous_cs, OldCs, undefined) of - undefined -> - case dtls_packet_demux:new_connection(Listener, Client) of - true -> - {keep_state, State}; - false -> - BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), - ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation), - ConnectionStates = ConnectionStates0#{previous_cs => OldCs}, - {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {false, first}}, - connection_states = ConnectionStates}} - end; - _ -> - %% Someone spamming new_connection, just drop them - {keep_state, State} - end; - -connection({call, From}, {application_data, Data}, State) -> - try - send_application_data(Data, From, ?FUNCTION_NAME, State) - catch throw:Error -> - ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) - end; -connection({call, From}, {downgrade, Pid}, - #state{connection_env = CEnv, - static_env = #static_env{transport_cb = Transport, - socket = {_Server, Socket} = DTLSSocket}} = State) -> - %% For testing purposes, downgrades without noticing the server - dtls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), - Transport:controlling_process(Socket, Pid), - {stop_and_reply, {shutdown, normal}, {reply, From, {ok, DTLSSocket}}, - State#state{connection_env = CEnv#connection_env{socket_terminated = true}}}; -connection(Type, Event, State) -> - try - tls_dtls_connection:?FUNCTION_NAME(Type, Event, State) - catch throw:#alert{}=Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. - -%%TODO does this make sense for DTLS ? -%%-------------------------------------------------------------------- --spec downgrade(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -downgrade(enter, _, State) -> - {keep_state, State}; -downgrade(Type, Event, State) -> - try - tls_dtls_connection:?FUNCTION_NAME(Type, Event, State) - catch throw:#alert{}=Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. - - -%%-------------------------------------------------------------------- -%% gen_statem callbacks -%%-------------------------------------------------------------------- -callback_mode() -> - [state_functions, state_enter]. - -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_gen_statem:format_status(Type, Data). - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -initial_state(Role, Host, Port, Socket, - {SSLOptions, SocketOptions, Trackers}, User, - {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> - put(log_level, maps:get(log_level, SSLOptions)), - BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), - ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation), - #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role), - InternalActiveN = ssl_config:get_internal_active_n(), - Monitor = erlang:monitor(process, User), - InitStatEnv = #static_env{ - role = Role, - transport_cb = CbModule, - protocol_cb = dtls_gen_connection, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - passive_tag = PassiveTag, - host = Host, - port = Port, - socket = Socket, - session_cache_cb = SessionCacheCb, - trackers = Trackers - }, - - #state{static_env = InitStatEnv, - handshake_env = #handshake_env{ - tls_handshake_history = ssl_handshake:init_handshake_history(), - renegotiation = {false, first}, - allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined) - }, - connection_env = #connection_env{user_application = {Monitor, User}}, - socket_options = SocketOptions, - ssl_options = SSLOptions, - session = #session{is_resumable = false}, - connection_states = ConnectionStates, - protocol_buffers = #protocol_buffers{}, - user_data_buffer = {[],0,[]}, - start_or_recv_from = undefined, - flight_buffer = dtls_gen_connection:new_flight(), - protocol_specific = #{active_n => InternalActiveN, - active_n_toggle => true, - flight_state => dtls_gen_connection:initial_flight_state(DataTag), - ignored_alerts => 0, - max_ignored_alerts => 10 - } - }. - - -handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) -> - try - #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 = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, - session = Session0, - ssl_options = SslOpts} = - tls_dtls_connection:handle_sni_extension(State0, Hello), - SessionTracker = proplists:get_value(session_id_tracker, Trackers), - {Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} = - dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, - ConnectionStates0, CertKeyAlts, KeyExAlg}, Renegotiation), - 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}), - {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]} - catch #alert{} = Alert -> - alert_or_reset_connection(Alert, hello, State0) - end. - - -handle_state_timeout(flight_retransmission_timeout, StateName, - #state{protocol_specific = - #{flight_state := {retransmit, CurrentTimeout}}} = State0) -> - {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, - retransmit_epoch(StateName, State0)), - {next_state, StateName, #state{protocol_specific = PS} = State2, Actions} = - dtls_gen_connection:next_event(StateName, no_record, State1, Actions0), - State = State2#state{protocol_specific = PS#{flight_state => {retransmit, new_timeout(CurrentTimeout)}}}, - %% This will reset the retransmission timer by repeating the enter state event - {repeat_state, State, Actions}. - -alert_or_reset_connection(Alert, StateName, #state{connection_states = Cs} = State) -> - case maps:get(previous_cs, Cs, undefined) of - undefined -> - ssl_gen_statem:handle_own_alert(Alert, StateName, State); - PreviousConn -> - %% There exists an old connection and the new one failed, - %% reset to the old working one. - %% The next alert will be sent - HsEnv0 = State#state.handshake_env, - HsEnv = HsEnv0#handshake_env{renegotiation = undefined}, - NewState = State#state{connection_states = PreviousConn, - handshake_env = HsEnv - }, - {next_state, connection, NewState} - end. - -gen_handshake(_, {call, _From}, {application_data, _Data}, _State) -> - {keep_state_and_data, [postpone]}; -gen_handshake(StateName, Type, Event, State) -> - try tls_dtls_connection:StateName(Type, Event, State) - catch - throw:#alert{}=Alert -> - alert_or_reset_connection(Alert, StateName, State); - error:Reason:ST -> - ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), - Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), - alert_or_reset_connection(Alert, StateName, State) - end. - -gen_info(Event, connection = StateName, State) -> - try dtls_gen_connection:handle_info(Event, StateName, State) - catch error:Reason:ST -> - ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]), - Alert = ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), - alert_or_reset_connection(Alert, StateName, State) - end; -gen_info(Event, StateName, State) -> - try dtls_gen_connection:handle_info(Event, StateName, State) - catch error:Reason:ST -> - ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), - Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,malformed_handshake_data), - alert_or_reset_connection(Alert, StateName, State) - end. - -prepare_flight(#state{flight_buffer = Flight, - connection_states = ConnectionStates0, - protocol_buffers = - #protocol_buffers{} = Buffers} = State) -> - ConnectionStates = dtls_record:save_current_connection_state(ConnectionStates0, write), - State#state{flight_buffer = next_flight(Flight), - connection_states = ConnectionStates, - protocol_buffers = Buffers#protocol_buffers{ - dtls_handshake_next_fragments = [], - dtls_handshake_later_fragments = []}}. - -next_flight(Flight) -> - Flight#{handshakes => [], - change_cipher_spec => undefined, - handshakes_after_change_cipher_spec => []}. - -handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, - protocol_specific = #{flight_state := {retransmit, Timeout}}} = State) -> - start_retransmision_timer(Timeout, State); -handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, - protocol_specific = #{flight_state := connection}} = State) -> - {State, []}; -handle_flight_timer(#state{protocol_specific = #{flight_state := reliable}} = State) -> - %% No retransmision needed i.e DTLS over SCTP - {State, []}. - -start_retransmision_timer(Timeout, #state{protocol_specific = PS} = State) -> - {State#state{protocol_specific = PS#{flight_state => {retransmit, Timeout}}}, - [{state_timeout, Timeout, flight_retransmission_timeout}]}. - -new_timeout(N) when N =< 30000 -> - N * 2; -new_timeout(_) -> - 60000. - -retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) -> - #{epoch := Epoch} = - ssl_record:current_connection_state(ConnectionStates, write), - Epoch. - -send_application_data(Data, From, _StateName, - #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = HsEnv, - connection_states = ConnectionStates0, - ssl_options = #{renegotiate_at := RenegotiateAt, - log_level := LogLevel}} = State0) -> - - case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of - true -> - renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}}, - [{next_event, {call, From}, {application_data, Data}}]); - false -> - {Msgs, ConnectionStates} = - dtls_record:encode_data(Data, Version, ConnectionStates0), - State = State0#state{connection_states = ConnectionStates}, - case send_msgs(Transport, Socket, Msgs) of - ok -> - ssl_logger:debug(LogLevel, outbound, 'record', Msgs), - ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]); - Result -> - ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}]) - end - end. - -send_msgs(Transport, Socket, [Msg|Msgs]) -> - case dtls_gen_connection:send(Transport, Socket, Msg) of - ok -> send_msgs(Transport, Socket, Msgs); - Error -> Error - end; -send_msgs(_, _, []) -> - ok. - -time_to_renegotiate(_Data, - #{current_write := #{sequence_number := Num}}, - RenegotiateAt) -> - - %% We could do test: - %% is_time_to_renegotiate((erlang:byte_size(_Data) div - %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to - %% have a some what lower renegotiateAt and a much cheaper test - is_time_to_renegotiate(Num, RenegotiateAt). - -is_time_to_renegotiate(N, M) when N < M-> - false; -is_time_to_renegotiate(_,_) -> - true. - -%%%################################################################ -%%%# -%%%# Tracing -%%%# -handle_trace(hbn, - {call, {?MODULE, connection, - [_Type = info, Event, _State]}}, - Stack) -> - {io_lib:format("Type = info Event = ~W ", [Event, 10]), Stack}. diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl index 95b0302b7418..c9bb4212650c 100644 --- a/lib/ssl/src/dtls_gen_connection.erl +++ b/lib/ssl/src/dtls_gen_connection.erl @@ -36,6 +36,7 @@ %% Setup -export([start_fsm/8, + initial_state/7, pids/1]). %% Handshake handling @@ -46,7 +47,16 @@ reinit/1, reinit_handshake_data/1, select_sni_extension/1, - empty_connection_state/2]). + empty_connection_state/2, + gen_info/3, + prepare_flight/1, + next_flight/1, + retransmit_epoch/2, + handle_flight_timer/1, + handle_state_timeout/3, + alert_or_reset_connection/3, + renegotiate/2 + ]). %% State transition handling -export([next_event/3, @@ -61,7 +71,8 @@ socket/4, setopts/3, getopts/3, - handle_info/3]). + handle_info/3, + send_application_data/4]). %% Alert and close handling -export([send_alert/2, @@ -81,6 +92,53 @@ %%==================================================================== %% Setup %%==================================================================== +initial_state(Role, Host, Port, Socket, + {SSLOptions, SocketOptions, Trackers}, User, + {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + put(log_level, maps:get(log_level, SSLOptions)), + BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), + ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation), + #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role), + InternalActiveN = ssl_config:get_internal_active_n(), + Monitor = erlang:monitor(process, User), + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = dtls_gen_connection, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + passive_tag = PassiveTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb, + trackers = Trackers + }, + + #state{static_env = InitStatEnv, + handshake_env = #handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + renegotiation = {false, first}, + allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined) + }, + connection_env = #connection_env{user_application = {Monitor, User}}, + socket_options = SocketOptions, + ssl_options = SSLOptions, + session = #session{is_resumable = false}, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_data_buffer = {[],0,[]}, + start_or_recv_from = undefined, + flight_buffer = new_flight(), + protocol_specific = #{active_n => InternalActiveN, + active_n_toggle => true, + flight_state => initial_flight_state(DataTag), + ignored_alerts => 0, + max_ignored_alerts => 10 + } + }. + start_fsm(Role, Host, Port, Socket, {_,_, Tracker} = Opts, User, {CbModule, _, _, _, _} = CbInfo, Timeout) -> @@ -397,6 +455,72 @@ handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> %%==================================================================== %% Handshake handling %%==================================================================== +gen_info(Event, connection = StateName, State) -> + try dtls_gen_connection:handle_info(Event, StateName, State) + catch error:Reason:ST -> + ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]), + Alert = ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), + alert_or_reset_connection(Alert, StateName, State) + end; +gen_info(Event, StateName, State) -> + try dtls_gen_connection:handle_info(Event, StateName, State) + catch error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,{malformed_handshake_data, ST}), + alert_or_reset_connection(Alert, StateName, State) + end. + +prepare_flight(#state{flight_buffer = Flight, + connection_states = ConnectionStates0, + protocol_buffers = + #protocol_buffers{} = Buffers} = State) -> + ConnectionStates = dtls_record:save_current_connection_state(ConnectionStates0, write), + State#state{flight_buffer = next_flight(Flight), + connection_states = ConnectionStates, + protocol_buffers = Buffers#protocol_buffers{ + dtls_handshake_next_fragments = [], + dtls_handshake_later_fragments = []}}. + +next_flight(Flight) -> + Flight#{handshakes => [], + change_cipher_spec => undefined, + handshakes_after_change_cipher_spec => []}. + +retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) -> + #{epoch := Epoch} = + ssl_record:current_connection_state(ConnectionStates, write), + Epoch. + +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, + protocol_specific = #{flight_state := {retransmit, Timeout}}} = State) -> + start_retransmision_timer(Timeout, State); +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, + protocol_specific = #{flight_state := connection}} = State) -> + {State, []}; +handle_flight_timer(#state{protocol_specific = #{flight_state := reliable}} = State) -> + %% No retransmision needed i.e DTLS over SCTP + {State, []}. + +start_retransmision_timer(Timeout, #state{protocol_specific = PS} = State) -> + {State#state{protocol_specific = PS#{flight_state => {retransmit, Timeout}}}, + [{state_timeout, Timeout, flight_retransmission_timeout}]}. + +new_timeout(N) when N =< 30000 -> + N * 2; +new_timeout(_) -> + 60000. + +handle_state_timeout(flight_retransmission_timeout, StateName, + #state{protocol_specific = + #{flight_state := {retransmit, CurrentTimeout}}} = State0) -> + {State1, Actions0} = send_handshake_flight(State0, + retransmit_epoch(StateName, State0)), + {next_state, StateName, #state{protocol_specific = PS} = State2, Actions} = + next_event(StateName, no_record, State1, Actions0), + State = State2#state{protocol_specific = PS#{flight_state => {retransmit, new_timeout(CurrentTimeout)}}}, + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}. + send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), send_handshake_flight(queue_handshake(Handshake, State), Epoch). @@ -462,6 +586,7 @@ select_sni_extension(_) -> empty_connection_state(ConnectionEnd, BeastMitigation) -> Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), dtls_record:empty_connection_state(Empty). + %%==================================================================== %% Alert and close handling %%==================================================================== @@ -492,10 +617,89 @@ close(_, Socket, Transport, _) -> protocol_name() -> "DTLS". + +alert_or_reset_connection(Alert, StateName, #state{connection_states = Cs} = State) -> + case maps:get(previous_cs, Cs, undefined) of + undefined -> + ssl_gen_statem:handle_own_alert(Alert, StateName, State); + PreviousConn -> + %% There exists an old connection and the new one failed, + %% reset to the old working one. + %% The next alert will be sent + HsEnv0 = State#state.handshake_env, + HsEnv = HsEnv0#handshake_env{renegotiation = undefined}, + NewState = State#state{connection_states = PreviousConn, + handshake_env = HsEnv + }, + {next_state, connection, NewState} + end. %%==================================================================== %% Data handling %%==================================================================== + +send_application_data(Data, From, _StateName, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = HsEnv, + connection_states = ConnectionStates0, + ssl_options = #{renegotiate_at := RenegotiateAt, + log_level := LogLevel}} = State0) -> + + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}}, + [{next_event, {call, From}, {application_data, Data}}]); + false -> + {Msgs, ConnectionStates} = + dtls_record:encode_data(Data, Version, ConnectionStates0), + State = State0#state{connection_states = ConnectionStates}, + case send_msgs(Transport, Socket, Msgs) of + ok -> + ssl_logger:debug(LogLevel, outbound, 'record', Msgs), + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]); + Result -> + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}]) + end + end. + +time_to_renegotiate(_Data, + #{current_write := #{sequence_number := Num}}, + RenegotiateAt) -> + + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div + %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to + %% have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). + +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. + +renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) -> + %% Handle same way as if server requested + %% the renegotiation + State = dtls_gen_connection:reinit_handshake_data(State0), + {next_state, connection, State, + [{next_event, internal, #hello_request{}} | Actions]}; + +renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -> + HelloRequest = ssl_handshake:hello_request(), + State1 = prepare_flight(State0), + {State, MoreActions} = dtls_gen_connection:send_handshake(HelloRequest, State1), + dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions). + +send_msgs(Transport, Socket, [Msg|Msgs]) -> + case send(Transport, Socket, Msg) of + ok -> send_msgs(Transport, Socket, Msgs); + Error -> Error + end; +send_msgs(_, _, []) -> + ok. + send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> %% Server socket dtls_socket:send(Transport, Socket, Data); @@ -714,10 +918,10 @@ handle_own_alert(Alert, StateName, log_ignore_alert(LogLevel, StateName, Alert, Role), {next_state, StateName, State}; {false, State} -> - dtls_connection:alert_or_reset_connection(Alert, StateName, State) + alert_or_reset_connection(Alert, StateName, State) end; handle_own_alert(Alert, StateName, State) -> - dtls_connection:alert_or_reset_connection(Alert, StateName, State). + alert_or_reset_connection(Alert, StateName, State). ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N, max_ignored_alerts := N}} = State) -> diff --git a/lib/ssl/src/dtls_server_connection.erl b/lib/ssl/src/dtls_server_connection.erl new file mode 100644 index 000000000000..be7f6372f1dd --- /dev/null +++ b/lib/ssl/src/dtls_server_connection.erl @@ -0,0 +1,580 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2023-2023. 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% +%% + +-module(dtls_server_connection). + +%%---------------------------------------------------------------------- +%% Purpose: DTLS-1-DTLS-1.2 FSM (* = optional) +%%---------------------------------------------------------------------- +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% For UDP transport the following flights are used as retransmission units +%% in case of package loss. Flight timers are handled in state entry functions. +%% +%% Client Server +%% ------ ------ +%% +%% ClientHello --------> Flight 1 +%% +%% <------- HelloVerifyRequest Flight 2 +%% +%% ClientHello --------> Flight 3 +%% +%% ServerHello \ +%% Certificate* \ +%% ServerKeyExchange* Flight 4 +%% CertificateRequest* / +%% <-------- ServerHelloDone / +%% +%% Certificate* \ +%% ClientKeyExchange \ +%% CertificateVerify* Flight 5 +%% [ChangeCipherSpec] / +%% NextProtocol* / +%% Finished --------> / +%% +%% [ChangeCipherSpec] \ Flight 6 +%% <-------- Finished / +%% +%% Message Flights for Full Handshake +%% +%% +%% Client Server +%% ------ ------ +%% +%% ClientHello --------> Abbrev Flight 1 +%% +%% ServerHello \ part 1 +%% [ChangeCipherSpec] Abbrev Flight 2 +%% <-------- Finished / part 2 +%% +%% [ChangeCipherSpec] \ Abbrev Flight 3 +%% NextProtocol* / +%% Finished --------> / +%% +%% +%% Message Flights for Abbbriviated Handshake +%%---------------------------------------------------------------------- +%% Start FSM ---> CONFIG_ERROR +%% Send error to user +%% | and shutdown +%% | +%% V +%% INITIAL_HELLO +%% +%% | Send/ Recv Flight 1 +%% | +%% | +%% USER_HELLO | +%% <- Possibly let user provide V +%% options after looking at hello ex -> HELLO +%% | Send Recv Flight 2 to Flight 4 or +%% | Abbrev Flight 1 to Abbrev Flight 2 part 1 +%% | +%% New session | Resumed session +%% WAIT_CERT_VERIFY CERTIFY <----------------------------------> ABBREVIATED +%% +%% <- Possibly Receive -- | | +%% CertVerify ------> | Send/ Recv Flight 5 | +%% | | +%% | | Send / Recv Abbrev +%% V | Flight part 2 +%% | to Abbrev Flight 3 +%% CIPHER | +%% | | +%% | Send/ Recv Flight 6 | +%% | | +%% V V +%% ---------------------------------------------------- +%% | +%% | +%% V +%% CONNECTION +%% | +%% | Renegotiaton +%% V +%% GO BACK TO HELLO +%%---------------------------------------------------------------------- + +%% Internal application API + +-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"). +-include("dtls_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). + +%% Internal application API + +%% Setup +-export([init/1]). + +%% gen_statem state functions +-export([initial_hello/3, + config_error/3, + downgrade/3, + hello/3, + user_hello/3, + certify/3, + wait_cert_verify/3, + cipher/3, + abbreviated/3, + connection/3]). + +%% gen_statem callbacks +-export([callback_mode/0, + terminate/3, + code_change/4, + format_status/2]). + +%% Tracing +-export([handle_trace/3]). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%==================================================================== +%% Setup +%%==================================================================== +init([Role, Host, Port, Socket, Options, User, CbInfo]) -> + process_flag(trap_exit, true), + State0 = dtls_gen_connection:initial_state(Role, Host, Port, Socket, + Options, User, CbInfo), + try + State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, + Role, State0), + gen_statem:enter_loop(?MODULE, [], initial_hello, State) + catch + throw:Error -> + #state{protocol_specific = Map} = State0, + EState = State0#state{protocol_specific = Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], config_error, EState) + end. +%%-------------------------------------------------------------------- +%% State functions +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +initial_hello(enter, _, State) -> + {keep_state, State}; +initial_hello({call, From}, {start, Timeout}, #state{protocol_specific = PS0} = State) -> + PS = PS0#{current_cookie_secret => dtls_v1:cookie_secret(), + previous_cookie_secret => <<>>}, + erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), + dtls_gen_connection:next_event(hello, no_record, + State#state{start_or_recv_from = From, + protocol_specific = PS}, + [{{timeout, handshake}, Timeout, close}]); +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:update_options(Opts, Role, OrigSSLOptions), + State = ssl_gen_statem: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(Type, Event, State) -> + ssl_gen_statem:initial_hello(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +config_error(enter, _, State) -> + {keep_state, State}; +config_error(Type, Event, State) -> + ssl_gen_statem:config_error(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #client_hello{} | #server_hello{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello(enter, _, State) -> + {keep_state, State}; +hello(internal, #client_hello{cookie = <<>>, + client_version = Version} = Hello, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}, + handshake_env = HsEnv, + connection_env = CEnv, + protocol_specific = #{current_cookie_secret := Secret}} = State0) -> + try tls_dtls_server_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 = dtls_gen_connection:prepare_flight( + State1#state{connection_env = + CEnv#connection_env{negotiated_version = Version}}), + {State, Actions} = dtls_gen_connection:send_handshake(VerifyRequest, State2), + NewHSEnv = HsEnv#handshake_env{tls_handshake_history = + ssl_handshake:init_handshake_history()}, + dtls_gen_connection:next_event(hello, no_record, + State#state{handshake_env = NewHSEnv}, Actions) + catch throw:#alert{} = Alert -> + dtls_gen_connection:alert_or_reset_connection(Alert, hello, State0) + end; +hello(internal, #client_hello{extensions = Extensions} = Hello, + #state{handshake_env = #handshake_env{continue_status = pause}, + start_or_recv_from = From} = State0) -> + try tls_dtls_server_connection:handle_sni_extension(State0, Hello) of + #state{} = State -> + {next_state, user_hello, State#state{start_or_recv_from = undefined}, + [{postpone, true}, {reply, From, {ok, Extensions}}]} + catch throw:#alert{} = Alert -> + dtls_gen_connection:alert_or_reset_connection(Alert, hello, State0) + end; +hello(internal, #client_hello{cookie = Cookie} = Hello, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}, + protocol_specific = #{current_cookie_secret := Secret, + previous_cookie_secret := PSecret} + } = State) -> + {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), + case dtls_handshake:cookie(Secret, IP, Port, Hello) of + Cookie -> + handle_client_hello(Hello, State); + _ -> + case dtls_handshake:cookie(PSecret, IP, Port, Hello) of + Cookie -> + handle_client_hello(Hello, State); + _ -> + %% Handle bad cookie as new cookie request RFC 6347 4.1.2 + hello(internal, Hello#client_hello{cookie = <<>>}, State) + end + end; +hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) -> + %% Initial hello should not be in handshake history + {next_state, hello, State, [{next_event, internal, Handshake}]}; +hello(internal, #change_cipher_spec{type = <<1>>}, State0) -> + Epoch = dtls_gen_connection:retransmit_epoch(hello, State0), + {State1, Actions0} = + dtls_gen_connection:send_handshake_flight(State0, Epoch), + {next_state, hello, State, Actions} = + dtls_gen_connection:next_event(hello, no_record, State1, Actions0), + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}; +hello(info, Event, State) -> + dtls_gen_connection:gen_info(Event, hello, State); +hello(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, hello, State); +hello(Type, Event, State) -> + gen_state(hello, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +user_hello(enter, _, State) -> + {keep_state, State}; +user_hello(info, Event, State) -> + dtls_gen_connection:gen_info(Event, user_hello, State); +user_hello(Type, Event, State) -> + gen_state(user_hello, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated(enter, _, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +abbreviated(info, Event, State) -> + dtls_gen_connection:gen_info(Event, abbreviated, State); +abbreviated(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, abbreviated, State); +abbreviated(internal = Type, #change_cipher_spec{} = Event, + #state{connection_states = ConnectionStates0} = State) -> + ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), + ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), + gen_state(abbreviated, Type, Event, + State#state{connection_states = ConnectionStates}); +abbreviated(Type, Event, State) -> + gen_state(abbreviated, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify(enter, _, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> + Epoch = dtls_gen_connection:retransmit_epoch(certify, State0), + {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, Epoch), + {next_state, certify, State, Actions} = + dtls_gen_connection:next_event(certify, no_record, State1, Actions0), + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}; +certify(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, certify, State); +certify(info, Event, State) -> + dtls_gen_connection:gen_info(Event, certify, State); +certify(Type, Event, State) -> + gen_state(certify, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cert_verify(enter, _Event, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +wait_cert_verify(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, wait_cert_verify, State); +wait_cert_verify(info, Event, State) -> + dtls_gen_connection:gen_info(Event, wait_cert_verify, State); +wait_cert_verify(Type, Event, State) -> + gen_state(wait_cert_verify, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher(enter, _, State0) -> + {State, Actions} = dtls_gen_connection:handle_flight_timer(State0), + {keep_state, State, Actions}; +cipher(state_timeout, Event, State) -> + dtls_gen_connection:handle_state_timeout(Event, cipher, State); +cipher(info, Event, State) -> + dtls_gen_connection:gen_info(Event, cipher, State); +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), + gen_state(cipher, Type, Event, State#state{connection_states = ConnectionStates}); +cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates, + protocol_specific = PS} = State) -> + gen_state(cipher, Type, Event, + dtls_gen_connection:prepare_flight( + State#state{connection_states = ConnectionStates, + protocol_specific = + PS#{flight_state => connection}})); +cipher(Type, Event, State) -> + gen_state(cipher, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + #hello_request{} | #client_hello{}| term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(enter, _, #state{connection_states = Cs0, + static_env = Env} = State0) -> + State = case Env of + #static_env{socket = {Listener, {Client, _}}} -> + dtls_packet_demux:connection_setup(Listener, Client), + case maps:is_key(previous_cs, Cs0) of + false -> + State0; + true -> + Cs = maps:remove(previous_cs, Cs0), + State0#state{connection_states = Cs} + end; + _ -> %% client + State0 + end, + {keep_state, State}; +connection(internal, #client_hello{} = Hello, + #state{handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) -> + %% Mitigate Computational DoS attack + %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html + %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client + %% initiated renegotiation we will disallow many client initiated + %% renegotiations immediately after each other. + erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), + {next_state, hello, + State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}, + allow_renegotiate = false}}, + [{next_event, internal, Hello}]}; +connection(internal, #client_hello{}, + #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = #handshake_env{allow_renegotiate = false}} = State0) -> + Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), + State1 = dtls_gen_connection:send_alert(Alert, State0), + {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection), + dtls_gen_connection:next_event(connection, Record, State); +connection(internal, new_connection, #state{ssl_options=SSLOptions, + handshake_env = HsEnv, + static_env = + #static_env{socket = {Listener, {Client, _}}}, + connection_states = OldCs} = State) -> + case maps:get(previous_cs, OldCs, undefined) of + undefined -> + case dtls_packet_demux:new_connection(Listener, Client) of + true -> + {keep_state, State}; + false -> + BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), + ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation), + ConnectionStates = ConnectionStates0#{previous_cs => OldCs}, + {next_state, hello, + State#state{handshake_env = + HsEnv#handshake_env{renegotiation = {false, first}}, + connection_states = ConnectionStates}} + end; + _ -> + %% Someone spamming new_connection, just drop them + {keep_state, State} + end; + +connection({call, From}, {application_data, Data}, State) -> + try + dtls_gen_connection:send_application_data(Data, From, connection, State) + catch throw:Error -> + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Error}]) + end; +connection({call, From}, {downgrade, Pid}, + #state{connection_env = CEnv, + static_env = #static_env{transport_cb = Transport, + socket = {_Server, Socket} = DTLSSocket}} = State) -> + %% For testing purposes, downgrades without noticing the server + dtls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), + Transport:controlling_process(Socket, Pid), + {stop_and_reply, {shutdown, normal}, {reply, From, {ok, DTLSSocket}}, + State#state{connection_env = CEnv#connection_env{socket_terminated = true}}}; +connection(info, Event, State) -> + dtls_gen_connection:gen_info(Event, connection, State); +connection(Type, Event, State) -> + gen_state(connection, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(enter, _, State) -> + {keep_state, State}; +downgrade(info, Event, State) -> + dtls_gen_connection:gen_info(Event, downgrade, State); +downgrade(Type, Event, State) -> + gen_state(downgrade, Type, Event, State). + +%%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- +callback_mode() -> + [state_functions, state_enter]. + +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_gen_statem:format_status(Type, Data). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) -> + try + #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 = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, + session = Session0, + ssl_options = SslOpts} = + tls_dtls_server_connection:handle_sni_extension(State0, Hello), + SessionTracker = proplists:get_value(session_id_tracker, Trackers), + {Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} = + dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, + ConnectionStates0, CertKeyAlts, KeyExAlg}, + Renegotiation), + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + + State = + dtls_gen_connection: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}), + {next_state, hello, State, + [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]} + catch #alert{} = Alert -> + dtls_gen_connection:alert_or_reset_connection(Alert, hello, State0) + end. + +gen_state(StateName, Type, Event, State) -> + try tls_dtls_server_connection:StateName(Type, Event, State) + catch + throw:#alert{}=Alert -> + dtls_gen_connection:alert_or_reset_connection(Alert, StateName, State); + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + AlertInfo = case StateName of + connection -> + malformed_data; + _ -> + malformed_handshake_data + end, + Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, AlertInfo), + dtls_gen_connection:alert_or_reset_connection(Alert, StateName, State) + end. + + +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. + + +%%-------------------------------------------------------------------- +%% Tracing +%%-------------------------------------------------------------------- +handle_trace(hbn, + {call, {?MODULE, connection, + [_Type = info, Event, _State]}}, + Stack) -> + {io_lib:format("Type = info Event = ~W ", [Event, 10]), Stack}. diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index b9f69af6a3bb..8efc7496dc5c 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -3,10 +3,11 @@ {vsn, "%VSN%"}, {modules, [ %% TLS/SSL - tls_connection, tls_client_connection_1_3, tls_server_connection_1_3, tls_gen_connection_1_3, + tls_client_connection, + tls_server_connection, tls_handshake, tls_handshake_1_3, tls_record, @@ -23,7 +24,8 @@ tls_dyn_connection_sup, ssl_dh_groups, %% DTLS - dtls_connection, + dtls_client_connection, + dtls_server_connection, dtls_handshake, dtls_record, dtls_socket, @@ -39,7 +41,9 @@ ssl, %% Main API ssl_session_cache_api, %% Both TLS/SSL and DTLS - tls_dtls_connection, + tls_dtls_client_connection, + tls_dtls_server_connection, + tls_dtls_gen_connection, ssl_config, ssl_gen_statem, ssl_handshake, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index f21a0fe9b3ed..3d91edac3fb3 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -1416,13 +1416,13 @@ renegotiate(#sslsocket{pid = [Pid, Sender |_]} = Socket) when is_pid(Pid), _ -> case tls_sender:renegotiate(Sender) of {ok, Write} -> - tls_dtls_connection:renegotiation(Pid, Write); + tls_dtls_gen_connection:renegotiation(Pid, Write); Error -> Error end end; renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) -> - tls_dtls_connection:renegotiation(Pid); + tls_dtls_gen_connection:renegotiation(Pid); renegotiate(#sslsocket{pid = {dtls,_}}) -> {error, enotconn}; renegotiate(#sslsocket{pid = {_Listen, #config{}}}) -> @@ -1462,7 +1462,7 @@ update_keys(_, Type) -> %%-------------------------------------------------------------------- prf(#sslsocket{pid = [Pid|_]}, Secret, Label, Seed, WantedLength) when is_pid(Pid) -> - tls_dtls_connection:prf(Pid, Secret, Label, Seed, WantedLength); + tls_dtls_gen_connection:prf(Pid, Secret, Label, Seed, WantedLength); prf(#sslsocket{pid = {_Listen, #config{}}}, _,_,_,_) -> {error, enotconn}. diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index 8d4e383f2fa9..cc200bb01646 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -38,7 +38,8 @@ init/1]). %% TLS connection setup --export([init_ssl_config/3, +-export([opposite_role/1, + init_ssl_config/3, ssl_config/3, connect/8, handshake/7, @@ -148,17 +149,32 @@ init([Role, _Sender, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = tls_client_connection_1_3:init(InitArgs); {?SERVER_ROLE, #{versions := [?TLS_1_3]}} -> tls_server_connection_1_3:init(InitArgs); - {_,_} -> - tls_connection:init(InitArgs) + {?CLIENT_ROLE,_} -> + tls_client_connection:init(InitArgs); + {?SERVER_ROLE,_} -> + tls_server_connection:init(InitArgs) end; -init([_Role, _Host, _Port, _Socket, _TLSOpts, _User, _CbInfo] = InitArgs) -> +init([Role, _Host, _Port, _Socket, _TLSOpts, _User, _CbInfo] = InitArgs) -> process_flag(trap_exit, true), - dtls_connection:init(InitArgs). + case Role of + ?CLIENT_ROLE -> + dtls_client_connection:init(InitArgs); + ?SERVER_ROLE -> + dtls_server_connection:init(InitArgs) + end. %%==================================================================== %% TLS connection setup %%==================================================================== +%%-------------------------------------------------------------------- +-spec opposite_role(client | server) -> client | server. +%%-------------------------------------------------------------------- +opposite_role(client) -> + server; +opposite_role(server) -> + client. + %%-------------------------------------------------------------------- -spec init_ssl_config(ssl_options(), client | server, #state{}) -> #state{}. %%-------------------------------------------------------------------- @@ -471,119 +487,12 @@ handle_sni_extension(#sni{hostname = Hostname}, State0) -> #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -initial_hello({call, From}, {start, Timeout}, - #state{static_env = #static_env{role = client = Role, - host = Host, - port = Port, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState0}, - connection_env = CEnv, - ssl_options = #{%% Use highest version in initial ClientHello. - %% Versions is a descending list of supported versions. - versions := [HelloVersion|_] = Versions, - session_tickets := SessionTickets, - early_data := EarlyData} = SslOpts, - session = Session, - connection_states = ConnectionStates0 - } = State0) -> - - KeyShare = maybe_generate_client_shares(SslOpts), - %% Update UseTicket in case of automatic session resumption. The automatic ticket handling - %% also takes it into account if the ticket is suitable for sending early data not exceeding - %% the max_early_data_size or if it can only be used for session resumption. - {UseTicket, State1} = tls_client_connection_1_3:maybe_automatic_session_resumption(State0), - TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), - OcspNonce = tls_handshake:ocsp_nonce(SslOpts), - Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, - Renegotiation, - KeyShare, - TicketData, - OcspNonce, - CertDbHandle, - CertDbRef - ), - - %% Early Data Indication - Hello1 = tls_handshake_1_3:maybe_add_early_data_indication(Hello0, - EarlyData, - HelloVersion), - - %% Update pre_shared_key extension with binders (TLS 1.3) - Hello2 = tls_handshake_1_3:maybe_add_binders(Hello1, TicketData, HelloVersion), - - MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined), - ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), - State2 = State1#state{connection_states = ConnectionStates1, - connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}, - - State3 = Connection:queue_handshake(Hello2, State2), - - %% 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), - - {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), - try - %% Send Early Data - State4 = Maybe(tls_client_connection_1_3:maybe_send_early_data(State3)), - - {#state{handshake_env = HsEnv1} = State5, _} = - Connection:send_handshake_flight(State4), - - OcspStaplingKeyPresent = maps:is_key(ocsp_stapling, SslOpts), - State = State5#state{ - connection_env = CEnv#connection_env{ - negotiated_version = RequestedVersion}, - session = Session, - handshake_env = - HsEnv1#handshake_env{ - ocsp_stapling_state = - OcspState0#{ocsp_nonce => OcspNonce, - ocsp_stapling => OcspStaplingKeyPresent}}, - start_or_recv_from = From, - key_share = KeyShare}, - NextState = next_statem_state(Versions, Role), - Connection:next_event(NextState, no_record, State, - [{{timeout, handshake}, Timeout, close}]) - catch - {Ref, #alert{} = Alert} -> - handle_own_alert(Alert, init, State0#state{start_or_recv_from = From}) - end; -initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - ssl_options = #{versions := Versions}} = State0) -> - - NextState = next_statem_state(Versions, Role), - Connection:next_event(NextState, no_record, State0#state{start_or_recv_from = From}, - [{{timeout, handshake}, Timeout, close}]); - -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:update_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); + handle_call(Msg, From, initial_hello, State); initial_hello({call, From}, _Msg, _State) -> {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]}; initial_hello(info, {'DOWN', _, _, _, _} = Event, State) -> - handle_info(Event, ?FUNCTION_NAME, State); + handle_info(Event, initial_hello, State); initial_hello(_Type, _Event, _State) -> {keep_state_and_data, [postpone]}. @@ -599,9 +508,9 @@ config_error({call, From}, {start, _Timeout}, 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}}]}; + {next_state, config_error, State, [{reply, From, {error, closed}}]}; config_error(info, {'DOWN', _, _, _, _} = Event, State) -> - handle_info(Event, ?FUNCTION_NAME, State); + handle_info(Event, config_error, State); config_error(_Type, _Event, _State) -> {keep_state_and_data, [postpone]}. @@ -614,30 +523,30 @@ connection({call, RecvFrom}, {recv, N, Timeout}, socket_options = #socket_options{active = false}} = State0) -> passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, + start_or_recv_from = RecvFrom}, connection, 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}}]); + hibernate_after(connection, 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}}]); + hibernate_after(connection, 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}}]); + hibernate_after(connection, 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}}]); + hibernate_after(connection, 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, + hibernate_after(connection, 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, + hibernate_after(connection, State, [{reply, From, {ok, SelectedProtocol}}]); connection({call, From}, {close,{NewController, Timeout}}, @@ -712,7 +621,7 @@ connection({call, From}, ktls_handover, #state{ end, {stop_and_reply, {shutdown, ktls}, [{reply, From, Reply}]}; connection({call, From}, Msg, State) -> - handle_call(Msg, From, ?FUNCTION_NAME, State); + handle_call(Msg, From, connection, State); connection(cast, {dist_handshake_complete, DHandle}, #state{ssl_options = #{erl_dist := true}, static_env = #static_env{protocol_cb = Connection}, @@ -727,12 +636,12 @@ connection(cast, {dist_handshake_complete, DHandle}, {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:handle_info(Msg, connection, 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, []); + passive_receive(State, connection, Connection, []); connection(Type, Msg, State) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + handle_common_event(Type, Msg, connection, State). %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), #state{}) -> @@ -763,12 +672,12 @@ downgrade(info, {CloseTag, Socket}, State) -> {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State}; downgrade(info, Info, State) -> - tls_gen_connection:handle_info(Info, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Info, downgrade, State); downgrade(Type, Event, State) -> try - tls_dtls_connection:?FUNCTION_NAME(Type, Event, State) + tls_dtls_gen_connection:downgrade(Type, Event, State) catch throw:#alert{} = Alert -> - handle_own_alert(Alert, ?FUNCTION_NAME, State) + handle_own_alert(Alert, downgrade, State) end. %%==================================================================== @@ -1131,7 +1040,8 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, 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}}); + 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}, @@ -1298,23 +1208,6 @@ format_status(terminate, [_, StateName, State]) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -next_statem_state([Version], client) -> - case ssl:tls_version(Version) of - ?TLS_1_3 -> - wait_sh; - _ -> - hello - end; -next_statem_state([Version], server) -> - case ssl:tls_version(Version) of - ?TLS_1_3 -> - start; - _ -> - hello - end; -next_statem_state(_, _) -> - hello. - call(FsmPid, Event) -> try gen_statem:call(FsmPid, Event) catch @@ -1416,8 +1309,9 @@ maybe_exclude_tlsv1(Versions, Options) -> Options end. -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer; - Initiater == internal -> +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), @@ -1438,9 +1332,10 @@ no_records(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{socket_tls_closed = true}, - user_data_buffer = {_,0,_}} = State) -> +handle_active_option(_, connection = StateName, To, Reply, + #state{static_env = #static_env{role = Role}, + connection_env = #connection_env{socket_tls_closed = 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}]}; @@ -1901,11 +1796,6 @@ invalidate_session(client, 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, @@ -2122,10 +2012,7 @@ 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); @@ -2223,14 +2110,6 @@ keylog_secret(SecretBin, sha384) -> keylog_secret(SecretBin, sha512) -> io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]). -maybe_generate_client_shares(#{versions := [?TLS_1_3|_], - supported_groups := - #supported_groups{ - supported_groups = [Group|_]}}) -> - %% 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/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 05a084a9f268..6852273e45b9 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -3835,9 +3835,10 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR customize_hostname_check := CustomizeHostnameCheck, crl_check := CrlCheck, log_level := Level} = Opts, - #{cert_ext := CertExt, - ocsp_responder_certs := OcspResponderCerts, - ocsp_state := OcspState}) -> + OCSPInfo) -> + CertExt = maps:get(cert_ext, OCSPInfo, undefined), + OcspState = maps:get(oscp_state, OCSPInfo, #{ocsp_stapling => false}), + OcspResponderCerts = maps:get(ocsp_responder_certs, OCSPInfo, undefined), SignAlgos = maps:get(signature_algs, Opts, undefined), SignAlgosCert = maps:get(signature_algs_cert, Opts, undefined), ValidationFunAndState = diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_client_connection.erl similarity index 50% rename from lib/ssl/src/tls_connection.erl rename to lib/ssl/src/tls_client_connection.erl index 83ebdbd167cc..04a74760476f 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_client_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2023. All Rights Reserved. +%% Copyright Ericsson AB 2023-2023. 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. @@ -21,27 +21,27 @@ %%---------------------------------------------------------------------- %% Purpose: TLS-1.0-TLS-1.2 FSM (* = optional) %% %%---------------------------------------------------------------------- -%% TLS Handshake protocol full Handshake +%% TLS Handshake protocol full Handshake %% Client Server %% %% ClientHello --------> Flight 1 %% ServerHello \ %% Certificate* \ %% ServerKeyExchange* Flight 2 -%% CertificateRequest* / +%% CertificateRequest* / %% <-------- ServerHelloDone / %% Certificate* \ %% ClientKeyExchange \ %% CertificateVerify* Flight 3 part 1 -%% [ChangeCipherSpec] / +%% [ChangeCipherSpec] / %% NextProtocol* %% Finished --------> / Flight 3 part 2 -%% [ChangeCipherSpec] +%% [ChangeCipherSpec] %% <-------- Finished Flight 4 %% Application Data <-------> Application Data %% %% -%% TLS Handshake protocol abbreviated Handshake +%% TLS Handshake protocol abbreviated Handshake %% Client Server %% %% ClientHello --------> Abbrev Flight 1 @@ -53,12 +53,12 @@ %% Finished --------> Abbrev Flight 3 %% Application Data <-------> Application Data %% -%% -%% -%% Start FSM ---> CONFIG_ERROR +%% +%% +%% Start FSM ---> CONFIG_ERROR %% Send error to user %% | and shutdown -%% | +%% | %% V %% INITIAL_HELLO %% @@ -68,21 +68,24 @@ %% USER_HELLO | %% <- Possibly let user provide V %% options after looking at hello ex -> HELLO -%% | Send/Recv Flight 2 or Abbrev Flight 1 - Abbrev Flight 2 part 1 +%% | Send/Recv Flight 2 or Abbrev Flight 1 - +%% | Abbrev Flight 2 part 1 %% | %% New session | Resumed session %% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED -%% WAIT_CERT_VERIFY +%% %% <- Possibly Receive -- | | -%% OCSP Staple/CertVerify -> | Flight 3 part 1 | +%% OCSP Staple -> | Flight 3 part 1 | %% | | -%% V | Abbrev Flight 2 part 2 to Abbrev Flight 3 +%% V | Abbrev Flight 2 +%% | part 2 to Abbrev +%% | Flight 3 %% CIPHER | %% | | %% | | -%% | Fligth 3 part 2 to Flight 4 | -%% | | -%% V V +%% | Fligth 3 part 2 to Flight 4 | +%% | | +%% V V %% ---------------------------------------------------- %% | %% | @@ -94,7 +97,7 @@ %% GO BACK TO HELLO %%---------------------------------------------------------------------- --module(tls_connection). +-module(tls_client_connection). -behaviour(gen_statem). @@ -114,9 +117,6 @@ %% Setup -export([init/1]). --export([renegotiate/2, - choose_tls_fsm/2]). - %% gen_statem state functions -export([initial_hello/3, config_error/3, @@ -125,7 +125,6 @@ user_hello/3, wait_ocsp_stapling/3, certify/3, - wait_cert_verify/3, cipher/3, abbreviated/3, connection/3]). @@ -138,26 +137,26 @@ %% Tracing -export([handle_trace/3]). + %%==================================================================== %% Internal application API -%%==================================================================== +%%==================================================================== + init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> - State0 = initial_state(Role, Sender, Host, Port, Socket, Options, User, CbInfo), + State0 = tls_dtls_gen_connection:initial_state(Role, Sender, Host, Port, + Socket, Options, User, CbInfo), try State1 = #state{static_env = #static_env{session_cache = Cache, session_cache_cb = CacheCb }, connection_env = #connection_env{cert_key_alts = CertKeyAlts}, ssl_options = SslOptions, - session = Session0} = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, Role, State0), - State = case Role of - client -> - CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), - Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0, CertKeyPairs), - State1#state{session = Session}; - server -> - State1 - end, + session = Session0} = + ssl_gen_statem:init_ssl_config(State0#state.ssl_options, Role, State0), + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), + Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, + CacheCb, Session0, CertKeyPairs), + State = State1#state{session = Session}, tls_gen_connection:initialize_tls_sender(State), gen_statem:enter_loop(?MODULE, [], initial_hello, State) catch throw:Error -> @@ -166,87 +165,139 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> gen_statem:enter_loop(?MODULE, [], config_error, EState) end. -renegotiate(#state{static_env = #static_env{role = client}, - handshake_env = HsEnv} = State, Actions) -> - %% Handle same way as if server requested - %% the renegotiation - Hs0 = ssl_handshake:init_handshake_history(), - {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, - [{next_event, internal, #hello_request{}} | Actions]}; -renegotiate(#state{static_env = #static_env{role = server, - socket = Socket, - transport_cb = Transport}, - handshake_env = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0} = State0, Actions) -> - HelloRequest = ssl_handshake:hello_request(), - Frag = tls_handshake:encode_handshake(HelloRequest, Version), - Hs0 = ssl_handshake:init_handshake_history(), - {BinMsg, ConnectionStates} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), - tls_socket:send(Transport, Socket, BinMsg), - State = State0#state{connection_states = - ConnectionStates, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, - tls_gen_connection:next_event(hello, no_record, State, Actions). - %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- + %%-------------------------------------------------------------------- -spec initial_hello(gen_statem:event_type(), {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -initial_hello(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). - +initial_hello({call, From}, {start, Timeout}, + #state{static_env = #static_env{host = Host, + port = Port, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState0}, + connection_env = CEnv, + ssl_options = #{%% Use highest version in initial ClientHello. + %% Versions is a descending list of supported versions. + versions := [HelloVersion|_] = Versions, + session_tickets := SessionTickets, + early_data := EarlyData} = SslOpts, + session = Session, + connection_states = ConnectionStates0 + } = State0) -> + + KeyShare = maybe_generate_client_shares(SslOpts), + %% Update UseTicket in case of automatic session resumption. The automatic ticket handling + %% also takes it into account if the ticket is suitable for sending early data not exceeding + %% the max_early_data_size or if it can only be used for session resumption. + {UseTicket, State1} = tls_client_connection_1_3:maybe_automatic_session_resumption(State0), + TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), + OcspNonce = tls_handshake:ocsp_nonce(SslOpts), + Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Session#session.session_id, + Renegotiation, + KeyShare, + TicketData, + OcspNonce, + CertDbHandle, + CertDbRef + ), + + %% Early Data Indication + Hello1 = tls_handshake_1_3:maybe_add_early_data_indication(Hello0, + EarlyData, + HelloVersion), + + %% Update pre_shared_key extension with binders (TLS 1.3) + Hello2 = tls_handshake_1_3:maybe_add_binders(Hello1, TicketData, HelloVersion), + + MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined), + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), + State2 = State1#state{connection_states = ConnectionStates1, + connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}, + + State3 = Connection:queue_handshake(Hello2, State2), + + %% 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), + + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + %% Send Early Data + State4 = Maybe(tls_client_connection_1_3:maybe_send_early_data(State3)), + + {#state{handshake_env = HsEnv1} = State5, _} = + Connection:send_handshake_flight(State4), + + OcspStaplingKeyPresent = maps:is_key(ocsp_stapling, SslOpts), + State = State5#state{ + connection_env = CEnv#connection_env{ + negotiated_version = RequestedVersion}, + session = Session, + handshake_env = + HsEnv1#handshake_env{ + ocsp_stapling_state = + OcspState0#{ocsp_nonce => OcspNonce, + ocsp_stapling => OcspStaplingKeyPresent}}, + start_or_recv_from = From, + key_share = KeyShare}, + NextState = next_statem_state(Versions), + Connection:next_event(NextState, no_record, State, + [{{timeout, handshake}, Timeout, close}]) + catch + {Ref, #alert{} = Alert} -> + ssl_gen_statem:handle_own_alert(Alert, init, State0#state{start_or_recv_from = From}) + 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:update_options(Opts, Role, OrigSSLOptions), + State = ssl_gen_statem: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(Event, Msg, State) -> + ssl_gen_statem:initial_hello(Event, Msg, State). + %%-------------------------------------------------------------------- -spec config_error(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> + {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- config_error(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). - + ssl_gen_statem:config_error(Type, Event, State). + %%-------------------------------------------------------------------- -spec hello(gen_statem:event_type(), - #hello_request{} | #client_hello{} | #server_hello{} | term(), + #hello_request{} | #server_hello{} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -hello(internal, #client_hello{extensions = Extensions}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{continue_status = pause}, - start_or_recv_from = From} = State) -> - {next_state, user_hello, State#state{start_or_recv_from = undefined}, - [{postpone, true}, {reply, From, {ok, Extensions}}]}; hello(internal, #server_hello{extensions = Extensions}, #state{handshake_env = #handshake_env{continue_status = pause}, - static_env = #static_env{role = client}, start_or_recv_from = From} = State) -> {next_state, user_hello, - State#state{start_or_recv_from = undefined}, [{postpone, true}, {reply, From, {ok, Extensions}}]}; -hello(internal, #client_hello{client_version = ClientVersion} = Hello, - #state{static_env = #static_env{role = server}, connection_env = CEnv} = State0) -> - try - #state{ssl_options = SslOpts} = State1 = tls_dtls_connection:handle_sni_extension(State0, Hello), - case choose_tls_fsm(SslOpts, Hello) of - tls_1_3_fsm -> - {next_state, start, State1, - [{change_callback_module, tls_server_connection_1_3}, {next_event, internal, Hello}]}; - tls_1_0_to_1_2_fsm -> - {ServerHelloExt, Type, State} = handle_client_hello(Hello, State1), - {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]} - end - catch throw:#alert{} = Alert -> - AlertState = State0#state{connection_env = CEnv#connection_env{negotiated_version = ClientVersion}}, - ssl_gen_statem:handle_own_alert(Alert, hello, AlertState) - end; + State#state{start_or_recv_from = undefined}, [{postpone, true}, + {reply, From, {ok, Extensions}}]}; hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, connection_env = CEnv, - static_env = #static_env{role = client}, handshake_env = #handshake_env{ ocsp_stapling_state = OcspState0, renegotiation = {Renegotiation, _}} = HsEnv, @@ -256,7 +307,7 @@ hello(internal, #server_hello{} = Hello, case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of %% Legacy TLS 1.2 and older {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} -> - tls_dtls_connection:handle_session( + tls_dtls_client_connection:handle_session( Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State#state{ handshake_env = @@ -278,94 +329,68 @@ hello(internal, #server_hello{} = Hello, ssl_gen_statem:handle_own_alert(Alert, hello, State) end; hello(info, Event, State) -> - tls_gen_connection:handle_info(Event, ?FUNCTION_NAME, State); + tls_gen_connection:gen_info(Event, hello, State); hello(Type, Event, State) -> - try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. + gen_state(hello, Type, Event, State). +%%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- user_hello(Type, Event, State) -> - try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. + gen_state(user_hello, Type, Event, State). %%-------------------------------------------------------------------- -spec abbreviated(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- abbreviated(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + tls_gen_connection:gen_info(Event, abbreviated, State); abbreviated(Type, Event, State) -> - try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. + gen_state(abbreviated, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_ocsp_stapling(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + tls_gen_connection:gen_info(Event, wait_ocsp_stapling, State); wait_ocsp_stapling(Type, Event, State) -> - try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. + gen_state(wait_ocsp_stapling, Type, Event, State). %%-------------------------------------------------------------------- -spec certify(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- certify(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + tls_gen_connection:gen_info(Event, certify, State); certify(Type, Event, State) -> - try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. - - -%%-------------------------------------------------------------------- --spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cert_verify(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -wait_cert_verify(Type, Event, State) -> - try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. + gen_state(certify, Type, Event, State). %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- cipher(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + tls_gen_connection:gen_info(Event, cipher, State); + cipher(Type, Event, State) -> - try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. + gen_state(cipher, Type, Event, State). %%-------------------------------------------------------------------- --spec connection(gen_statem:event_type(), +-spec connection(gen_statem:event_type(), #hello_request{} | #client_hello{}| term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- connection(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); -connection({call, From}, {user_renegotiate, WriteState}, + tls_gen_connection:gen_info(Event, connection, State); +connection({call, From}, {user_renegotiate, WriteState}, #state{connection_states = ConnectionStates} = State) -> - {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}}, + {next_state, connection, + State#state{connection_states = ConnectionStates#{current_write => WriteState}}, [{next_event,{call, From}, renegotiate}]}; connection(internal, #hello_request{}, - #state{static_env = #static_env{role = client, - host = Host, + #state{static_env = #static_env{host = Host, port = Port, cert_db = CertDbHandle, cert_db_ref = CertDbRef, @@ -376,22 +401,26 @@ connection(internal, #hello_request{}, ocsp_stapling_state = OcspState}, connection_env = #connection_env{cert_key_alts = CertKeyAlts}, session = Session0, - ssl_options = SslOpts, + ssl_options = SslOpts, protocol_specific = #{sender := Pid}, connection_states = ConnectionStates} = State0) -> try tls_sender:peer_renegotiate(Pid) of {ok, Write} -> CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), + Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, + CacheCb, Session0, CertKeyPairs), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Session#session.session_id, Renegotiation, undefined, - undefined, maps:get(ocsp_nonce, OcspState, undefined), + undefined, maps:get(ocsp_nonce, + OcspState, undefined), CertDbHandle, CertDbRef), - {State, Actions} = tls_gen_connection:send_handshake(Hello, - State0#state{connection_states = - ConnectionStates#{current_write => Write}, - session = Session}), + {State, Actions} = + tls_gen_connection:send_handshake(Hello, + State0#state{connection_states = + ConnectionStates#{current_write + => Write}, + session = Session}), tls_gen_connection:next_event(hello, no_record, State, Actions) catch _:Reason:ST -> @@ -399,8 +428,7 @@ connection(internal, #hello_request{}, {stop, {shutdown, sender_blocked}, State0} end; connection(internal, #hello_request{}, - #state{static_env = #static_env{role = client, - host = Host, + #state{static_env = #static_env{host = Host, port = Port, cert_db = CertDbHandle, cert_db_ref = CertDbRef @@ -408,7 +436,7 @@ connection(internal, #hello_request{}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}, ocsp_stapling_state = OcspState}, - ssl_options = SslOpts, + ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, <<>>, Renegotiation, undefined, @@ -417,44 +445,16 @@ connection(internal, #hello_request{}, {State, Actions} = tls_gen_connection:send_handshake(Hello, State0), tls_gen_connection:next_event(hello, no_record, State, Actions); -connection(internal, #client_hello{} = Hello, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv, - connection_states = CS, - protocol_specific = #{sender := Sender} - } = State) -> - %% Mitigate Computational DoS attack - %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html - %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client - %% initiated renegotiation we will disallow many client initiated - %% renegotiations immediately after each other. - erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - {ok, Write} = tls_sender:renegotiate(Sender), - tls_gen_connection:next_event(hello, no_record, - State#state{connection_states = CS#{current_write => Write}, - handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}, - allow_renegotiate = false} - }, - [{next_event, internal, Hello}]); -connection(internal, #client_hello{}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{allow_renegotiate = false}} = State0) -> - Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - tls_gen_connection:send_alert_in_connection(Alert, State0), - State = tls_gen_connection:reinit_handshake_data(State0), - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); + connection(Type, Event, State) -> - try tls_dtls_connection:?FUNCTION_NAME(Type, Event, State) - catch throw:#alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) - end. + gen_state(connection, Type, Event, State). %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- downgrade(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + ssl_gen_statem:downgrade(Type, Event, State). %-------------------------------------------------------------------- %% gen_statem callbacks @@ -462,12 +462,6 @@ downgrade(Type, Event, State) -> callback_mode() -> state_functions. -terminate({shutdown, {sender_died, Reason}}, _StateName, - #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}} - = State) -> - ssl_gen_statem:handle_trusted_certs_db(State), - tls_gen_connection:close(Reason, Socket, Transport, undefined); terminate(Reason, StateName, State) -> ssl_gen_statem:terminate(Reason, StateName, State). @@ -477,138 +471,44 @@ format_status(Type, Data) -> code_change(_OldVsn, StateName, State, _) -> {ok, StateName, State}. -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User, - {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> - put(log_level, maps:get(log_level, SSLOptions)), - %% Use highest supported version for client/server random nonce generation - #{versions := [Version|_]} = SSLOptions, - BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), - ConnectionStates = tls_record:init_connection_states(Role, - Version, - BeastMitigation), - #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role), - UserMonitor = erlang:monitor(process, User), - InitStatEnv = #static_env{ - role = Role, - transport_cb = CbModule, - protocol_cb = tls_gen_connection, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - passive_tag = PassiveTag, - host = Host, - port = Port, - socket = Socket, - session_cache_cb = SessionCacheCb, - trackers = Trackers - }, - #state{ - static_env = InitStatEnv, - handshake_env = #handshake_env{ - tls_handshake_history = ssl_handshake:init_handshake_history(), - renegotiation = {false, first}, - allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined) - }, - connection_env = #connection_env{user_application = {UserMonitor, User}}, - socket_options = SocketOptions, - ssl_options = SSLOptions, - session = #session{is_resumable = false}, - connection_states = ConnectionStates, - protocol_buffers = #protocol_buffers{}, - user_data_buffer = {[],0,[]}, - start_or_recv_from = undefined, - flight_buffer = [], - protocol_specific = #{sender => Sender, - active_n => ssl_config:get_internal_active_n( - maps:get(erl_dist, SSLOptions, false)), - active_n_toggle => true - } - }. - -handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State) -> - #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 = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, - session = Session0, - ssl_options = SslOpts} = State, - SessionTracker = proplists:get_value(session_id_tracker, Trackers), - {Version, {Type, Session}, - ConnectionStates, Protocol0, ServerHelloExt0, HashSign} = - tls_handshake:hello(Hello, - SslOpts, - {SessionTracker, Session0, - ConnectionStates0, CertKeyAlts, KeyExAlg}, - Renegotiation), - 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 - }}. - - -gen_info(Event, connection = StateName, State) -> - try - tls_gen_connection:handle_info(Event, StateName, State) - catch - _:Reason:ST -> - ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]), - ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, - malformed_data), - StateName, State) - end; -gen_info(Event, StateName, State) -> - try - tls_gen_connection:handle_info(Event, StateName, State) - catch - _:Reason:ST -> - ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), - ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - StateName, State) +%%==================================================================== +%% Internal functions +%%==================================================================== +gen_state(StateName, Type, Event, State) -> + try tls_dtls_client_connection:StateName(Type, Event, State) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, StateName, State) end. -choose_tls_fsm(#{versions := Versions}, - #client_hello{ - extensions = #{client_hello_versions := - #client_hello_versions{versions = ClientVersions} - } - }) -> - case ssl_handshake:select_supported_version(ClientVersions, Versions) of +next_statem_state([Version]) -> + case ssl:tls_version(Version) of ?TLS_1_3 -> - tls_1_3_fsm; - _Else -> - tls_1_0_to_1_2_fsm + wait_sh; + _ -> + hello end; -choose_tls_fsm(_, _) -> - tls_1_0_to_1_2_fsm. +next_statem_state(_) -> + hello. + +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. + +maybe_generate_client_shares(#{versions := [?TLS_1_3|_], + supported_groups := + #supported_groups{ + supported_groups = [Group|_]}}) -> + %% Generate only key_share entry for the most preferred group + ssl_cipher:generate_client_shares([Group]); +maybe_generate_client_shares(_) -> + undefined. + +%%==================================================================== +%% Tracing +%%==================================================================== -%%%################################################################ -%%%# -%%%# Tracing -%%%# handle_trace(csp, {call, {?MODULE, wait_ocsp_stapling, [Type, Event|_]}}, Stack) -> diff --git a/lib/ssl/src/tls_client_connection_1_3.erl b/lib/ssl/src/tls_client_connection_1_3.erl index 8f7486d419f1..8653d721678e 100644 --- a/lib/ssl/src/tls_client_connection_1_3.erl +++ b/lib/ssl/src/tls_client_connection_1_3.erl @@ -125,13 +125,6 @@ init([?CLIENT_ROLE, Sender, Host, Port, Socket, Options, User, CbInfo]) -> EState = State0#state{protocol_specific = Map#{error => Error}}, 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_gen_statem:handle_trusted_certs_db(State), - tls_gen_connection:close(Reason, Socket, Transport, undefined); terminate(Reason, StateName, State) -> ssl_gen_statem:terminate(Reason, StateName, State). @@ -153,7 +146,7 @@ code_change(_OldVsn, StateName, State, _) -> initial_hello(enter, _, State) -> {keep_state, State}; initial_hello(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + tls_client_connection:initial_hello(Type, Event, State). %%-------------------------------------------------------------------- -spec config_error(gen_statem:event_type(), @@ -163,7 +156,7 @@ initial_hello(Type, Event, State) -> config_error(enter, _, State) -> {keep_state, State}; config_error(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + ssl_gen_statem:config_error(Type, Event, State). %%-------------------------------------------------------------------- -spec user_hello(gen_statem:event_type(), @@ -176,7 +169,7 @@ user_hello({call, From}, cancel, State) -> gen_statem:reply(From, ok), ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), - ?FUNCTION_NAME, State); + user_hello, State); user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{handshake_env = #handshake_env{continue_status = pause} = HSEnv, ssl_options = Options0} = State0) -> @@ -192,7 +185,8 @@ user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, catch throw:{error, Reason} -> gen_statem:reply(From, {error, Reason}), - ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0) + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), + user_hello, State0) end; user_hello(Type, Msg, State) -> tls_gen_connection_1_3:user_hello(Type, Msg, State). @@ -204,10 +198,10 @@ user_hello(Type, Msg, State) -> %%-------------------------------------------------------------------- start(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, start, State,[]}; start(internal = Type, #change_cipher_spec{} = Msg, State) -> tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, - ?FUNCTION_NAME, State); + start, State); start(internal, #server_hello{extensions = #{server_hello_selected_version := @@ -221,7 +215,7 @@ start(internal, handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State); false -> ssl_gen_statem:handle_own_alert( - ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State) + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), start, State) end; start(internal, #server_hello{extensions = #{server_hello_selected_version := @@ -240,7 +234,7 @@ start(internal, #server_hello{extensions = {reply, From, {ok, Extensions}}]}; false -> ssl_gen_statem:handle_own_alert( - ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State) + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), start, State) end; start(internal, #server_hello{} = ServerHello, #state{handshake_env = @@ -250,11 +244,11 @@ start(internal, #server_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, %%so it is a previous version hello. ssl_gen_statem:handle_own_alert( - ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0); + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), start, State0); start(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, start, State); start(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, start, State). %%-------------------------------------------------------------------- -spec wait_sh(gen_statem:event_type(), @@ -263,10 +257,10 @@ start(Type, Msg, State) -> %%-------------------------------------------------------------------- wait_sh(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_sh, State,[]}; wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)-> tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, - ?FUNCTION_NAME, State); + wait_sh, State); wait_sh(internal, #server_hello{extensions = Extensions}, #state{handshake_env = #handshake_env{continue_status = pause}, start_or_recv_from = From} = State) -> @@ -303,9 +297,9 @@ wait_sh(internal, #server_hello{} = Hello, no_record, State1) end; wait_sh(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_sh, State); wait_sh(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_sh, State). %%-------------------------------------------------------------------- -spec hello_middlebox_assert(gen_statem:event_type(), @@ -316,14 +310,15 @@ hello_middlebox_assert(enter, _, State) -> {keep_state, State}; hello_middlebox_assert(internal, #change_cipher_spec{}, State) -> tls_gen_connection:next_event(wait_ee, no_record, State); -hello_middlebox_assert(internal = Type, #encrypted_extensions{} = Msg, #state{ssl_options = #{log_level := Level}} = State) -> +hello_middlebox_assert(internal = Type, #encrypted_extensions{} = Msg, + #state{ssl_options = #{log_level := Level}} = State) -> ssl_logger:log(warning, Level, #{description => "Failed to assert middlebox server message", reason => [{missing, #change_cipher_spec{}}]}, ?LOCATION), - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State); + ssl_gen_statem:handle_common_event(Type, Msg, hello_middlebox_assert, State); hello_middlebox_assert(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, hello_middlebox_assert, State); hello_middlebox_assert(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, hello_middlebox_assert, State). %%-------------------------------------------------------------------- -spec hello_retry_middlebox_assert(gen_statem:event_type(), @@ -335,14 +330,15 @@ hello_retry_middlebox_assert(enter, _, State) -> {keep_state, State}; hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) -> tls_gen_connection:next_event(wait_sh, no_record, State); -hello_retry_middlebox_assert(internal = Type, #server_hello{} = Msg, #state{ssl_options = #{log_level := Level}} = State) -> +hello_retry_middlebox_assert(internal = Type, #server_hello{} = Msg, + #state{ssl_options = #{log_level := Level}} = State) -> ssl_logger:log(warning, Level, #{description => "Failed to assert middlebox server message", reason => [{missing, #change_cipher_spec{}}]}, ?LOCATION), - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State); + ssl_gen_statem:handle_common_event(Type, Msg, hello_retry_middlebox_assert, State); hello_retry_middlebox_assert(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, hello_retry_middlebox_assert, State); hello_retry_middlebox_assert(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, hello_retry_middlebox_assert, State). %%-------------------------------------------------------------------- -spec wait_ee(gen_statem:event_type(), @@ -352,21 +348,21 @@ hello_retry_middlebox_assert(Type, Msg, State) -> %%-------------------------------------------------------------------- wait_ee(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_ee, State,[]}; wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) -> tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, - ?FUNCTION_NAME, State); + wait_ee, State); wait_ee(internal, #encrypted_extensions{extensions = Extensions}, State0) -> case handle_encrypted_extensions(Extensions, State0) of #alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0); + ssl_gen_statem:handle_own_alert(Alert, wait_ee, State0); {State, NextState} -> tls_gen_connection:next_event(NextState, no_record, State) end; wait_ee(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_ee, State); wait_ee(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_ee, State). %%-------------------------------------------------------------------- -spec wait_cert_cr(gen_statem:event_type(), @@ -375,10 +371,10 @@ wait_ee(Type, Msg, State) -> %%-------------------------------------------------------------------- wait_cert_cr(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_cert_cr, State,[]}; wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg, State) -> tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, - ?FUNCTION_NAME, State); + wait_cert_cr, State); wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) -> case handle_certificate(Certificate, State0) of {#alert{} = Alert, State} -> @@ -395,9 +391,9 @@ wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, tls_gen_connection:next_event(NextState, no_record, State1) end; wait_cert_cr(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_cert_cr, State); wait_cert_cr(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_cert_cr, State). %%-------------------------------------------------------------------- -spec wait_cert(gen_statem:event_type(), @@ -434,10 +430,10 @@ wait_cv(Type, Msg, State) -> %%-------------------------------------------------------------------- wait_finished(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_finished, State,[]}; wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) -> tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, - ?FUNCTION_NAME, State); + wait_finished, State); wait_finished(internal, #finished{verify_data = VerifyData}, #state{static_env = #static_env{protocol_cb = Connection}} @@ -469,12 +465,12 @@ wait_finished(internal, [{{timeout, handshake}, cancel}]) catch {Ref, #alert{} = Alert} -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0) + ssl_gen_statem:handle_own_alert(Alert, wait_finished, State0) end; wait_finished(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_finished, State); wait_finished(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_finished, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), term(), #state{}) -> diff --git a/lib/ssl/src/tls_dtls_client_connection.erl b/lib/ssl/src/tls_dtls_client_connection.erl new file mode 100644 index 000000000000..8349a49ba1b4 --- /dev/null +++ b/lib/ssl/src/tls_dtls_client_connection.erl @@ -0,0 +1,825 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2023-2023. 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: Common handling of a TLS/SSL/DTLS connection, see also +%% tls_connection.erl and dtls_connection.erl +%% +%% NOTE: All alerts are thrown out of this module +%%---------------------------------------------------------------------- + +-module(tls_dtls_client_connection). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-include("ssl_api.hrl"). +-include("tls_connection.hrl"). +-include("ssl_connection.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). + + +%% General state handlingfor TLS-1.0 to TLS-1.2 +-export([hello/3, + user_hello/3, + abbreviated/3, + certify/3, + wait_ocsp_stapling/3, + cipher/3, + connection/3, + downgrade/3]). + +%% Help functions for tls|dtls_client|server_connection.erl +-export([handle_session/7]). + +%%==================================================================== +%% gen_statem general state functions +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #server_hello{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello(internal, #hello_request{}, _) -> + keep_state_and_data; +hello(Type, Event, State) -> + tls_dtls_gen_connection:hello(Type, Event, State). + +%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +user_hello(Type, Event, State) -> + tls_dtls_gen_connection:user_hello(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), + #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated(internal, #hello_request{}, _) -> + keep_state_and_data; +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{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) -> + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, + get_pending_prf(ConnectionStates0, write), + MasterSecret, Hist0) of + verified -> + ConnectionStates1 = + ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), + {#state{handshake_env = HsEnv} = State1, Actions} = + tls_dtls_gen_connection:finalize_handshake( + State0#state{connection_states = ConnectionStates1}, + abbreviated, 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 -> + throw(Alert) + end; +abbreviated(Type, Event, State) -> + tls_dtls_gen_connection:abbreviated(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec wait_ocsp_stapling(gen_statem:event_type(), + #certificate{} | #certificate_status{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +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 + {next_state, wait_ocsp_stapling, State, [{postpone, true}]}; +%% Receive OCSP staple message +wait_ocsp_stapling(internal, #certificate_status{} = CertStatus, + #state{static_env = #static_env{protocol_cb = _Connection}, + handshake_env = + #handshake_env{ocsp_stapling_state = OcspState} = HsEnv} = State) -> + {next_state, certify, + 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{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) -> + {next_state, certify, + State#state{handshake_env = + HsEnv#handshake_env{ocsp_stapling_state = + OcspState#{ocsp_expect => undetermined}}}, + [{postpone, true}]}; +wait_ocsp_stapling(internal, #hello_request{}, _) -> + keep_state_and_data; +wait_ocsp_stapling(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, wait_ocsp_stapling, State). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), + #hello_request{} | #certificate{} | #server_key_exchange{} | + #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify(internal, #certificate{}, + #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, + 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, + ssl:tls_version(Version), OcspInfo) of + {PeerCert, PublicKeyInfo} -> + tls_dtls_gen_connection:handle_peer_cert(Role, PeerCert, + PublicKeyInfo, State, Connection, []); + #alert{} = Alert -> + throw(Alert) + end; +certify(internal, #server_key_exchange{exchange_keys = Keys}, + #state{static_env = #static_env{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) + when KexAlg == dhe_dss; + KexAlg == dhe_rsa; + KexAlg == ecdhe_rsa; + KexAlg == ecdhe_ecdsa; + KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == rsa_psk; + KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + + Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)), + + %% Use negotiated value if TLS-1.2 otherwise return default + HashSign = tls_dtls_gen_connection:negotiated_hashsign(Params#server_key_params.hashsign, + KexAlg, PubKeyInfo, + ssl:tls_version(Version)), + + case tls_dtls_gen_connection:is_anonymous(KexAlg) of + true -> + calculate_secret(Params#server_key_params.params, + State#state{handshake_env = + HsEnv#handshake_env{hashsign_algorithm = HashSign}}, + Connection); + false -> + case ssl_handshake:verify_server_key(Params, HashSign, + ConnectionStates, ssl:tls_version(Version), + PubKeyInfo) of + true -> + calculate_secret(Params#server_key_params.params, + State#state{handshake_env = + HsEnv#handshake_env{hashsign_algorithm + = HashSign}, + session = + session_handle_params( + Params#server_key_params.params, Session)}, + Connection); + false -> + throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) + end + end; +certify(internal, #certificate_request{}, + #state{handshake_env = #handshake_env{kex_algorithm = KexAlg}}) + when KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == rsa_psk; + KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)); +certify(internal, #certificate_request{}, + #state{static_env = #static_env{protocol_cb = Connection}, + session = Session0, + connection_env = #connection_env{cert_key_alts = [#{certs := [[]]}]}} = 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 algorithms needed as there is + %% no certificate to verify. + Connection:next_event(certify, no_record, State#state{client_certificate_status = requested, + session = Session0#session{own_certificates = [[]], + private_key = #{}}}); +certify(internal, #certificate_request{} = CertRequest, + #state{static_env = #static_env{protocol_cb = Connection, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + connection_env = #connection_env{negotiated_version = Version, + cert_key_alts = CertKeyAlts + }, + session = Session0, + ssl_options = #{signature_algs := SupportedHashSigns}} = State) -> + TLSVersion = ssl:tls_version(Version), + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, ssl:tls_version(Version)), + Session = select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, + SupportedHashSigns, TLSVersion, + CertDbHandle, CertDbRef), + Connection:next_event(certify, no_record, + State#state{client_certificate_status = requested, + session = Session}); +%% PSK and RSA_PSK might bypass the Server-Key-Exchange +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{protocol_cb = Connection}, + session = #session{master_secret = undefined}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + premaster_secret = undefined, + server_psk_identity = PSKIdentity} = HsEnv, + ssl_options = #{user_lookup_fun := PSKLookup}} = State0) + when KexAlg == psk -> + case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of + #alert{} = Alert -> + throw(Alert); + PremasterSecret -> + State = master_secret(PremasterSecret, + State0#state{handshake_env = + HsEnv#handshake_env{premaster_secret = PremasterSecret}}), + client_certify_and_key_exchange(State, Connection) + end; +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = {Major, Minor}}, + 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) + when KexAlg == rsa_psk -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + RSAPremasterSecret = <>, + case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup, + RSAPremasterSecret) of + #alert{} = Alert -> + throw(Alert); + PremasterSecret -> + State = master_secret(PremasterSecret, + State0#state{handshake_env = + HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}), + client_certify_and_key_exchange(State, Connection) + end; +%% Master secret was determined with help of server-key exchange msg +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{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) -> + 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 -> + throw(Alert) + end; +%% Master secret is calculated from premaster_secret +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = #handshake_env{premaster_secret = PremasterSecret}, + session = Session0, + connection_states = ConnectionStates0} = State0) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, + ConnectionStates0, client) of + {MasterSecret, ConnectionStates} -> + Session = Session0#session{master_secret = MasterSecret}, + State = State0#state{connection_states = ConnectionStates, + session = Session}, + client_certify_and_key_exchange(State, Connection); + #alert{} = Alert -> + throw(Alert) + end; +certify(internal, #hello_request{}, _) -> + keep_state_and_data; +certify(Type, Event, State) -> + tls_dtls_gen_connection:certify(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher(internal, #hello_request{}, _) -> + keep_state_and_data; +cipher(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{protocol_cb = Connection, + role = Role, + host = Host, + port = Port, + trackers = Trackers}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + expecting_finished = true}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} + = Session0, + ssl_options = SslOpts, + connection_states = ConnectionStates0} = State0) -> + #{security_parameters := SecParams} = ssl_record:current_connection_state(ConnectionStates0, read), + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, + ssl_gen_statem:opposite_role(Role), + SecParams#security_parameters.prf_algorithm, + MasterSecret, Hist) of + verified -> + Session = maybe_register_session(SslOpts, Host, Port, Trackers, Session0), + ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, + ConnectionStates0), + {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}]); + #alert{} = Alert -> + throw(Alert) + end; +cipher(Type, Event, State) -> + tls_dtls_gen_connection:cipher(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv} = State0) -> + State = Connection:reinit_handshake_data(State0#state{handshake_env = + HsEnv#handshake_env{renegotiation = {true, From}}}), + %% Handle same way as if server requested the renegotiation + {next_state, connection, State, [{next_event, internal, #hello_request{}}]}; +connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv, + connection_states = ConnectionStates} + = State0) -> + State1 = State0#state{handshake_env = + HsEnv#handshake_env{renegotiation = {true, internal}}, + connection_states = ConnectionStates#{current_write => WriteState}}, + State = Connection:reinit_handshake_data(State1), + %% Handle same way as if server requested the renegotiation + {next_state, connection, State, [{next_event, internal, #hello_request{}}]}; +connection(internal, {handshake, {#hello_request{} = Handshake, _}}, + #state{handshake_env = HsEnv} = State) -> + %% Should not be included in handshake history + {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}}, + [{next_event, internal, Handshake}]}; +connection(Type, Event, State) -> + tls_dtls_gen_connection:connection(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, downgrade, State). + +%%==================================================================== +%% Help functions for tls|dtls_client_connection.erl +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec handle_session(#server_hello{}, ssl_record:ssl_version(), + binary(), ssl_record:connection_states(), _,_, #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +handle_session(#server_hello{cipher_suite = CipherSuite, + compression_method = Compression}, + Version, NewId, ConnectionStates, ProtoExt, Protocol0, + #state{session = Session, + handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv, + connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) -> + #{key_exchange := KeyAlgorithm} = + ssl_cipher_format:suite_bin_to_map(CipherSuite), + + PremasterSecret = tls_dtls_gen_connection:make_premaster_secret(ReqVersion, KeyAlgorithm), + + {ExpectNPN, Protocol} = + case Protocol0 of + undefined -> {false, CurrentProtocol}; + _ -> {ProtoExt =:= npn, Protocol0} + end, + + State = State0#state{connection_states = ConnectionStates, + handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm, + premaster_secret = PremasterSecret, + expecting_next_protocol_negotiation = ExpectNPN, + negotiated_protocol = Protocol}, + connection_env = CEnv#connection_env{negotiated_version = Version}}, + + case ssl_session:is_new(Session, NewId) of + true -> + handle_new_session(NewId, CipherSuite, Compression, + State#state{connection_states = ConnectionStates}); + false -> + handle_resumed_session(NewId, + State#state{connection_states = ConnectionStates}) + end. + + +%%==================================================================== +%% Internal functions +%%==================================================================== +select_client_cert_key_pair(Session0,_, + [#{private_key := NoKey, certs := [[]] = NoCerts}], + _,_,_,_) -> + %% No certificate supplied : empty certificate will be sent + Session0#session{own_certificates = NoCerts, + private_key = NoKey}; +select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef) -> + select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, undefined). + +select_client_cert_key_pair(Session0,_,[], _, _,_,_, undefined) -> + %% No certificate compliant with supported algorithms: empty certificate will be sent + Session0#session{own_certificates = [[]], + private_key = #{}}; +select_client_cert_key_pair(_,_,[], _, _,_,_,#session{}=Session) -> + %% No certificate compliant with guide lines send default + Session; +select_client_cert_key_pair(Session0, #certificate_request{certificate_authorities = CertAuths} = CertRequest, + [#{private_key := PrivateKey, certs := [Cert| _] = Certs} | Rest], + SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default) -> + case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, TLSVersion) of + #alert{} -> + select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default); + SelectedHashSign -> + case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of + {ok, EncodedChain} -> + Session0#session{sign_alg = SelectedHashSign, + own_certificates = EncodedChain, + private_key = PrivateKey + }; + {error, EncodedChain, not_in_auth_domain} -> + Session = Session0#session{sign_alg = SelectedHashSign, + own_certificates = EncodedChain, + private_key = PrivateKey + }, + select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion, + CertDbHandle, CertDbRef, default_cert_key_pair_return(Default, Session)) + end + end. + +default_cert_key_pair_return(undefined, Session) -> + Session; +default_cert_key_pair_return(Default, _) -> + Default. + +handle_new_session(NewId, CipherSuite, Compression, + #state{static_env = #static_env{protocol_cb = Connection}, + session = Session0 + } = State0) -> + Session = Session0#session{session_id = NewId, + cipher_suite = CipherSuite, + compression_method = Compression}, + Connection:next_event(certify, no_record, State0#state{session = Session}). + +handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, + port = Port, + protocol_cb = Connection, + session_cache = Cache, + session_cache_cb = CacheCb}, + connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0, + ssl_options = Opts} = State) -> + + Session = case maps:get(reuse_session, Opts, undefined) of + {SessId,SessionData} when is_binary(SessId), is_binary(SessionData) -> + binary_to_term(SessionData, [safe]); + _Else -> + CacheCb:lookup(Cache, {{Host, Port}, SessId}) + end, + + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, + ConnectionStates0, client) of + {_, ConnectionStates} -> + Connection:next_event(abbreviated, no_record, State#state{ + connection_states = ConnectionStates, + session = Session}); + #alert{} = Alert -> + throw(Alert) + end. + +calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, + dh_y = ServerPublicDhKey} = Params, + #state{handshake_env = HsEnv} = State, Connection) -> + Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), + PremasterSecret = + ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, + State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, + Connection, certify, certify); +calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, + #state{handshake_env = HsEnv, + session = Session} = State, Connection) -> + ECDHKeys = public_key:generate_key(ECCurve), + PremasterSecret = + ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, + State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, + session = Session#session{ecc = ECCurve}}, + Connection, certify, certify); +calculate_secret(#server_psk_params{ + hint = IdentityHint}, + #state{handshake_env = HsEnv} = State, Connection) -> + %% store for later use + Connection:next_event(certify, no_record, + State#state{handshake_env = + HsEnv#handshake_env{server_psk_identity = IdentityHint}}); +calculate_secret(#server_dhe_psk_params{ + dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, + #state{handshake_env = HsEnv, + ssl_options = #{user_lookup_fun := PSKLookup}} = + State, Connection) -> + Keys = {_, PrivateDhKey} = + crypto:generate_key(dh, [Prime, Base]), + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, + State#state{handshake_env = + HsEnv#handshake_env{kex_keys = Keys}}, + Connection, certify, certify); +calculate_secret(#server_ecdhe_psk_params{ + dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey, + #state{ssl_options = #{user_lookup_fun := PSKLookup}} = + #state{handshake_env = HsEnv, + session = Session} = State, Connection) -> + ECDHKeys = public_key:generate_key(ECCurve), + + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, + State#state{handshake_env = + HsEnv#handshake_env{kex_keys = ECDHKeys}, + session = Session#session{ecc = ECCurve}}, + Connection, certify, certify); +calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey, + #state{handshake_env = HsEnv, + ssl_options = #{srp_identity := SRPId}} = State, + Connection) -> + Keys = generate_srp_client_keys(Generator, Prime, 0), + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, + State#state{handshake_env = + HsEnv#handshake_env{kex_keys = Keys}}, + Connection, certify, certify). + +master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + connection_states = ConnectionStates0} = State) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, + ConnectionStates0, Role) of + {MasterSecret, ConnectionStates} -> + State#state{ + session = + Session#session{master_secret = MasterSecret}, + connection_states = ConnectionStates}; + #alert{} = Alert -> + throw(Alert) + end. + +client_certify_and_key_exchange(State0, Connection) -> + State1 = do_client_certify_and_key_exchange(State0, Connection), + {State2, Actions} = tls_dtls_gen_connection:finalize_handshake(State1, certify, Connection), + State = State2#state{client_certificate_status = not_requested}, %% Reinitialize + Connection:next_event(cipher, no_record, State, Actions). + +do_client_certify_and_key_exchange(State0, Connection) -> + State1 = certify_client(State0, Connection), + State2 = key_exchange(State1, Connection), + verify_client_cert(State2, Connection). + +certify_client(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + client_certificate_status = requested, + session = #session{own_certificates = OwnCerts}} + = State, Connection) -> + Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client), + Connection:queue_handshake(Certificate, State); +certify_client(#state{client_certificate_status = not_requested} = State, _) -> + State. + +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = rsa, + public_key_info = PublicKeyInfo, + premaster_secret = PremasterSecret}, + connection_env = #connection_env{negotiated_version = Version} + } = State0, Connection) -> + Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg, + kex_keys = {DhPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version} + } = State0, Connection) + when KexAlg == dhe_dss; + KexAlg == dhe_rsa; + KexAlg == dh_anon -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg, + kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session + } = State0, Connection) + when KexAlg == ecdhe_ecdsa; + KexAlg == ecdhe_rsa; + KexAlg == ecdh_ecdsa; + KexAlg == ecdh_rsa; + KexAlg == ecdh_anon -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), + Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = psk}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {psk, PSKIdentity}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = dhe_psk, + kex_keys = {DhPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {dhe_psk, + PSKIdentity, DhPubKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, + kex_keys = ECDHKeys}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {ecdhe_psk, + PSKIdentity, ECDHKeys}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = rsa_psk, + public_key_info = PublicKeyInfo, + premaster_secret = PremasterSecret}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} + = State0, Connection) -> + Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity, + PremasterSecret, PublicKeyInfo), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg, + kex_keys = {ClientPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version}} + = State0, Connection) + when KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}), + Connection:queue_handshake(Msg, State0). + +rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) + when Algorithm == ?rsaEncryption; + Algorithm == ?md2WithRSAEncryption; + Algorithm == ?md5WithRSAEncryption; + Algorithm == ?sha1WithRSAEncryption; + Algorithm == ?sha224WithRSAEncryption; + Algorithm == ?sha256WithRSAEncryption; + Algorithm == ?sha384WithRSAEncryption; + Algorithm == ?sha512WithRSAEncryption + -> + ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {premaster_secret, PremasterSecret, + PublicKeyInfo}); +rsa_key_exchange(_, _, _) -> + throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). + +rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, + PublicKeyInfo = {Algorithm, _, _}) + when Algorithm == ?rsaEncryption; + Algorithm == ?md2WithRSAEncryption; + Algorithm == ?md5WithRSAEncryption; + Algorithm == ?sha1WithRSAEncryption; + Algorithm == ?sha224WithRSAEncryption; + Algorithm == ?sha256WithRSAEncryption; + Algorithm == ?sha384WithRSAEncryption; + Algorithm == ?sha512WithRSAEncryption + -> + ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {psk_premaster_secret, PskIdentity, PremasterSecret, + PublicKeyInfo}); +rsa_psk_key_exchange(_, _, _, _) -> + throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). + +generate_srp_client_keys(_Generator, _Prime, 10) -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); +generate_srp_client_keys(Generator, Prime, N) -> + try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) + catch + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]), + generate_srp_client_keys(Generator, Prime, N+1) + end. + +verify_client_cert(#state{handshake_env = #handshake_env{tls_handshake_history = Hist}, + connection_env = #connection_env{negotiated_version = Version}, + client_certificate_status = requested, + session = #session{sign_alg = HashSign, + master_secret = MasterSecret, + private_key = PrivateKey, + own_certificates = OwnCerts}} = State, Connection) -> + case ssl_handshake:client_certificate_verify(OwnCerts, MasterSecret, + ssl:tls_version(Version), HashSign, PrivateKey, Hist) of + #certificate_verify{} = Verified -> + Connection:queue_handshake(Verified, State); + ignore -> + State; + #alert{} = Alert -> + throw(Alert) + end; +verify_client_cert(#state{client_certificate_status = not_requested} = State, _) -> + State. + +get_pending_prf(CStates, Direction) -> + #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction), + SecParams#security_parameters.prf_algorithm. + +ocsp_info(#{ocsp_expect := stapled, ocsp_response := CertStatus} = OcspState, + #{ocsp_stapling := OcspStapling} = _SslOpts, PeerCert) -> + #{ocsp_responder_certs := OcspResponderCerts} = OcspStapling, + #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]}, + ocsp_responder_certs => OcspResponderCerts, + ocsp_state => OcspState}; +ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) -> + #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []}, + ocsp_responder_certs => [], + ocsp_state => OcspState}. + +session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> + Session#session{ecc = ECCurve}; +session_handle_params(_, Session) -> + Session. + +maybe_register_session(#{verify := verify_peer, + reuse_sessions := Reuse} = SslOpts, + Host, Port, _, #session{is_resumable = false} = Session0) when Reuse =/= false -> + Session = Session0#session{is_resumable = true}, + ssl_manager:register_session(host_id(Host, SslOpts), Port, Session, reg_type(Reuse)), + Session; +maybe_register_session(_,_,_,_, Session) -> + Session. + +host_id(_Host, #{server_name_indication := Hostname}) when is_list(Hostname) -> + Hostname; +host_id(Host, _) -> + Host. + +reg_type(save) -> + true; +reg_type(true) -> + unique. diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl deleted file mode 100644 index c2edbffe3000..000000000000 --- a/lib/ssl/src/tls_dtls_connection.erl +++ /dev/null @@ -1,1747 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2013-2023. 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: Common handling of a TLS/SSL/DTLS connection, see also -%% tls_connection.erl and dtls_connection.erl -%% -%% NOTE: All alerts are thrown out of this module -%%---------------------------------------------------------------------- - --module(tls_dtls_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"). --include("ssl_alert.hrl"). --include("ssl_record.hrl"). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_srp.hrl"). - -%% TLS-1.0 to TLS-1.2 Specific User Events --export([renegotiation/1, renegotiation/2, prf/5]). - -%% 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]). - -%% General state handlingfor TLS-1.0 to TLS-1.2 and gen_handshake that wraps -%% handling of common state handling for handshake messages for error handling --export([hello/3, - user_hello/3, - abbreviated/3, - certify/3, - wait_cert_verify/3, - wait_ocsp_stapling/3, - cipher/3, - connection/3, - downgrade/3, - gen_handshake/4]). - -%% Tracing --export([handle_trace/3]). - -%%-------------------------------------------------------------------- --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}). - -%%==================================================================== -%% User events -%%==================================================================== - -%%-------------------------------------------------------------------- --spec renegotiation(pid()) -> ok | {error, reason()}. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -renegotiation(ConnectionPid) -> - ssl_gen_statem:call(ConnectionPid, renegotiate). - -renegotiation(Pid, WriteState) -> - ssl_gen_statem:call(Pid, {user_renegotiate, WriteState}). - -%%-------------------------------------------------------------------- --spec prf(pid(), binary() | 'master_secret', binary(), - [binary() | ssl:prf_random()], non_neg_integer()) -> - {ok, binary()} | {error, reason()} | {'EXIT', term()}. -%% -%% Description: use a ssl sessions TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> - ssl_gen_statem:call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). - -%%==================================================================== -%% Help functions for tls|dtls_connection.erl -%%==================================================================== -%%-------------------------------------------------------------------- --spec handle_session(#server_hello{}, ssl_record:ssl_version(), - binary(), ssl_record:connection_states(), _,_, #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -handle_session(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression}, - Version, NewId, ConnectionStates, ProtoExt, Protocol0, - #state{session = Session, - handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) -> - #{key_exchange := KeyAlgorithm} = - ssl_cipher_format:suite_bin_to_map(CipherSuite), - - PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - - {ExpectNPN, Protocol} = - case Protocol0 of - undefined -> {false, CurrentProtocol}; - _ -> {ProtoExt =:= npn, Protocol0} - end, - - State = State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm, - premaster_secret = PremasterSecret, - expecting_next_protocol_negotiation = ExpectNPN, - negotiated_protocol = Protocol}, - connection_env = CEnv#connection_env{negotiated_version = Version}}, - - case ssl_session:is_new(Session, NewId) of - true -> - handle_new_session(NewId, CipherSuite, Compression, - State#state{connection_states = ConnectionStates}); - false -> - handle_resumed_session(NewId, - State#state{connection_states = ConnectionStates}) - end. - - -%%==================================================================== -%% gen_statem general state functions with connection cb argument -%%==================================================================== - -%%-------------------------------------------------------------------- --spec hello(gen_statem:event_type(), - #hello_request{} | #server_hello{} | term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -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(internal, #hello_request{}, _) -> - keep_state_and_data; -hello(Type, Event, State) -> - ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). - -%%-------------------------------------------------------------------- --spec user_hello(gen_statem:event_type(), - #hello_request{} | term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -user_hello({call, From}, cancel, _State) -> - gen_statem:reply(From, ok), - throw(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled)); -user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, - #state{static_env = #static_env{role = Role}, - handshake_env = HSEnv, - ssl_options = Options0} = State0) -> - try ssl:update_options(NewOptions, Role, Options0) of - Options -> - State = ssl_gen_statem:ssl_config(Options, Role, State0), - {next_state, hello, State#state{start_or_recv_from = From, - handshake_env = HSEnv#handshake_env{continue_status = continue} - }, - [{{timeout, handshake}, Timeout, close}]} - catch - throw:{error, Reason} -> - gen_statem:reply(From, {error, Reason}), - ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0) - end; -user_hello(info, {'DOWN', _, _, _, _} = Event, State) -> - ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State); -user_hello(_, _, _) -> - {keep_state_and_data, [postpone]}. - -%%-------------------------------------------------------------------- --spec abbreviated(gen_statem:event_type(), - #hello_request{} | #finished{} | term(), - #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -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, - 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) -> - 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} = - 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 -> - throw(Alert) - end; -abbreviated(internal, #finished{verify_data = Data} = Finished, - #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) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, - get_pending_prf(ConnectionStates0, write), - MasterSecret, Hist0) of - verified -> - ConnectionStates1 = - ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), - {#state{handshake_env = HsEnv} = State1, Actions} = - finalize_handshake(State0#state{connection_states = ConnectionStates1}, - ?FUNCTION_NAME, 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 -> - throw(Alert) - 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, - 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{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, - handshake_env = HsEnv#handshake_env{expecting_finished = true}}); -abbreviated(info, Msg, State) -> - handle_info(Msg, ?FUNCTION_NAME, State); -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{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -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 - {next_state, ?FUNCTION_NAME, State, [{postpone, true}]}; -%% Receive OCSP staple message -wait_ocsp_stapling(internal, #certificate_status{} = CertStatus, - #state{static_env = #static_env{protocol_cb = _Connection}, - handshake_env = - #handshake_env{ocsp_stapling_state = OcspState} = HsEnv} = State) -> - {next_state, certify, - 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{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) -> - {next_state, certify, - State#state{handshake_env = - HsEnv#handshake_env{ocsp_stapling_state = - OcspState#{ocsp_expect => undetermined}}}, - [{postpone, true}]}; -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{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -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}, - ssl_options = #{verify := verify_peer, fail_if_no_peer_cert := true}}) -> - throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided)); -certify(internal, #certificate{asn1_certificates = []}, - #state{static_env = #static_env{role = server, - protocol_cb = Connection}, - ssl_options = #{verify := verify_peer, - fail_if_no_peer_cert := false}} = - State0) -> - Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_status = empty}); -certify(internal, #certificate{}, - #state{static_env = #static_env{role = server}, - ssl_options = #{verify := verify_none}}) -> - throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate)); -certify(internal, #certificate{}, - #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, - 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} = State0) 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} -> - State = case Role of - server -> - State0#state{client_certificate_status = needs_verifying}; - client -> - State0 - end, - handle_peer_cert(Role, PeerCert, PublicKeyInfo, State, Connection, []); - #alert{} = Alert -> - throw(Alert) - end; -certify(internal, #server_key_exchange{exchange_keys = Keys}, - #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) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdhe_ecdsa; - KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - - Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)), - - %% Use negotiated value if TLS-1.2 otherwise return default - HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)), - - case is_anonymous(KexAlg) of - true -> - calculate_secret(Params#server_key_params.params, - State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection); - false -> - case ssl_handshake:verify_server_key(Params, HashSign, - ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of - true -> - calculate_secret(Params#server_key_params.params, - State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}, - session = session_handle_params(Params#server_key_params.params, Session)}, - Connection); - false -> - throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) - end - end; -certify(internal, #certificate_request{}, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg}}) - when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)); -certify(internal, #certificate_request{}, - #state{static_env = #static_env{role = client, - protocol_cb = Connection}, - session = Session0, - connection_env = #connection_env{cert_key_alts = [#{certs := [[]]}]}} = 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 algorithms needed as there is - %% no certificate to verify. - Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_status = requested, - session = Session0#session{own_certificates = [[]], - private_key = #{}}}); -certify(internal, #certificate_request{} = CertRequest, - #state{static_env = #static_env{role = client, - protocol_cb = Connection, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef}, - connection_env = #connection_env{negotiated_version = Version, - cert_key_alts = CertKeyAlts - }, - session = Session0, - ssl_options = #{signature_algs := SupportedHashSigns}} = State) -> - TLSVersion = ssl:tls_version(Version), - CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, ssl:tls_version(Version)), - Session = select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, - SupportedHashSigns, TLSVersion, - CertDbHandle, CertDbRef), - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{client_certificate_status = requested, - session = Session}); -%% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client, - protocol_cb = Connection}, - session = #session{master_secret = undefined}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - premaster_secret = undefined, - server_psk_identity = PSKIdentity} = HsEnv, - ssl_options = #{user_lookup_fun := PSKLookup}} = State0) - when KexAlg == psk -> - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of - #alert{} = Alert -> - throw(Alert); - PremasterSecret -> - State = master_secret(PremasterSecret, - State0#state{handshake_env = - HsEnv#handshake_env{premaster_secret = PremasterSecret}}), - client_certify_and_key_exchange(State, Connection) - end; -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client, - protocol_cb = Connection}, - connection_env = #connection_env{negotiated_version = {Major, Minor}}, - 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) - when KexAlg == rsa_psk -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - RSAPremasterSecret = <>, - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup, - RSAPremasterSecret) of - #alert{} = Alert -> - throw(Alert); - PremasterSecret -> - State = master_secret(PremasterSecret, - State0#state{handshake_env = - HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}), - client_certify_and_key_exchange(State, Connection) - end; -%% Master secret was determined with help of server-key exchange msg -certify(internal, #server_hello_done{}, - #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) -> - 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 -> - throw(Alert) - end; -%% Master secret is calculated from premaster_secret -certify(internal, #server_hello_done{}, - #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) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - client_certify_and_key_exchange(State, Connection); - #alert{} = Alert -> - throw(Alert) - end; -certify(internal = Type, #client_key_exchange{} = Msg, - #state{static_env = #static_env{role = server}, - client_certificate_status = requested, - ssl_options = #{fail_if_no_peer_cert := true}}) -> - %% We expect a certificate here - throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}})); -certify(internal, #client_key_exchange{exchange_keys = Keys}, - State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg}, - 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 -> - throw(Alert) - end; -certify(internal, #hello_request{}, _) -> - keep_state_and_data; -certify(Type, Event, State) -> - ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). - -%%-------------------------------------------------------------------- --spec wait_cert_verify(gen_statem:event_type(), - #hello_request{} | #certificate_verify{} | term(), - #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cert_verify(internal, #certificate_verify{signature = Signature, - hashsign_algorithm = CertHashSign}, - #state{static_env = #static_env{role = server, - protocol_cb = Connection}, - client_certificate_status = needs_verifying, - handshake_env = #handshake_env{tls_handshake_history = Hist, - kex_algorithm = KexAlg, - public_key_info = PubKeyInfo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} = Session0 - } = State) -> - - TLSVersion = ssl:tls_version(Version), - %% Use negotiated value if TLS-1.2 otherwise return default - HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion), - case ssl_handshake:certificate_verify(Signature, PubKeyInfo, - TLSVersion, HashSign, MasterSecret, Hist) of - valid -> - Connection:next_event(cipher, no_record, - State#state{client_certificate_status = verified, - session = Session0#session{sign_alg = HashSign}}); - #alert{} = Alert -> - throw(Alert) - end; -wait_cert_verify(internal, #hello_request{}, _) -> - keep_state_and_data; -wait_cert_verify(Type, Event, State) -> - ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). - -%%-------------------------------------------------------------------- --spec cipher(gen_statem:event_type(), - #hello_request{} | #finished{} | term(), - #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -cipher({call, From}, Msg, State) -> - handle_call(Msg, From, ?FUNCTION_NAME, State); -cipher(info, Msg, State) -> - handle_info(Msg, ?FUNCTION_NAME, State); -cipher(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = Role, - host = Host, - port = Port, - trackers = Trackers}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - expecting_finished = true} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} - = Session0, - ssl_options = SslOpts, - 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}}); - #alert{} = Alert -> - throw(Alert) - 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, protocol_cb = Connection}, - handshake_env = #handshake_env{expecting_finished = true, - 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, - 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(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{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = tls_gen_connection}, - handshake_env = HsEnv} = State) -> - tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []); -connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = dtls_gen_connection}, - handshake_env = HsEnv} = State) -> - dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = undefined, - 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) -> - 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) -> - ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, - [{reply, From, {ok, SelectedProtocol}}]); -connection({call, From}, Msg, State) when element(1, Msg) =:= prf -> - handle_call(Msg, From, ?FUNCTION_NAME, State); -connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = tls_gen_connection}, - handshake_env = HsEnv, - connection_states = ConnectionStates} - = State) -> - tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, - connection_states = ConnectionStates#{current_write => WriteState}}, []); -connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = dtls_gen_connection}, - handshake_env = HsEnv, - connection_states = ConnectionStates} - = State) -> - dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, - connection_states = ConnectionStates#{current_write => WriteState}}, []); - -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{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -downgrade(Type, Event, State) -> - ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). - -gen_handshake(StateName, Type, Event, State) -> - try - tls_dtls_connection:StateName(Type, Event, State) - catch error:Reason:ST -> - ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)) - end. - -%%-------------------------------------------------------------------- -%% Event handling functions called by state functions to handle -%% common or unexpected events for the 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:Reason:ST -> - ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), - {error, badarg}; - error:Reason:ST -> - ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), - {error, Reason} - end, - {keep_state_and_data, [{reply, From, Reply}]}; -handle_call(Msg, From, StateName, State) -> - ssl_gen_statem:handle_call(Msg, From, StateName, State). - -handle_info(Msg, StateName, State) -> - ssl_gen_statem:handle_info(Msg, StateName, State). - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -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) 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), - State1 = State0#state{connection_states = ConnectionStates1}, - ServerHello = - ssl_handshake:server_hello(SessId, ssl:tls_version(Version), - ConnectionStates1, ServerHelloExt), - - State = server_hello(ServerHello, - State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation = - NextProtocols =/= undefined}}, Connection), - case Type of - new -> - new_server_hello(ServerHello, State, Connection); - resumed -> - resumed_server_hello(State, Connection) - end. - -update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} = - ReadState0, - pending_write := #{security_parameters := WriteSecParams0} = - WriteState0} = ConnectionStates, - Version, HighestVersion) -> - ReadRandom = override_server_random( - ReadSecParams0#security_parameters.server_random, - Version, - HighestVersion), - WriteRandom = override_server_random( - WriteSecParams0#security_parameters.server_random, - Version, - HighestVersion), - ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom}, - WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom}, - ReadState = ReadState0#{security_parameters => ReadSecParams}, - WriteState = WriteState0#{security_parameters => WriteSecParams}, - - ConnectionStates#{pending_read => ReadState, pending_write => WriteState}. - -%% TLS 1.3 - Section 4.1.3 -%% -%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes -%% of their Random value to the bytes: -%% -%% 44 4F 57 4E 47 52 44 01 -%% -%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2 -%% servers SHOULD set the last eight bytes of their Random value to the -%% bytes: -%% -%% 44 4F 57 4E 47 52 44 00 -override_server_random(<> = Random, {M,N}, {Major,Minor}) - when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above - if M =:= 3 andalso N =:= 3 -> %% Negotiating TLS 1.2 - Down = ?RANDOM_OVERRIDE_TLS12, - <>; - M =:= 3 andalso N < 3 -> %% Negotiating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <>; - true -> - Random - end; -override_server_random(<> = Random, {M,N}, {Major,Minor}) - when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 - if M =:= 3 andalso N < 3 -> %% Negotiating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <>; - true -> - Random - end; -override_server_random(Random, _, _) -> - Random. - -new_server_hello(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression, - session_id = SessionId}, - #state{session = Session0, - static_env = #static_env{protocol_cb = Connection}} = State0, Connection) -> - #state{} = State1 = server_certify_and_key_exchange(State0, Connection), - {State, Actions} = server_hello_done(State1, Connection), - Session = Session0#session{session_id = SessionId, - cipher_suite = CipherSuite, - compression_method = Compression}, - Connection:next_event(certify, no_record, State#state{session = Session}, Actions). - -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, - ConnectionStates0, server) of - {_, ConnectionStates1} -> - State1 = State0#state{connection_states = ConnectionStates1, - session = Session}, - {State, Actions} = - finalize_handshake(State1, abbreviated, Connection), - Connection:next_event(abbreviated, no_record, State, Actions); - #alert{} = Alert -> - throw(Alert) - end. - -server_hello(ServerHello, State0, Connection) -> - CipherSuite = ServerHello#server_hello.cipher_suite, - #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}. - -server_hello_done(State, Connection) -> - HelloDone = ssl_handshake:server_hello_done(), - Connection:send_handshake(HelloDone, State). - -handle_peer_cert(Role, PeerCert, PublicKeyInfo, - #state{handshake_env = HsEnv, - 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 = - Session#session{peer_certificate = PeerCert}}, - #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), - Connection:next_event(certify, no_record, State, Actions). - -handle_peer_cert_key(client, _, - {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, - PublicKeyParams}, - KeyAlg, #state{handshake_env = HsEnv, - session = Session} = State) when KeyAlg == ecdh_rsa; - KeyAlg == ecdh_ecdsa -> - ECDHKey = public_key:generate_key(PublicKeyParams), - PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey), - master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey}, - session = Session#session{ecc = PublicKeyParams}}); -handle_peer_cert_key(_, _, _, _, State) -> - State. - -certify_client(#state{static_env = #static_env{role = client, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef}, - client_certificate_status = requested, - session = #session{own_certificates = OwnCerts}} - = State, Connection) -> - Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client), - Connection:queue_handshake(Certificate, State); -certify_client(#state{client_certificate_status = not_requested} = State, _) -> - State. - -verify_client_cert(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{tls_handshake_history = Hist}, - connection_env = #connection_env{negotiated_version = Version}, - client_certificate_status = requested, - session = #session{sign_alg = HashSign, - master_secret = MasterSecret, - private_key = PrivateKey, - own_certificates = OwnCerts}} = State, Connection) -> - case ssl_handshake:client_certificate_verify(OwnCerts, MasterSecret, - ssl:tls_version(Version), HashSign, PrivateKey, Hist) of - #certificate_verify{} = Verified -> - Connection:queue_handshake(Verified, State); - ignore -> - State; - #alert{} = Alert -> - throw(Alert) - end; -verify_client_cert(#state{client_certificate_status = not_requested} = State, _) -> - State. - -client_certify_and_key_exchange(State0, Connection) -> - State1 = do_client_certify_and_key_exchange(State0, Connection), - {State2, Actions} = finalize_handshake(State1, certify, Connection), - State = State2#state{client_certificate_status = not_requested}, %% Reinitialize - Connection:next_event(cipher, no_record, State, Actions). - -do_client_certify_and_key_exchange(State0, Connection) -> - State1 = certify_client(State0, Connection), - State2 = key_exchange(State1, Connection), - verify_client_cert(State2, Connection). - -server_certify_and_key_exchange(State0, Connection) -> - State1 = certify_server(State0, Connection), - State2 = key_exchange(State1, Connection), - request_client_cert(State2, Connection). - -certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{session = #session{private_key = PrivateKey}, - handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}, - client_certificate_status = CCStatus} - = State, Connection) -> - FakeSecret = make_premaster_secret(Version, rsa), - %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret - %% and fail handshake later.RFC 5246 section 7.4.7.1. - PremasterSecret = - try ssl_handshake:premaster_secret(EncPMS, PrivateKey) of - Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> - case Secret of - <> -> %% Correct - <>; - <> -> %% Version mismatch - <> - end; - _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES - FakeSecret - catch - #alert{description = ?DECRYPT_ERROR} -> - FakeSecret - end, - calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); -certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, - #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}}, - client_certificate_status = CCStatus - } = State, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), - calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); - -certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{handshake_env = #handshake_env{kex_keys = ECDHKey}, - client_certificate_status = CCStatus - } = State, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), - calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); -certify_client_key_exchange(#client_psk_identity{} = ClientKey, - #state{ssl_options = - #{user_lookup_fun := PSKLookup}, - client_certificate_status = CCStatus - } = State0, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)); -certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, - #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}}, - ssl_options = - #{user_lookup_fun := PSKLookup}, - client_certificate_status = CCStatus - } = State0, - Connection) -> - PremasterSecret = - ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)); -certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, - #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, - ssl_options = - #{user_lookup_fun := PSKLookup}, - client_certificate_status = CCStatus - } = State, - Connection) -> - PremasterSecret = - ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), - calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); -certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, - #state{session = #session{private_key = PrivateKey}, - ssl_options = - #{user_lookup_fun := PSKLookup}, - client_certificate_status = CCStatus - } = State0, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PrivateKey, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)); -certify_client_key_exchange(#client_srp_public{} = ClientKey, - #state{handshake_env = #handshake_env{srp_params = Params, - kex_keys = Key}, - client_certificate_status = CCStatus - } = State0, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), - calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)). - -client_kex_next_state(needs_verifying) -> - wait_cert_verify; -client_kex_next_state(empty) -> - cipher; -client_kex_next_state(not_requested) -> - cipher. - -certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} = - State, _) when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == srp_anon -> - State; -certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, - cert_db_ref = CertDbRef}, - session = #session{own_certificates = OwnCerts}} = State, Connection) -> - Cert = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, server), - #certificate{} = Cert, %% Assert - Connection:queue_handshake(Cert, State). - -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - diffie_hellman_params = #'DHParameter'{} = Params, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == dh_anon -> - DHKeys = public_key:generate_key(Params), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv, - session = #session{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key} = Session} = State, _) - when KexAlg == ecdh_ecdsa; - KexAlg == ecdh_rsa -> - State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key}, - session = Session#session{ecc = ECCurve}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{ecc = ECCCurve, private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) - when KexAlg == ecdhe_ecdsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdh_anon -> - - ECDHKeys = public_key:generate_key(ECCCurve), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = psk}, - ssl_options = #{psk_identity := undefined}} = State, _) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) -> - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = dhe_psk, - diffie_hellman_params = #'DHParameter'{} = Params, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - DHKeys = public_key:generate_key(Params), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {dhe_psk, - PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{ecc = ECCCurve, private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - ECDHKeys = public_key:generate_key(ECCCurve), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {ecdhe_psk, - PskIdentityHint, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk}, - ssl_options = #{psk_identity := undefined}} = State, _) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{user_lookup_fun := LookupFun}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{srp_username = Username, private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) - when KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - SrpParams = handle_srp_identity(Username, LookupFun), - Keys = generate_srp_server_keys(SrpParams, 0), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams, - kex_keys = Keys}}; -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = rsa, - public_key_info = PublicKeyInfo, - premaster_secret = PremasterSecret}, - connection_env = #connection_env{negotiated_version = Version} - } = State0, Connection) -> - Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = {DhPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version} - } = State0, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == dh_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session - } = State0, Connection) - when KexAlg == ecdhe_ecdsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdh_ecdsa; - KexAlg == ecdh_rsa; - KexAlg == ecdh_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), - Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = psk}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {psk, PSKIdentity}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = dhe_psk, - kex_keys = {DhPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {dhe_psk, - PSKIdentity, DhPubKey}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, - kex_keys = ECDHKeys}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {ecdhe_psk, - PSKIdentity, ECDHKeys}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk, - public_key_info = PublicKeyInfo, - premaster_secret = PremasterSecret}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} - = State0, Connection) -> - Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity, - PremasterSecret, PublicKeyInfo), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = {ClientPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version}} - = State0, Connection) - when KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}), - Connection:queue_handshake(Msg, State0). - -rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {premaster_secret, PremasterSecret, - PublicKeyInfo}); -rsa_key_exchange(_, _, _) -> - throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). - -rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, - PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {psk_premaster_secret, PskIdentity, PremasterSecret, - PublicKeyInfo}); -rsa_psk_key_exchange(_, _, _, _) -> - throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). - -request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _) - when Alg == dh_anon; - Alg == ecdh_anon; - Alg == psk; - Alg == dhe_psk; - Alg == ecdhe_psk; - Alg == rsa_psk; - Alg == srp_dss; - Alg == srp_rsa; - Alg == srp_anon -> - State; - -request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, - cert_db_ref = CertDbRef}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_peer} = Opts} = State0, Connection) -> - SupportedHashSigns = maps:get(signature_algs, Opts, undefined), - TLSVersion = ssl:tls_version(Version), - HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, - TLSVersion), - Msg = ssl_handshake:certificate_request(CertDbHandle, CertDbRef, - HashSigns, TLSVersion), - State = Connection:queue_handshake(Msg, State0), - State#state{client_certificate_status = requested}; - -request_client_cert(#state{ssl_options = #{verify := verify_none}} = - State, _) -> - State. - -calculate_master_secret(PremasterSecret, - #state{connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0, - session = Session0} = State0, Connection, - _Current, Next) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, server) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - Connection:next_event(Next, no_record, State); - #alert{} = Alert -> - throw(Alert) - end. - -finalize_handshake(State0, StateName, Connection) -> - #state{connection_states = ConnectionStates0} = - State1 = cipher_protocol(State0, Connection), - - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, - write, Connection), - - State2 = State1#state{connection_states = ConnectionStates}, - State = next_protocol(State2, Connection), - finished(State, StateName, Connection). - -next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) -> - NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), - Connection:queue_handshake(NextProtocolMessage, State0). - -cipher_protocol(State, Connection) -> - Connection:queue_change_cipher(#change_cipher_spec{}, State). - -finished(#state{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{tls_handshake_history = Hist}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates0} = State0, - StateName, Connection) -> - MasterSecret = Session#session.master_secret, - Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, - get_current_prf(ConnectionStates0, write), - MasterSecret, Hist), - ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), - Connection:send_handshake(Finished, State0#state{connection_states = - ConnectionStates}). - -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> - ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> - ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). - -calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, - dh_y = ServerPublicDhKey} = Params, - #state{handshake_env = HsEnv} = State, Connection) -> - Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), - PremasterSecret = - ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, - Connection, certify, certify); - -calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - #state{handshake_env = HsEnv, - session = Session} = State, Connection) -> - ECDHKeys = public_key:generate_key(ECCurve), - PremasterSecret = - ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, - session = Session#session{ecc = ECCurve}}, - Connection, certify, certify); - -calculate_secret(#server_psk_params{ - hint = IdentityHint}, - #state{handshake_env = HsEnv} = State, Connection) -> - %% store for later use - Connection:next_event(certify, no_record, - State#state{handshake_env = - HsEnv#handshake_env{server_psk_identity = IdentityHint}}); - -calculate_secret(#server_dhe_psk_params{ - dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, - #state{handshake_env = HsEnv, - ssl_options = #{user_lookup_fun := PSKLookup}} = - State, Connection) -> - Keys = {_, PrivateDhKey} = - crypto:generate_key(dh, [Prime, Base]), - PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup), - calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, - Connection, certify, certify); - -calculate_secret(#server_ecdhe_psk_params{ - dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey, - #state{ssl_options = #{user_lookup_fun := PSKLookup}} = - #state{handshake_env = HsEnv, - session = Session} = State, Connection) -> - ECDHKeys = public_key:generate_key(ECCurve), - - PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, - session = Session#session{ecc = ECCurve}}, - Connection, certify, certify); - -calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey, - #state{handshake_env = HsEnv, - ssl_options = #{srp_identity := SRPId}} = State, - Connection) -> - Keys = generate_srp_client_keys(Generator, Prime, 0), - PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId), - calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection, - certify, certify). - -master_secret(#alert{} = Alert, _) -> - throw(Alert); -master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates0} = State) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, Role) of - {MasterSecret, ConnectionStates} -> - State#state{ - session = - Session#session{master_secret = MasterSecret}, - connection_states = ConnectionStates}; - #alert{} = Alert -> - throw(Alert) - end. - -generate_srp_server_keys(_SrpParams, 10) -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); -generate_srp_server_keys(SrpParams = - #srp_user{generator = Generator, prime = Prime, - verifier = Verifier}, N) -> - try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) - catch - error:Reason:ST -> - ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]), - generate_srp_server_keys(SrpParams, N+1) - end. - -generate_srp_client_keys(_Generator, _Prime, 10) -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); -generate_srp_client_keys(Generator, Prime, N) -> - try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) - catch - error:Reason:ST -> - ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]), - generate_srp_client_keys(Generator, Prime, N+1) - end. - -handle_srp_identity(Username, {Fun, UserState}) -> - case Fun(srp, Username, UserState) of - {ok, {SRPParams, Salt, DerivedKey}} - when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> - {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), - Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), - #srp_user{generator = Generator, prime = Prime, - salt = Salt, verifier = Verifier}; - #alert{} = Alert -> - throw(Alert); - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end. - - -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} = 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{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} = 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; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_anon -> - true; -is_anonymous(_) -> - false. - -get_current_prf(CStates, Direction) -> - #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction), - SecParams#security_parameters.prf_algorithm. -get_pending_prf(CStates, Direction) -> - #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction), - SecParams#security_parameters.prf_algorithm. - -opposite_role(client) -> - server; -opposite_role(server) -> - client. - - - -session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> - Session#session{ecc = ECCurve}; -session_handle_params(_, Session) -> - Session. - -handle_session(server, #{reuse_sessions := true}, - _Host, _Port, Trackers, #session{is_resumable = false} = Session) -> - Tracker = proplists:get_value(session_id_tracker, Trackers), - server_register_session(Tracker, Session#session{is_resumable = true}); -handle_session(Role = client, #{verify := verify_peer, - reuse_sessions := Reuse} = SslOpts, - Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false -> - client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true}, - reg_type(Reuse)); -handle_session(_,_,_,_,_, Session) -> - Session. - -reg_type(save) -> - true; -reg_type(true) -> - unique. - -client_register_session(Host, Port, Session, Save) -> - ssl_manager:register_session(Host, Port, Session, Save), - Session. -server_register_session(Tracker, Session) -> - ssl_server_session_cache:register_session(Tracker, Session), - Session. - -host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) -> - Hostname; -host_id(_, Host, _) -> - Host. - -handle_new_session(NewId, CipherSuite, Compression, - #state{static_env = #static_env{protocol_cb = Connection}, - session = Session0 - } = State0) -> - Session = Session0#session{session_id = NewId, - cipher_suite = CipherSuite, - compression_method = Compression}, - Connection:next_event(certify, no_record, State0#state{session = Session}). - -handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, - port = Port, - protocol_cb = Connection, - session_cache = Cache, - session_cache_cb = CacheCb}, - connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0, - ssl_options = Opts} = State) -> - - Session = case maps:get(reuse_session, Opts, undefined) of - {SessId,SessionData} when is_binary(SessId), is_binary(SessionData) -> - binary_to_term(SessionData, [safe]); - _Else -> - CacheCb:lookup(Cache, {{Host, Port}, SessId}) - end, - - case ssl_handshake:master_secret(ssl:tls_version(Version), Session, - ConnectionStates0, client) of - {_, ConnectionStates} -> - Connection:next_event(abbreviated, no_record, State#state{ - connection_states = ConnectionStates, - session = Session}); - #alert{} = Alert -> - throw(Alert) - end. - -make_premaster_secret(Version, rsa) -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - {MajVer,MinVer} = Version, - <>; -make_premaster_secret(_, _) -> - undefined. - -negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) -> - %% Not negotiated choose default - case is_anonymous(KexAlg) of - true -> - {null, anon}; - false -> - {PubAlg, _, _} = PubKeyInfo, - ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version) - end; -negotiated_hashsign(HashSign = {_, _}, _, _, _) -> - HashSign. - -%% 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 ssl_gen_statem:handle_sni_extension(PossibleSNI, State0) of - {ok, State} -> - State; - {error, #alert{}=Alert} -> - throw(Alert) - end. - -ensure_tls(Version) when ?DTLS_1_X(Version) -> - dtls_v1:corresponding_tls_version(Version); -ensure_tls(Version) -> - Version. - -ocsp_info(#{ocsp_expect := stapled, ocsp_response := CertStatus} = OcspState, - #{ocsp_stapling := OcspStapling} = _SslOpts, PeerCert) -> - #{ocsp_responder_certs := OcspResponderCerts} = OcspStapling, - #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]}, - ocsp_responder_certs => OcspResponderCerts, - ocsp_state => OcspState}; -ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) -> - #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []}, - ocsp_responder_certs => [], - ocsp_state => OcspState}. - -select_client_cert_key_pair(Session0,_, - [#{private_key := NoKey, certs := [[]] = NoCerts}], - _,_,_,_) -> - %% No certificate supplied : empty certificate will be sent - Session0#session{own_certificates = NoCerts, - private_key = NoKey}; -select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef) -> - select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, undefined). - -select_client_cert_key_pair(Session0,_,[], _, _,_,_, undefined) -> - %% No certificate compliant with supported algorithms: empty certificate will be sent - Session0#session{own_certificates = [[]], - private_key = #{}}; -select_client_cert_key_pair(_,_,[], _, _,_,_,#session{}=Session) -> - %% No certificate compliant with guide lines send default - Session; -select_client_cert_key_pair(Session0, #certificate_request{certificate_authorities = CertAuths} = CertRequest, - [#{private_key := PrivateKey, certs := [Cert| _] = Certs} | Rest], - SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default) -> - case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, TLSVersion) of - #alert{} -> - select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default); - SelectedHashSign -> - case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of - {ok, EncodedChain} -> - Session0#session{sign_alg = SelectedHashSign, - own_certificates = EncodedChain, - private_key = PrivateKey - }; - {error, EncodedChain, not_in_auth_domain} -> - Session = Session0#session{sign_alg = SelectedHashSign, - own_certificates = EncodedChain, - private_key = PrivateKey - }, - select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion, - CertDbHandle, CertDbRef, default_cert_key_pair_return(Default, Session)) - end - end. - -default_cert_key_pair_return(undefined, Session) -> - Session; -default_cert_key_pair_return(Default, _) -> - Default. - -%%%################################################################ -%%%# -%%%# Tracing -%%%# -handle_trace(csp, - {call, {?MODULE, wait_ocsp_stapling, [Type, Msg | _]}}, Stack) -> - {io_lib:format("Type = ~w Msg = ~W", [Type, Msg, 10]), Stack}. diff --git a/lib/ssl/src/tls_dtls_gen_connection.erl b/lib/ssl/src/tls_dtls_gen_connection.erl new file mode 100644 index 000000000000..1cc892874c2b --- /dev/null +++ b/lib/ssl/src/tls_dtls_gen_connection.erl @@ -0,0 +1,479 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2023. 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: Common handling of a TLS/SSL/DTLS connection, see also +%% tls_connection.erl and dtls_connection.erl +%% +%% NOTE: All alerts are thrown out of this module +%%---------------------------------------------------------------------- + +-module(tls_dtls_gen_connection). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-include("ssl_api.hrl"). +-include("tls_connection.hrl"). +-include("ssl_connection.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). + +%% TLS-1.0 to TLS-1.2 Specific User Events +-export([internal_renegotiation/2, + renegotiation/1, + renegotiation/2, + prf/5, + is_anonymous/1]). + +%% Help functions for tls|dtls*_connection.erl +-export([initial_state/8, + negotiated_hashsign/4, + finalize_handshake/3, + make_premaster_secret/2, + handle_peer_cert/6, + calculate_master_secret/5 + ]). + +%% General state handlingfor TLS-1.0 to TLS-1.2 +-export([hello/3, + user_hello/3, + abbreviated/3, + certify/3, + cipher/3, + connection/3, + downgrade/3]). + +%%-------------------------------------------------------------------- +-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}). + +%%==================================================================== +%% User events +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec renegotiation(pid()) -> ok | {error, reason()}. +%% +%% Description: Starts a renegotiation of the ssl session. +%%-------------------------------------------------------------------- +renegotiation(ConnectionPid) -> + ssl_gen_statem:call(ConnectionPid, renegotiate). + +renegotiation(Pid, WriteState) -> + ssl_gen_statem:call(Pid, {user_renegotiate, WriteState}). + +%%-------------------------------------------------------------------- +-spec prf(pid(), binary() | 'master_secret', binary(), + [binary() | ssl:prf_random()], non_neg_integer()) -> + {ok, binary()} | {error, reason()} | {'EXIT', term()}. +%% +%% Description: use a ssl sessions TLS PRF to generate key material +%%-------------------------------------------------------------------- +prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> + ssl_gen_statem:call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). + + +is_anonymous(KexAlg) when KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == rsa_psk; + KexAlg == srp_anon -> + true; +is_anonymous(_) -> + false. + +make_premaster_secret(Version, rsa) -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + {MajVer,MinVer} = Version, + <>; +make_premaster_secret(_, _) -> + undefined. + +handle_peer_cert(Role, PeerCert, PublicKeyInfo, + #state{handshake_env = HsEnv, + 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 = + Session#session{peer_certificate = PeerCert}}, + #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), + Connection:next_event(certify, no_record, State, Actions). + +handle_peer_cert_key(client, _, + {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, + PublicKeyParams}, + KeyAlg, #state{handshake_env = HsEnv, + session = Session} = State) when KeyAlg == ecdh_rsa; + KeyAlg == ecdh_ecdsa -> + ECDHKey = public_key:generate_key(PublicKeyParams), + PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey), + master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey}, + session = Session#session{ecc = PublicKeyParams}}); +handle_peer_cert_key(_, _, _, _, State) -> + State. + +%%==================================================================== +%% Help functions for tls|dtls_connection.erl +%%==================================================================== + +initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User, + {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + put(log_level, maps:get(log_level, SSLOptions)), + %% Use highest supported version for client/server random nonce generation + #{versions := [Version|_]} = SSLOptions, + BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), + ConnectionStates = tls_record:init_connection_states(Role, + Version, + BeastMitigation), + #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role), + UserMonitor = erlang:monitor(process, User), + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = tls_gen_connection, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + passive_tag = PassiveTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb, + trackers = Trackers + }, + #state{ + static_env = InitStatEnv, + handshake_env = #handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + renegotiation = {false, first}, + allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined) + }, + connection_env = #connection_env{user_application = {UserMonitor, User}}, + socket_options = SocketOptions, + ssl_options = SSLOptions, + session = #session{is_resumable = false}, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_data_buffer = {[],0,[]}, + start_or_recv_from = undefined, + flight_buffer = [], + protocol_specific = #{sender => Sender, + active_n => ssl_config:get_internal_active_n( + maps:get(erl_dist, SSLOptions, false)), + active_n_toggle => true + } + }. + +negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) -> + %% Not negotiated choose default + case is_anonymous(KexAlg) of + true -> + {null, anon}; + false -> + {PubAlg, _, _} = PubKeyInfo, + ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version) + end; +negotiated_hashsign(HashSign = {_, _}, _, _, _) -> + HashSign. + +finalize_handshake(State0, StateName, Connection) -> + #state{connection_states = ConnectionStates0} = + State1 = cipher_protocol(State0, Connection), + + ConnectionStates = + ssl_record:activate_pending_connection_state(ConnectionStates0, + write, Connection), + + State2 = State1#state{connection_states = ConnectionStates}, + State = next_protocol(State2, Connection), + finished(State, StateName, Connection). + +calculate_master_secret(PremasterSecret, + #state{connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0, + session = Session0} = State0, Connection, + _Current, Next) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, + ConnectionStates0, server) of + {MasterSecret, ConnectionStates} -> + Session = Session0#session{master_secret = MasterSecret}, + State = State0#state{connection_states = ConnectionStates, + session = Session}, + Connection:next_event(Next, no_record, State); + #alert{} = Alert -> + throw(Alert) + end. + +%%==================================================================== +%% gen_statem general state functions +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #server_hello{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello({call, From}, Msg, State) -> + handle_call(Msg, From, hello, State); +hello(internal, #hello_request{}, _) -> + keep_state_and_data; +hello(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, hello, State). + +%%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), + #hello_request{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +user_hello({call, From}, cancel, _State) -> + gen_statem:reply(From, ok), + throw(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled)); +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{static_env = #static_env{role = Role}, + handshake_env = HSEnv, + ssl_options = Options0} = State0) -> + try ssl:update_options(NewOptions, Role, Options0) of + Options -> + State = ssl_gen_statem:ssl_config(Options, Role, State0), + {next_state, hello, + State#state{start_or_recv_from = From, + handshake_env = + HSEnv#handshake_env{continue_status = continue} + }, + [{{timeout, handshake}, Timeout, close}]} + catch + throw:{error, Reason} -> + gen_statem:reply(From, {error, Reason}), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), + user_hello, State0) + end; +user_hello(info, {'DOWN', _, _, _, _} = Event, State) -> + ssl_gen_statem:handle_info(Event, user_hello, State); +user_hello(_, _, _) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated({call, From}, Msg, State) -> + handle_call(Msg, From, abbreviated, State); +abbreviated(internal, + #change_cipher_spec{type = <<1>>}, + #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(abbreviated, no_record, + State#state{connection_states = + ConnectionStates1, + handshake_env = + HsEnv#handshake_env{expecting_finished = true}}); +abbreviated(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, abbreviated, State). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), + #hello_request{} | #certificate{} | #server_key_exchange{} | + #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify({call, From}, Msg, State) -> + handle_call(Msg, From, certify, State); +certify(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, certify, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher({call, From}, Msg, State) -> + handle_call(Msg, From, cipher, State); +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(cipher, no_record, + State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true}, + connection_states = ConnectionStates}); +cipher(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, cipher, State). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- + +connection({call, From}, renegotiate, + #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv} = State) -> + Connection:renegotiate(State#state{handshake_env = + HsEnv#handshake_env{renegotiation = {true, From}}}, []); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = undefined}} = State) -> + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, {error, protocol_not_negotiated}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = SelectedProtocol}} = State) -> + ssl_gen_statem:hibernate_after(connection, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = SelectedProtocol, + negotiated_protocol = undefined}} = State) -> + ssl_gen_statem:hibernate_after(connection, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, Msg, State) when element(1, Msg) =:= prf -> + handle_call(Msg, From, connection, State); +connection(internal, {handshake, {#hello_request{} = Handshake, _}}, + #state{handshake_env = HsEnv} = State) -> + %% Should not be included in handshake history + {next_state, connection, + State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}}, + [{next_event, internal, Handshake}]}; +connection(Type, Event, State) -> + ssl_gen_statem:connection(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, downgrade, State). + +%%-------------------------------------------------------------------- +%% Event handling functions called by state functions to handle +%% common or unexpected events for the 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:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + {error, badarg}; + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + {error, Reason} + end, + {keep_state_and_data, [{reply, From, Reply}]}; +handle_call(Msg, From, StateName, State) -> + ssl_gen_statem:handle_call(Msg, From, StateName, State). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> + State; +next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> + State; +next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) -> + State; +next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) -> + NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), + Connection:queue_handshake(NextProtocolMessage, State0). + +cipher_protocol(State, Connection) -> + Connection:queue_change_cipher(#change_cipher_spec{}, State). + +finished(#state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{tls_handshake_history = Hist}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + connection_states = ConnectionStates0} = State0, + StateName, Connection) -> + MasterSecret = Session#session.master_secret, + #{security_parameters := SecParams} = ssl_record:current_connection_state(ConnectionStates0, write), + Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, + SecParams#security_parameters.prf_algorithm, + MasterSecret, Hist), + ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), + Connection:send_handshake(Finished, State0#state{connection_states = + ConnectionStates}). + +save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> + ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); +save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> + ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); +save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> + ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); +save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> + ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). + + +master_secret(#alert{} = Alert, _) -> + throw(Alert); +master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + connection_states = ConnectionStates0} = State) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, + ConnectionStates0, Role) of + {MasterSecret, ConnectionStates} -> + State#state{ + session = + Session#session{master_secret = MasterSecret}, + connection_states = ConnectionStates}; + #alert{} = Alert -> + throw(Alert) + end. diff --git a/lib/ssl/src/tls_dtls_server_connection.erl b/lib/ssl/src/tls_dtls_server_connection.erl new file mode 100644 index 000000000000..ca0ce6fd73cc --- /dev/null +++ b/lib/ssl/src/tls_dtls_server_connection.erl @@ -0,0 +1,799 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2023-2023. 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: Common handling of a TLS/SSL/DTLS connection, see also +%% tls_connection.erl and dtls_connection.erl +%% +%% NOTE: All alerts are thrown out of this module +%%---------------------------------------------------------------------- + +-module(tls_dtls_server_connection). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-include("ssl_api.hrl"). +-include("tls_connection.hrl"). +-include("ssl_connection.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). + +%% General state handlingfor TLS-1.0 to TLS-1.2 +-export([hello/3, + user_hello/3, + abbreviated/3, + certify/3, + wait_cert_verify/3, + cipher/3, + connection/3, + downgrade/3]). + +%% Help functions for tls|dtls*_connection.erl +-export([handle_sni_extension/2]). + +%%==================================================================== +%% gen_statem general state functions with connection cb argument +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello(internal, {common_client_hello, Type, ServerHelloExt}, State) -> + do_server_hello(Type, ServerHelloExt, State); +hello(Type, Event, State) -> + tls_dtls_gen_connection:hello(Type, Event, State). + +%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +user_hello(Type, Event, State) -> + tls_dtls_gen_connection:user_hello(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), + #finished{} | #next_protocol{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{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) -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates0, write), + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client, + SecParams#security_parameters.prf_algorithm, + MasterSecret, Hist) of + verified -> + ConnectionStates = + ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), + {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 -> + throw(Alert) + 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{protocol_cb = Connection}, + handshake_env = + #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} + = State) -> + NewHSEnv = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, + expecting_next_protocol_negotiation = false}, + Connection:next_event(abbreviated, no_record, + State#state{handshake_env = NewHSEnv}); +abbreviated(Type, Event, State) -> + tls_dtls_gen_connection:abbreviated(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), + #hello_request{} | #certificate{} | #server_key_exchange{} | + #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify(internal, #certificate{asn1_certificates = []}, + #state{ssl_options = #{verify := verify_peer, fail_if_no_peer_cert := true}}) -> + throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided)); +certify(internal, #certificate{asn1_certificates = []}, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + ssl_options = #{verify := verify_peer, + fail_if_no_peer_cert := false}} = + State0) -> + Connection:next_event(certify, no_record, + State0#state{client_certificate_status = empty}); +certify(internal, #certificate{}, + #state{ssl_options = #{verify := verify_none}}) -> + throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate)); +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}, + connection_env = #connection_env{ + negotiated_version = Version}, + ssl_options = Opts} = State0) -> + %% Dummy OCSP info + OcspInfo = ocsp_info(#{ocsp_state => #{ocsp_stapling => false}}, Opts, Peer), + case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, + Opts, CRLDbInfo, Role, Host, + ssl:tls_version(Version), + OcspInfo) of + {PeerCert, PublicKeyInfo} -> + State = State0#state{client_certificate_status = needs_verifying}, + tls_dtls_gen_connection:handle_peer_cert(Role, PeerCert, PublicKeyInfo, State, + Connection, []); + #alert{} = Alert -> + throw(Alert) + end; +certify(internal = Type, #client_key_exchange{} = Msg, + #state{client_certificate_status = requested, + ssl_options = #{fail_if_no_peer_cert := true}}) -> + %% We expect a certificate here + throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}})); +certify(internal, #client_key_exchange{exchange_keys = Keys}, + State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg}, + 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 -> + throw(Alert) + end; +certify(Type, Event, State) -> + tls_dtls_gen_connection:certify(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec wait_cert_verify(gen_statem:event_type(), + #hello_request{} | #certificate_verify{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cert_verify(internal, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, + #state{static_env = #static_env{protocol_cb = Connection}, + client_certificate_status = needs_verifying, + handshake_env = #handshake_env{tls_handshake_history = Hist, + kex_algorithm = KexAlg, + public_key_info = PubKeyInfo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} = Session0 + } = State) -> + + TLSVersion = ssl:tls_version(Version), + %% Use negotiated value if TLS-1.2 otherwise return default + HashSign = tls_dtls_gen_connection:negotiated_hashsign(CertHashSign, KexAlg, + PubKeyInfo, TLSVersion), + case ssl_handshake:certificate_verify(Signature, PubKeyInfo, + TLSVersion, HashSign, MasterSecret, Hist) of + valid -> + Connection:next_event(cipher, no_record, + State#state{client_certificate_status = verified, + session = Session0#session{sign_alg = HashSign}}); + #alert{} = Alert -> + throw(Alert) + end; +wait_cert_verify(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, wait_cert_verify, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +%% 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{protocol_cb = Connection}, + handshake_env = #handshake_env{expecting_finished = true, + expecting_next_protocol_negotiation = true} = HsEnv} + = State) -> + NewHSEnv = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, + expecting_next_protocol_negotiation = false}, + Connection:next_event(cipher, no_record, + State#state{handshake_env = NewHSEnv}); +cipher(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{protocol_cb = Connection, + role = Role, + host = Host, + port = Port, + trackers = Trackers}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + expecting_finished = true}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} + = Session0, + ssl_options = SslOpts, + connection_states = ConnectionStates0} = State0) -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates0, read), + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, + ssl_gen_statem:opposite_role(Role), + SecParams#security_parameters.prf_algorithm, + MasterSecret, Hist) of + verified -> + Session = maybe_register_session(SslOpts, Host, Port, Trackers, Session0), + ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, + ConnectionStates0), + {State1, Actions} = + tls_dtls_gen_connection:finalize_handshake(State0#state{connection_states = + ConnectionStates1, + session = Session}, + cipher, Connection), + {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection), + Connection:next_event(connection, Record, State, + [{{timeout, handshake}, infinity, close} | Actions]); + #alert{} = Alert -> + throw(Alert) + end; +cipher(Type, Event, State) -> + tls_dtls_gen_connection:cipher(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(cast, {internal_renegotiate, WriteState}, + #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv, + connection_states = ConnectionStates} + = State0) -> + State = State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, + connection_states = ConnectionStates#{current_write => WriteState}}, + Connection:renegotiate(State, []); +connection(Type, Event, State) -> + tls_dtls_gen_connection:connection(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, downgrade, State). + + +%%==================================================================== +%% Help functions for tls|dtls_server_connection.erl +%%==================================================================== + +%% 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 ssl_gen_statem:handle_sni_extension(PossibleSNI, State0) of + {ok, State} -> + State; + {error, #alert{}=Alert} -> + throw(Alert) + end. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +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) 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), + State1 = State0#state{connection_states = ConnectionStates1}, + ServerHello = + ssl_handshake:server_hello(SessId, ssl:tls_version(Version), + ConnectionStates1, ServerHelloExt), + + State = server_hello(ServerHello, + State1#state{handshake_env = + HsEnv#handshake_env{expecting_next_protocol_negotiation = + NextProtocols =/= undefined}}, + Connection), + case Type of + new -> + new_server_hello(ServerHello, State, Connection); + resumed -> + resumed_server_hello(State, Connection) + end. + +update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} = + ReadState0, + pending_write := #{security_parameters := WriteSecParams0} = + WriteState0} = ConnectionStates, + Version, HighestVersion) -> + ReadRandom = override_server_random( + ReadSecParams0#security_parameters.server_random, + Version, + HighestVersion), + WriteRandom = override_server_random( + WriteSecParams0#security_parameters.server_random, + Version, + HighestVersion), + ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom}, + WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom}, + ReadState = ReadState0#{security_parameters => ReadSecParams}, + WriteState = WriteState0#{security_parameters => WriteSecParams}, + + ConnectionStates#{pending_read => ReadState, pending_write => WriteState}. + +%% TLS 1.3 - Section 4.1.3 +%% +%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes +%% of their Random value to the bytes: +%% +%% 44 4F 57 4E 47 52 44 01 +%% +%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2 +%% servers SHOULD set the last eight bytes of their Random value to the +%% bytes: +%% +%% 44 4F 57 4E 47 52 44 00 +override_server_random(<> = Random, {M,N}, {Major,Minor}) + when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above + if M =:= 3 andalso N =:= 3 -> %% Negotiating TLS 1.2 + Down = ?RANDOM_OVERRIDE_TLS12, + <>; + M =:= 3 andalso N < 3 -> %% Negotiating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <>; + true -> + Random + end; +override_server_random(<> = Random, {M,N}, {Major,Minor}) + when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 + if M =:= 3 andalso N < 3 -> %% Negotiating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <>; + true -> + Random + end; +override_server_random(Random, _, _) -> + Random. + +new_server_hello(#server_hello{cipher_suite = CipherSuite, + compression_method = Compression, + session_id = SessionId}, + #state{session = Session0, + static_env = #static_env{protocol_cb = Connection}} = State0, Connection) -> + #state{} = State1 = server_certify_and_key_exchange(State0, Connection), + {State, Actions} = server_hello_done(State1, Connection), + Session = Session0#session{session_id = SessionId, + cipher_suite = CipherSuite, + compression_method = Compression}, + Connection:next_event(certify, no_record, State#state{session = Session}, Actions). + +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, + ConnectionStates0, server) of + {_, ConnectionStates1} -> + State1 = State0#state{connection_states = ConnectionStates1, + session = Session}, + {State, Actions} = + tls_dtls_gen_connection:finalize_handshake(State1, abbreviated, Connection), + Connection:next_event(abbreviated, no_record, State, Actions); + #alert{} = Alert -> + throw(Alert) + end. + +server_hello(ServerHello, State0, Connection) -> + CipherSuite = ServerHello#server_hello.cipher_suite, + #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}. + +server_hello_done(State, Connection) -> + HelloDone = ssl_handshake:server_hello_done(), + Connection:send_handshake(HelloDone, State). + +server_certify_and_key_exchange(State0, Connection) -> + State1 = certify_server(State0, Connection), + State2 = key_exchange(State1, Connection), + request_client_cert(State2, Connection). + +certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} = + State, _) when KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == srp_anon -> + State; +certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + session = #session{own_certificates = OwnCerts}} = State, Connection) -> + Cert = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, server), + #certificate{} = Cert, %% Assert + Connection:queue_handshake(Cert, State). + +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) -> + State; +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg, + diffie_hellman_params = + #'DHParameter'{} = Params, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, + connection_states = ConnectionStates0} = State0, Connection) + when KexAlg == dhe_dss; + KexAlg == dhe_rsa; + KexAlg == dh_anon -> + DHKeys = public_key:generate_key(Params), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv, + session = #session{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key} + = Session} = State, _) + when KexAlg == ecdh_ecdsa; + KexAlg == ecdh_rsa -> + State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key}, + session = Session#session{ecc = ECCurve}}; +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{ecc = ECCCurve, private_key = PrivateKey}, + connection_states = ConnectionStates0} = State0, Connection) + when KexAlg == ecdhe_ecdsa; + KexAlg == ecdhe_rsa; + KexAlg == ecdh_anon -> + + ECDHKeys = public_key:generate_key(ECCCurve), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {ecdh, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = psk}, + ssl_options = #{psk_identity := undefined}} = State, _) -> + State; +key_exchange(#state{ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = psk, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, + connection_states = ConnectionStates0} = State0, Connection) -> + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = dhe_psk, + diffie_hellman_params = + #'DHParameter'{} = Params, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, + connection_states = ConnectionStates0 + } = State0, Connection) -> + DHKeys = public_key:generate_key(Params), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {dhe_psk, + PskIdentityHint, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; +key_exchange(#state{ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{ecc = ECCCurve, private_key = PrivateKey}, + connection_states = ConnectionStates0 + } = State0, Connection) -> + ECDHKeys = public_key:generate_key(ECCCurve), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {ecdhe_psk, + PskIdentityHint, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; +key_exchange(#state{handshake_env = #handshake_env{kex_algorithm = rsa_psk}, + ssl_options = #{psk_identity := undefined}} = State, _) -> + State; +key_exchange(#state{ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = rsa_psk, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, + connection_states = ConnectionStates0 + } = State0, Connection) -> + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{ssl_options = #{user_lookup_fun := LookupFun}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{srp_username = Username, private_key = PrivateKey}, + connection_states = ConnectionStates0 + } = State0, Connection) + when KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + SrpParams = handle_srp_identity(Username, LookupFun), + Keys = generate_srp_server_keys(SrpParams, 0), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {srp, Keys, SrpParams, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams, + kex_keys = Keys}}. + +request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _) + when Alg == dh_anon; + Alg == ecdh_anon; + Alg == psk; + Alg == dhe_psk; + Alg == ecdhe_psk; + Alg == rsa_psk; + Alg == srp_dss; + Alg == srp_rsa; + Alg == srp_anon -> + State; +request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{verify := verify_peer} = Opts} = State0, Connection) -> + SupportedHashSigns = maps:get(signature_algs, Opts, undefined), + TLSVersion = ssl:tls_version(Version), + HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, + TLSVersion), + Msg = ssl_handshake:certificate_request(CertDbHandle, CertDbRef, + HashSigns, TLSVersion), + State = Connection:queue_handshake(Msg, State0), + State#state{client_certificate_status = requested}; + +request_client_cert(#state{ssl_options = #{verify := verify_none}} = + State, _) -> + State. + +certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, + #state{session = #session{private_key = PrivateKey}, + handshake_env = + #handshake_env{client_hello_version = {Major, Minor} + = Version}, + client_certificate_status = CCStatus} + = State, Connection) -> + FakeSecret = tls_dtls_gen_connection:make_premaster_secret(Version, rsa), + %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret + %% and fail handshake later.RFC 5246 section 7.4.7.1. + PremasterSecret = + try ssl_handshake:premaster_secret(EncPMS, PrivateKey) of + Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> + case Secret of + <> -> %% Correct + <>; + <> -> %% Version mismatch + <> + end; + _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES + FakeSecret + catch + #alert{description = ?DECRYPT_ERROR} -> + FakeSecret + end, + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State, Connection, + certify, client_kex_next_state(CCStatus)); +certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, + #state{handshake_env = + #handshake_env{diffie_hellman_params = + #'DHParameter'{} = Params, + kex_keys = {_, ServerDhPrivateKey}}, + client_certificate_status = CCStatus + } = State, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, + ServerDhPrivateKey, Params), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State, + Connection, certify, + client_kex_next_state(CCStatus)); + +certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, + #state{handshake_env = #handshake_env{kex_keys = ECDHKey}, + client_certificate_status = CCStatus + } = State, Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State, + Connection, certify, + client_kex_next_state(CCStatus)); +certify_client_key_exchange(#client_psk_identity{} = ClientKey, + #state{ssl_options = + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State0, + Connection, certify, + client_kex_next_state(CCStatus)); +certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, + #state{handshake_env = + #handshake_env{diffie_hellman_params = + #'DHParameter'{} = Params, + kex_keys = {_, ServerDhPrivateKey}}, + ssl_options = + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State0, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State0, + Connection, certify, + client_kex_next_state(CCStatus)); +certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, + #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, + ssl_options = + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State, + Connection, certify, + client_kex_next_state(CCStatus)); +certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, + #state{session = #session{private_key = PrivateKey}, + ssl_options = + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PrivateKey, PSKLookup), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State0, + Connection, certify, + client_kex_next_state(CCStatus)); +certify_client_key_exchange(#client_srp_public{} = ClientKey, + #state{handshake_env = #handshake_env{srp_params = Params, + kex_keys = Key}, + client_certificate_status = CCStatus + } = State0, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), + tls_dtls_gen_connection:calculate_master_secret(PremasterSecret, State0, + Connection, certify, + client_kex_next_state(CCStatus)). + +client_kex_next_state(needs_verifying) -> + wait_cert_verify; +client_kex_next_state(empty) -> + cipher; +client_kex_next_state(not_requested) -> + cipher. + +handle_srp_identity(Username, {Fun, UserState}) -> + case Fun(srp, Username, UserState) of + {ok, {SRPParams, Salt, DerivedKey}} + when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> + {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), + Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), + #srp_user{generator = Generator, prime = Prime, + salt = Salt, verifier = Verifier}; + #alert{} = Alert -> + throw(Alert); + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + +generate_srp_server_keys(_SrpParams, 10) -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); +generate_srp_server_keys(SrpParams = + #srp_user{generator = Generator, prime = Prime, + verifier = Verifier}, N) -> + try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) + catch + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]), + generate_srp_server_keys(SrpParams, N+1) + end. + +maybe_register_session(#{reuse_sessions := true}, + _Host, _Port, Trackers, #session{is_resumable = false} = Session0) -> + Tracker = proplists:get_value(session_id_tracker, Trackers), + Session = Session0#session{is_resumable = true}, + ssl_server_session_cache:register_session(Tracker, Session), + Session; +maybe_register_session(_,_,_,_, Session) -> + Session. + +ocsp_info(OcspState, _, PeerCert) -> + #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []}, + ocsp_responder_certs => [], + ocsp_state => OcspState}. diff --git a/lib/ssl/src/tls_dyn_connection_sup.erl b/lib/ssl/src/tls_dyn_connection_sup.erl index 5905889225b8..ab97747437be 100644 --- a/lib/ssl/src/tls_dyn_connection_sup.erl +++ b/lib/ssl/src/tls_dyn_connection_sup.erl @@ -75,7 +75,8 @@ receiver(Args) -> significant => true, start => {ssl_gen_statem, start_link, Args}, modules => [ssl_gen_statem, - tls_connection, + tls_client_connection, + tls_server_connection, tls_gen_connection, tls_client_connection_1_3, tls_server_connection_1_3, diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl index 7a64bc7db3f9..0894859ef574 100644 --- a/lib/ssl/src/tls_gen_connection.erl +++ b/lib/ssl/src/tls_gen_connection.erl @@ -41,9 +41,7 @@ initialize_tls_sender/1]). %% Handshake handling --export([renegotiation/2, - renegotiate/2, - send_handshake/2, +-export([send_handshake/2, send_handshake_flight/1, queue_handshake/2, queue_change_cipher/2, @@ -62,7 +60,8 @@ -export([socket/4, setopts/3, getopts/3, - handle_info/3]). + handle_info/3, + gen_info/3]). %% Alert and close handling -export([send_alert/2, @@ -109,7 +108,8 @@ start_connection_tree(User, IsErlDist, SenderOpts, Role, ReceiverOpts) -> case tls_dyn_connection_sup:start_child(DynSup, sender, SenderOpts) of {ok, Sender} -> case tls_dyn_connection_sup:start_child(DynSup, receiver, - [Role, Sender | ReceiverOpts]) of + [Role, Sender | + ReceiverOpts]) of {ok, Receiver} -> User ! {self(), Receiver, Sender}; {error, Error} -> @@ -137,7 +137,8 @@ start_dyn_connection_sup(false) -> tls_connection_sup:start_child([]). socket_control(Socket, SockReceiver, SockSender, CbModule, Trackers, Timeout) -> - case ssl_gen_statem:socket_control(?MODULE, Socket, [SockReceiver, SockSender], CbModule, Trackers) of + case ssl_gen_statem:socket_control(?MODULE, Socket, [SockReceiver, SockSender], + CbModule, Trackers) of {ok, SslSocket} -> ssl_gen_statem:handshake(SslSocket, Timeout); Error -> @@ -179,37 +180,11 @@ initialize_tls_sender(#state{static_env = #static_env{ %%==================================================================== %% Handshake handling %%==================================================================== -renegotiation(Pid, WriteState) -> - gen_statem:call(Pid, {user_renegotiate, WriteState}). - -renegotiate(#state{static_env = #static_env{role = client}, - handshake_env = HsEnv} = State, Actions) -> - %% Handle same way as if server requested - %% the renegotiation - Hs0 = ssl_handshake:init_handshake_history(), - {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, - [{next_event, internal, #hello_request{}} | Actions]}; -renegotiate(#state{static_env = #static_env{role = server, - socket = Socket, - transport_cb = Transport}, - handshake_env = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0} = State0, Actions) -> - HelloRequest = ssl_handshake:hello_request(), - Frag = tls_handshake:encode_handshake(HelloRequest, Version), - Hs0 = ssl_handshake:init_handshake_history(), - {BinMsg, ConnectionStates} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), - tls_socket:send(Transport, Socket, BinMsg), - State = State0#state{connection_states = - ConnectionStates, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, - next_event(hello, no_record, State, Actions). - send_handshake(Handshake, State) -> send_handshake_flight(queue_handshake(Handshake, State)). -queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, +queue_handshake(Handshake, #state{handshake_env = + #handshake_env{tls_handshake_history = Hist0} = HsEnv, connection_env = #connection_env{negotiated_version = Version}, flight_buffer = Flight0, ssl_options = #{log_level := LogLevel}, @@ -255,7 +230,8 @@ reinit_handshake_data(#state{handshake_env = HsEnv} =State) -> %% are only needed during the handshake phase. %% To reduce memory foot print of a connection reinitialize them. State#state{ - handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), + handshake_env = HsEnv#handshake_env{tls_handshake_history = + ssl_handshake:init_handshake_history(), public_key_info = undefined, premaster_secret = undefined} }. @@ -281,6 +257,28 @@ setopts(Transport, Socket, Other) -> getopts(Transport, Socket, Tag) -> tls_socket:getopts(Transport, Socket, Tag). +gen_info(Event, connection = StateName, State) -> + try + handle_info(Event, StateName, State) + catch + _:Reason:ST -> + ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + malformed_data), + StateName, State) + end; + +gen_info(Event, StateName, State) -> + try + handle_info(Event, StateName, State) + catch + _:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + StateName, State) + end. + %% raw data from socket, upack records handle_info({Protocol, _, Data}, StateName, #state{static_env = #static_env{data_tag = Protocol}} = State0) -> @@ -299,7 +297,8 @@ handle_info({PassiveTag, Socket}, StateName, } = State0) -> case (From =/= undefined) andalso (CTs == []) of true -> - {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}), + {Record, State} = activate_socket(State0#state{protocol_specific = + PS#{active_n_toggle => true}}), next_event(StateName, Record, State); false -> next_event(StateName, no_record, @@ -360,7 +359,8 @@ handle_info({CloseTag, Socket}, StateName, %% active_n_toggle here which will cause %% this to happen when tls_connection:activate_socket/1 %% is called after all data has been deliver. - {next_state, StateName, State#state{protocol_specific = PS#{active_n_toggle => true}}, []} + {next_state, StateName, State#state{protocol_specific = + PS#{active_n_toggle => true}}, []} end; handle_info({ssl_tls, Port, Type, {Major, Minor}, Data}, StateName, #state{static_env = #static_env{data_tag = Protocol}, @@ -419,11 +419,13 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName, } = State) when StateName == wait_cert; StateName == wait_cv; StateName == wait_finished-> - Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_handshake_or_intervened_in_post_handshake_auth), + Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, + application_data_before_handshake_or_intervened_in_post_handshake_auth), ssl_gen_statem:handle_own_alert(Alert, StateName, State); 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 -> + socket_options = #socket_options{active = false}} + = State0) when From =/= undefined -> case ssl_gen_statem:read_application_data(Data, State0) of {stop, _, _} = Stop-> Stop; @@ -448,7 +450,8 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, #state{ssl_options = Options, protocol_buffers = Buffers} = State0) -> try {HSPackets, NewHSBuffer, RecordRest} = get_tls_handshakes(Data, StateName, State0), - State = State0#state{protocol_buffers = Buffers#protocol_buffers{tls_handshake_buffer = NewHSBuffer}}, + State = State0#state{protocol_buffers = Buffers#protocol_buffers{tls_handshake_buffer + = NewHSBuffer}}, case HSPackets of [] -> assert_buffer_sanity(NewHSBuffer, Options), @@ -463,7 +466,8 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{unprocessed_handshake_events - = unprocessed_events(Events)}}, Events} + = unprocessed_events(Events)}}, + Events} end end catch throw:#alert{} = Alert -> @@ -485,7 +489,8 @@ handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, catch _:Reason:ST -> ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), - ssl_gen_statem: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), StateName, State) end; @@ -576,8 +581,10 @@ protocol_name() -> %%==================================================================== %% Internal functions %%==================================================================== -get_tls_handshakes(Data, StateName, #state{protocol_buffers = #protocol_buffers{tls_handshake_buffer = HSBuffer}, - connection_env = #connection_env{negotiated_version = Version}, +get_tls_handshakes(Data, StateName, #state{protocol_buffers = + #protocol_buffers{tls_handshake_buffer = HSBuffer}, + connection_env = + #connection_env{negotiated_version = Version}, static_env = #static_env{role = Role}, ssl_options = Options}) -> case handle_unnegotiated_version(Version, Options, Data, HSBuffer, Role, StateName) of @@ -597,7 +604,8 @@ tls_handshake_events(HSPackets, <<>>) -> tls_handshake_events(HSPackets, RecordRest) -> %% Coalesced TLS record data to be handled after first handshake message has been handled - RestEvent = {next_event, internal, {protocol_record, #ssl_tls{type = ?HANDSHAKE, fragment = RecordRest}}}, + RestEvent = {next_event, internal, {protocol_record, #ssl_tls{type = ?HANDSHAKE, + fragment = RecordRest}}}, FirstHS = tls_handshake_events(HSPackets, <<>>), FirstHS ++ [RestEvent]. @@ -729,11 +737,12 @@ flow_ctrl(State) -> activate_socket(State). -activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, +activate_socket(#state{protocol_specific = + #{active_n_toggle := true, active_n := N} = ProtocolSpec, static_env = #static_env{socket = Socket, close_tag = CloseTag, transport_cb = Transport} - } = State) -> + } = State) -> case tls_socket:setopts(Transport, Socket, [{active, N}]) of ok -> {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; @@ -747,19 +756,21 @@ activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n : next_record(State, CipherTexts, ConnectionStates, Check) -> next_record(State, CipherTexts, ConnectionStates, Check, [], false). %% -next_record(#state{connection_env = #connection_env{negotiated_version = ?TLS_1_3 = Version}} = State, - [CT|CipherTexts], ConnectionStates0, Check, Acc, IsEarlyData) -> +next_record(#state{connection_env = #connection_env{negotiated_version = ?TLS_1_3 = Version}} = + State, [CT|CipherTexts], ConnectionStates0, Check, Acc, IsEarlyData) -> case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of {Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> case CipherTexts of [] -> %% End of cipher texts - build and deliver an ?APPLICATION_DATA record %% from the accumulated fragments + NewFragment = iolist_to_binary(lists:reverse(Acc, [Fragment])), next_record_done(State, [], ConnectionStates, Record#ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + fragment = NewFragment}); [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc], Record#ssl_tls.early_data) + next_record(State, CipherTexts, ConnectionStates, + Check, [Fragment|Acc], Record#ssl_tls.early_data) end; {no_record, ConnectionStates} -> case CipherTexts of @@ -785,18 +796,21 @@ next_record(#state{connection_env = #connection_env{negotiated_version = ?TLS_1_ Alert end; next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, - [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc, NotRelevant) -> + [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, + Check, Acc, NotRelevant) -> case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of {Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> case CipherTexts of [] -> %% End of cipher texts - build and deliver an ?APPLICATION_DATA record %% from the accumulated fragments + NewFragment = iolist_to_binary(lists:reverse(Acc, [Fragment])), next_record_done(State, [], ConnectionStates, Record#ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + fragment = NewFragment}); [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc], NotRelevant) + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc], + NotRelevant) end; #alert{} = Alert -> Alert @@ -823,29 +837,39 @@ accumulated_app_record([_|_] = Acc, IsEarlyData) -> early_data = IsEarlyData, fragment = iolist_to_binary(lists:reverse(Acc))}. -next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) -> +next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, + ConnectionStates, Record) -> {Record, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts}, connection_states = ConnectionStates}}. -%% Pre TLS-1.3, on the client side, the connection state variable `negotiated_version` will initially be -%% the requested version. On the server side the same variable is initially undefined. -%% When the client can support TLS-1.3 and one or more prior versions and we are waiting -%% for the server hello the "initial requested version" kept in the connection state variable `negotiated_version` -%% (before the versions is actually negotiated) will always be the value of TLS-1.2 (which is a legacy -%% field in TLS-1.3 client hello). The versions are instead negotiated with an hello extension. When -%% decoding the server_hello messages we want to go through TLS-1.3 decode functions to be able -%% to handle TLS-1.3 extensions if TLS-1.3 will be the negotiated version. -handle_unnegotiated_version(?LEGACY_VERSION , #{versions := [?TLS_1_3 = Version |_]} = Options, Data, Buffer, client, hello) -> - %% The effective version for decoding the server hello message should be the TLS-1.3. Possible coalesced TLS-1.2 - %% server handshake messages should be decoded with the negotiated version in later state. +%% Pre TLS-1.3, on the client side, the connection state variable +%% `negotiated_version` will initially be the requested version. On +%% the server side the same variable is initially undefined. When the +%% client can support TLS-1.3 and one or more prior versions and we +%% are waiting for the server hello the "initial requested version" +%% kept in the connection state variable `negotiated_version` (before +%% the versions is actually negotiated) will always be the value of +%% TLS-1.2 (which is a legacy field in TLS-1.3 client hello). The +%% versions are instead negotiated with an hello extension. When +%% decoding the server_hello messages we want to go through TLS-1.3 +%% decode functions to be able to handle TLS-1.3 extensions if TLS-1.3 +%% will be the negotiated version. +handle_unnegotiated_version(?LEGACY_VERSION , #{versions := [?TLS_1_3 = Version |_]} = Options, + Data, Buffer, client, hello) -> + %% The effective version for decoding the server hello message + %% should be the TLS-1.3. Possible coalesced TLS-1.2 server + %% handshake messages should be decoded with the negotiated + %% version in later state. <<_:8, ?UINT24(Length), _/binary>> = Data, <> = Data, - {HSPacket, <<>> = NewHsBuffer} = tls_handshake:get_tls_handshakes(Version, FirstPacket, Buffer, Options), + {HSPacket, <<>> = NewHsBuffer} = tls_handshake:get_tls_handshakes(Version, FirstPacket, + Buffer, Options), {HSPacket, NewHsBuffer, RecordRest}; %% TLS-1.3 RetryRequest -handle_unnegotiated_version(?TLS_1_2 , #{versions := [?TLS_1_3 = Version |_]} = Options, Data, Buffer, client, wait_sh) -> +handle_unnegotiated_version(?TLS_1_2 , #{versions := [?TLS_1_3 = Version |_]} = Options, Data, + Buffer, client, wait_sh) -> tls_handshake:get_tls_handshakes(Version, Data, Buffer, Options); %% When the `negotiated_version` variable is not yet set use the highest supported version. handle_unnegotiated_version(undefined, #{versions := [Version|_]} = Options, Data, Buff, _, _) -> @@ -884,15 +908,16 @@ handle_alerts([], Result) -> handle_alerts(_, {stop, _, _} = Stop) -> Stop; handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts], - {next_state, connection = StateName, #state{connection_env = CEnv, - socket_options = #socket_options{active = false}, - start_or_recv_from = From} = State}) when From == undefined -> - {next_state, StateName, State#state{connection_env = CEnv#connection_env{socket_tls_closed = true}}}; + {next_state, connection = StateName, + #state{connection_env = CEnv, + socket_options = #socket_options{active = false}, + start_or_recv_from = From} = State}) when From == undefined -> + {next_state, StateName, State#state{connection_env = + CEnv#connection_env{socket_tls_closed = true}}}; handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> - handle_alerts(Alerts, ssl_gen_statem: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_gen_statem:handle_alert(Alert, StateName, State)). + handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)). handle_record_alert(Alert, _) -> Alert. - diff --git a/lib/ssl/src/tls_gen_connection_1_3.erl b/lib/ssl/src/tls_gen_connection_1_3.erl index eec4aa324e16..e7cf4014a99a 100644 --- a/lib/ssl/src/tls_gen_connection_1_3.erl +++ b/lib/ssl/src/tls_gen_connection_1_3.erl @@ -100,17 +100,17 @@ initial_state(Role, Sender, Host, Port, Socket, }. user_hello(info, {'DOWN', _, _, _, _} = Event, State) -> - ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State); + ssl_gen_statem:handle_info(Event, user_hello, State); user_hello(_, _, _) -> {keep_state_and_data, [postpone]}. wait_cert(enter, _, State0) -> State = handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_cert, State,[]}; wait_cert(internal = Type, #change_cipher_spec{} = Msg, #state{session = #session{session_id = Id}} = State) when Id =/= ?EMPTY_ID -> - handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); + handle_change_cipher_spec(Type, Msg, wait_cert, State); wait_cert(internal, #certificate_1_3{} = Certificate, State0) -> case do_wait_cert(Certificate, State0) of @@ -120,56 +120,56 @@ wait_cert(internal, tls_gen_connection:next_event(NextState, no_record, State) end; wait_cert(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_cert, State); wait_cert(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_cert, State). wait_cv(enter, _, State0) -> State = handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_cv, State,[]}; wait_cv(internal = Type, #change_cipher_spec{} = Msg, #state{session = #session{session_id = Id}} = State) when Id =/= ?EMPTY_ID -> - handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); + handle_change_cipher_spec(Type, Msg, wait_cv, State); wait_cv(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_cv, State); wait_cv(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_cv, State). connection(enter, _, State) -> {keep_state, State}; connection(internal, #new_session_ticket{} = NewSessionTicket, State) -> handle_new_session_ticket(NewSessionTicket, State), - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); + tls_gen_connection:next_event(connection, no_record, State); connection(internal, #key_update{} = KeyUpdate, State0) -> case handle_key_update(KeyUpdate, State0) of {ok, State} -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); + tls_gen_connection:next_event(connection, no_record, State); {error, State, Alert} -> ssl_gen_statem:handle_own_alert(Alert, connection, State), - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State) + tls_gen_connection:next_event(connection, 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}}]); + ssl_gen_statem:hibernate_after(connection, 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, + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, {ok, SelectedProtocol}}]); connection(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + ssl_gen_statem:connection(Type, Event, State). downgrade(enter, _, State) -> {keep_state, State}; downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) -> _ = handle_new_session_ticket(NewSessionTicket, State), - {next_state, ?FUNCTION_NAME, State}; + {next_state, downgrade, State}; downgrade(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + ssl_gen_statem:downgrade(Type, Event, State). %% Description: Enqueues a change_cipher_spec record as the first/last %% message of the current flight buffer diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 455684bd87aa..185b90b66603 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -253,7 +253,7 @@ init({call, From}, {Pid, #{current_write := WriteState, hibernate_after = HibernateAfter}}, {next_state, handshake, StateData, [{reply, From, ok}]}; init(info = Type, Msg, StateData) -> - handle_common(?FUNCTION_NAME, Type, Msg, StateData); + handle_common(init, Type, Msg, StateData); init(_, _, _) -> %% Just in case anything else sneaks through {keep_state_and_data, [postpone]}. @@ -269,15 +269,15 @@ connection({call, From}, {application_data, AppData}, StateData) -> case encode_packet(Packet, AppData) of {error, _} = Error -> - {next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]}; + {next_state, connection, StateData, [{reply, From, Error}]}; Data -> - send_application_data(Data, From, ?FUNCTION_NAME, StateData) + send_application_data(Data, From, connection, StateData) end; connection({call, From}, {post_handshake_data, HSData}, StateData) -> - send_post_handshake_data(HSData, From, ?FUNCTION_NAME, StateData); + send_post_handshake_data(HSData, From, connection, StateData); connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) -> StateData = send_tls_alert(Alert, StateData0), - {next_state, ?FUNCTION_NAME, StateData, + {next_state, connection, StateData, [{reply,From,ok}]}; connection({call, From}, renegotiate, #data{connection_states = #{current_write := Write}} = StateData) -> @@ -286,14 +286,14 @@ connection({call, From}, downgrade, #data{connection_states = #{current_write := Write}} = StateData) -> {next_state, death_row, StateData, [{reply,From, {ok, Write}}]}; connection({call, From}, {set_opts, Opts}, StateData) -> - handle_set_opts(?FUNCTION_NAME, From, Opts, StateData); + handle_set_opts(connection, From, Opts, StateData); connection({call, From}, dist_get_tls_socket, #data{static = #static{transport_cb = Transport, socket = Socket, connection_pid = Pid, trackers = Trackers}} = StateData) -> TLSSocket = tls_gen_connection:socket([Pid, self()], Transport, Socket, Trackers), - hibernate_after(?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]); + hibernate_after(connection, StateData, [{reply, From, {ok, TLSSocket}}]); 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), @@ -304,7 +304,7 @@ connection({call, From}, {dist_handshake_complete, _Node, DHandle}, case dist_data(DHandle) of [] -> - hibernate_after(?FUNCTION_NAME, + hibernate_after(connection, StateData#data{ static = Static#static{dist_handle = DHandle}}, [{reply,From,ok}]); @@ -320,15 +320,15 @@ connection({call, From}, get_application_traffic_secret, State) -> SecurityParams = maps:get(security_parameters, CurrentWrite), ApplicationTrafficSecret = SecurityParams#security_parameters.application_traffic_secret, - hibernate_after(?FUNCTION_NAME, State, + hibernate_after(connection, State, [{reply, From, {ok, ApplicationTrafficSecret}}]); connection(internal, {application_packets, From, Data}, StateData) -> - send_application_data(Data, From, ?FUNCTION_NAME, StateData); + send_application_data(Data, From, connection, StateData); connection(internal, {post_handshake_data, From, HSData}, StateData) -> - send_post_handshake_data(HSData, From, ?FUNCTION_NAME, StateData); + send_post_handshake_data(HSData, From, connection, StateData); connection(cast, #alert{} = Alert, StateData0) -> StateData = send_tls_alert(Alert, StateData0), - {next_state, ?FUNCTION_NAME, StateData}; + {next_state, connection, StateData}; connection(cast, {new_write, WritesState, Version}, #data{connection_states = ConnectionStates, static = Static} = StateData) -> hibernate_after(connection, @@ -341,7 +341,7 @@ connection(info, dist_data, #data{static = #static{dist_handle = DHandle}} = StateData) -> case dist_data(DHandle) of [] -> - hibernate_after(?FUNCTION_NAME, StateData, []); + hibernate_after(connection, StateData, []); Data -> {keep_state_and_data, [{next_event, internal, @@ -351,7 +351,7 @@ connection(info, tick, StateData) -> consume_ticks(), Data = [<<0:32>>], % encode_packet(4, <<>>) From = {self(), undefined}, - send_application_data(Data, From, ?FUNCTION_NAME, StateData); + send_application_data(Data, From, connection, StateData); connection(info, {send, From, Ref, Data}, _StateData) -> %% This is for testing only! %% @@ -364,7 +364,7 @@ connection(info, {send, From, Ref, Data}, _StateData) -> connection(timeout, hibernate, _StateData) -> {keep_state_and_data, [hibernate]}; connection(Type, Msg, StateData) -> - handle_common(?FUNCTION_NAME, Type, Msg, StateData). + handle_common(connection, Type, Msg, StateData). %%-------------------------------------------------------------------- -spec handshake(gen_statem:event_type(), @@ -373,7 +373,7 @@ connection(Type, Msg, StateData) -> gen_statem:event_handler_result(atom()). %%-------------------------------------------------------------------- handshake({call, From}, {set_opts, Opts}, StateData) -> - handle_set_opts(?FUNCTION_NAME, From, Opts, StateData); + handle_set_opts(handshake, From, Opts, StateData); handshake({call, _}, _, _) -> %% Postpone all calls to the connection state {keep_state_and_data, [postpone]}; @@ -397,7 +397,7 @@ handshake(info, {send, _, _, _}, _) -> %% Testing only, OTP distribution test suites... {keep_state_and_data, [postpone]}; handshake(Type, Msg, StateData) -> - handle_common(?FUNCTION_NAME, Type, Msg, StateData). + handle_common(handshake, Type, Msg, StateData). %%-------------------------------------------------------------------- -spec death_row(gen_statem:event_type(), @@ -408,7 +408,7 @@ handshake(Type, Msg, StateData) -> death_row(state_timeout, Reason, _StateData) -> {stop, {shutdown, Reason}}; death_row(info = Type, Msg, StateData) -> - handle_common(?FUNCTION_NAME, Type, Msg, StateData); + handle_common(death_row, Type, Msg, StateData); death_row(_Type, _Msg, _StateData) -> %% Waste all other events keep_state_and_data. @@ -509,7 +509,7 @@ send_application_data(Data, From, StateName, {keep_state_and_data, [{next_event, internal, {post_handshake_data, From, KeyUpdate}}, {next_event, internal, {application_packets, From, Data}}]}; renegotiate -> - tls_dtls_connection:internal_renegotiation(Pid, ConnectionStates0), + tls_dtls_gen_connection:internal_renegotiation(Pid, ConnectionStates0), {next_state, handshake, StateData0, [{next_event, internal, {application_packets, From, Data}}]}; chunk_and_key_update -> diff --git a/lib/ssl/src/tls_server_connection.erl b/lib/ssl/src/tls_server_connection.erl new file mode 100644 index 000000000000..fd0f678b11cf --- /dev/null +++ b/lib/ssl/src/tls_server_connection.erl @@ -0,0 +1,476 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2023-2023. 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: TLS-1.0-TLS-1.2 FSM (* = optional) +%% %%---------------------------------------------------------------------- +%% TLS Handshake protocol full Handshake +%% Client Server +%% +%% ClientHello --------> Flight 1 +%% ServerHello \ +%% Certificate* \ +%% ServerKeyExchange* Flight 2 +%% CertificateRequest* / +%% <-------- ServerHelloDone / +%% Certificate* \ +%% ClientKeyExchange \ +%% CertificateVerify* Flight 3 part 1 +%% [ChangeCipherSpec] / +%% NextProtocol* +%% Finished --------> / Flight 3 part 2 +%% [ChangeCipherSpec] +%% <-------- Finished Flight 4 +%% Application Data <-------> Application Data +%% +%% +%% TLS Handshake protocol abbreviated Handshake +%% Client Server +%% +%% ClientHello --------> Abbrev Flight 1 +%% ServerHello Abbrev Flight 2 part 1 +%% [ChangeCipherSpec] +%% <-------- Finished Abbrev Flight 2 part 2 +%% [ChangeCipherSpec] +%% NextProtocol* +%% Finished --------> Abbrev Flight 3 +%% Application Data <-------> Application Data +%% +%% +%% +%% Start FSM ---> CONFIG_ERROR +%% Send error to user +%% | and shutdown +%% | +%% V +%% INITIAL_HELLO +%% +%% | Send/Recv Flight 1 +%% | +%% | +%% USER_HELLO | +%% <- Possibly let user provide V +%% options after looking at hello ex -> HELLO +%% | Send/Recv Flight 2 or Abbrev Flight 1 +%% | - Abbrev Flight 2 part 1 +%% | +%% New session | Resumed session +%% WAIT_CERT_VERIFY CERTIFY <----------------------------------> ABBREVIATED +%% +%% <- Possibly Receive -- | | +%% CertVerify -> | Flight 3 part 1 | +%% | | +%% V | Abbrev Flight 2 part +%% | 2 to Abbrev Flight 3 +%% CIPHER | +%% | | +%% | | +%% | Fligth 3 part 2 to Flight 4 | +%% | | +%% V V +%% ---------------------------------------------------- +%% | +%% | +%% V +%% CONNECTION +%% | +%% | Renegotiaton +%% V +%% GO BACK TO HELLO +%%---------------------------------------------------------------------- + +-module(tls_server_connection). + +-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("ssl_alert.hrl"). +-include("tls_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). + +%% Internal application API + +%% Setup +-export([init/1]). + +%% gen_statem state functions +-export([initial_hello/3, + config_error/3, + downgrade/3, + hello/3, + user_hello/3, + certify/3, + wait_cert_verify/3, + cipher/3, + abbreviated/3, + connection/3]). + +%% gen_statem callbacks +-export([callback_mode/0, + terminate/3, + code_change/4, + format_status/2]). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> + State0 = tls_dtls_gen_connection:initial_state(Role, Sender, Host, Port, Socket, + Options, User, CbInfo), + try + State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, Role, State0), + tls_gen_connection:initialize_tls_sender(State), + gen_statem:enter_loop(?MODULE, [], initial_hello, State) + catch throw:Error -> + #state{protocol_specific = Map} = State0, + EState = State0#state{protocol_specific = Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], config_error, EState) + end. + +%%-------------------------------------------------------------------- +%% State functions +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +initial_hello({call, From}, {start, Timeout}, + #state{static_env = #static_env{ + protocol_cb = Connection}, + ssl_options = #{versions := Versions}} = State0) -> + NextState = next_statem_state(Versions), + Connection:next_event(NextState, no_record, State0#state{start_or_recv_from = From}, + [{{timeout, handshake}, Timeout, close}]); +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:update_options(Opts, Role, OrigSSLOptions), + State = ssl_gen_statem: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(Type, Event, State) -> + ssl_gen_statem:initial_hello(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +config_error(Type, Event, State) -> + ssl_gen_statem:config_error(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #client_hello{} | #server_hello{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello(internal, #client_hello{extensions = Extensions}, + #state{handshake_env = #handshake_env{continue_status = pause}, + start_or_recv_from = From} = State) -> + {next_state, user_hello, State#state{start_or_recv_from = undefined}, + [{postpone, true}, {reply, From, {ok, Extensions}}]}; +hello(internal, #client_hello{client_version = ClientVersion} = Hello, + #state{connection_env = CEnv} = State0) -> + try + #state{ssl_options = SslOpts} = State1 = + tls_dtls_server_connection:handle_sni_extension(State0, Hello), + case choose_tls_fsm(SslOpts, Hello) of + tls_1_3_fsm -> + {next_state, start, State1, + [{change_callback_module, tls_server_connection_1_3}, + {next_event, internal, Hello}]}; + tls_1_0_to_1_2_fsm -> + {ServerHelloExt, Type, State} = handle_client_hello(Hello, State1), + {next_state, hello, State, + [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]} + end + catch throw:#alert{} = Alert -> + NewCenv = CEnv#connection_env{negotiated_version = ClientVersion}, + AlertState = + State0#state{connection_env = NewCenv}, + ssl_gen_statem:handle_own_alert(Alert, hello, AlertState) + end; +hello(info, Event, State) -> + tls_gen_connection:handle_info(Event, hello, State); +hello(Type, Event, State) -> + gen_state(hello, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +user_hello(Type, Event, State) -> + gen_state(user_hello, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated(info, Event, State) -> + tls_gen_connection:gen_info(Event, abbreviated, State); +abbreviated(Type, Event, State) -> + gen_state(abbreviated, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify(info, Event, State) -> + tls_gen_connection:gen_info(Event, certify, State); +certify(Type, Event, State) -> + gen_state(certify, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cert_verify(info, Event, State) -> + tls_gen_connection:gen_info(Event, wait_cert_verify, State); +wait_cert_verify(internal, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, + #state{static_env = #static_env{protocol_cb = Connection}, + client_certificate_status = needs_verifying, + handshake_env = #handshake_env{tls_handshake_history = Hist, + kex_algorithm = KexAlg, + public_key_info = PubKeyInfo}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} = Session0 + } = State) -> + + TLSVersion = ssl:tls_version(Version), + %% Use negotiated value if TLS-1.2 otherwise return default + HashSign = tls_dtls_gen_connection:negotiated_hashsign(CertHashSign, KexAlg, + PubKeyInfo, TLSVersion), + case ssl_handshake:certificate_verify(Signature, PubKeyInfo, + TLSVersion, HashSign, MasterSecret, Hist) of + valid -> + Connection:next_event(cipher, no_record, + State#state{client_certificate_status = verified, + session = Session0#session{sign_alg = HashSign}}); + #alert{} = Alert -> + throw(Alert) + end; +wait_cert_verify(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, wait_cert_verify, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher(info, Event, State) -> + tls_gen_connection:gen_info(Event, cipher, State); +cipher(Type, Event, State) -> + gen_state(cipher, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + #hello_request{} | #client_hello{}| term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(info, Event, State) -> + tls_gen_connection:gen_info(Event, connection, State); +connection(cast, {internal_renegotiate, WriteState}, #state{handshake_env = HsEnv, + connection_states = ConnectionStates} + = State) -> + renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, + connection_states = ConnectionStates#{current_write => WriteState}}, []); +connection({call, From}, {user_renegotiate, WriteState}, + #state{connection_states = ConnectionStates} = State) -> + {next_state, connection, + State#state{connection_states = ConnectionStates#{current_write => WriteState}}, + [{next_event,{call, From}, renegotiate}]}; +connection(internal, #client_hello{} = Hello, + #state{handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv, + connection_states = CS, + protocol_specific = #{sender := Sender} + } = State) -> + %% Mitigate Computational DoS attack + %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html + %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client + %% initiated renegotiation we will disallow many client initiated + %% renegotiations immediately after each other. + erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), + {ok, Write} = tls_sender:renegotiate(Sender), + tls_gen_connection:next_event(hello, no_record, + State#state{connection_states = + CS#{current_write => Write}, + handshake_env = + HsEnv#handshake_env{renegotiation = {true, peer}, + allow_renegotiate = false} + }, + [{next_event, internal, Hello}]); +connection(internal, #client_hello{}, + #state{handshake_env = #handshake_env{allow_renegotiate = false}} = State0) -> + Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), + tls_gen_connection:send_alert_in_connection(Alert, State0), + State = tls_gen_connection:reinit_handshake_data(State0), + tls_gen_connection:next_event(connection, no_record, State); +connection({call, From}, renegotiate, #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + handshake_env = HsEnv, + connection_env = + #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0} = State0) -> + HelloRequest = ssl_handshake:hello_request(), + Frag = tls_handshake:encode_handshake(HelloRequest, Version), + Hs0 = ssl_handshake:init_handshake_history(), + {BinMsg, ConnectionStates} = + tls_record:encode_handshake(Frag, Version, ConnectionStates0), + tls_socket:send(Transport, Socket, BinMsg), + State = State0#state{connection_states = + ConnectionStates, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0, + renegotiation = {true, From}}}, + tls_gen_connection:next_event(hello, no_record, State); +connection(Type, Event, State) -> + gen_state(connection, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(Type, Event, State) -> + ssl_gen_statem:downgrade(Type, Event, State). + +%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- +callback_mode() -> + state_functions. + +terminate(Reason, StateName, State) -> + ssl_gen_statem:terminate(Reason, StateName, State). + +format_status(Type, Data) -> + ssl_gen_statem:format_status(Type, Data). + +code_change(_OldVsn, StateName, State, _) -> + {ok, StateName, State}. + +%%==================================================================== +%% Internal functions +%%==================================================================== +handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State) -> + #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 = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, + session = Session0, + ssl_options = SslOpts} = State, + SessionTracker = proplists:get_value(session_id_tracker, Trackers), + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt0, HashSign} = + tls_handshake:hello(Hello, + SslOpts, + {SessionTracker, Session0, + ConnectionStates0, CertKeyAlts, KeyExAlg}, + Renegotiation), + 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 + }}. + +choose_tls_fsm(#{versions := Versions}, + #client_hello{ + extensions = #{client_hello_versions := + #client_hello_versions{versions = ClientVersions} + } + }) -> + case ssl_handshake:select_supported_version(ClientVersions, Versions) of + ?TLS_1_3 -> + tls_1_3_fsm; + _Else -> + tls_1_0_to_1_2_fsm + end; +choose_tls_fsm(_, _) -> + tls_1_0_to_1_2_fsm. + +gen_state(StateName, Type, Event, State) -> + try tls_dtls_server_connection:StateName(Type, Event, State) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, StateName, State) + end. + +renegotiate(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + handshake_env = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0} = State0, Actions) -> + HelloRequest = ssl_handshake:hello_request(), + Frag = tls_handshake:encode_handshake(HelloRequest, Version), + Hs0 = ssl_handshake:init_handshake_history(), + {BinMsg, ConnectionStates} = + tls_record:encode_handshake(Frag, Version, ConnectionStates0), + tls_socket:send(Transport, Socket, BinMsg), + State = State0#state{connection_states = + ConnectionStates, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, + tls_gen_connection:next_event(hello, no_record, State, Actions). + +next_statem_state([Version]) -> + case ssl:tls_version(Version) of + ?TLS_1_3 -> + start; + _ -> + hello + end; +next_statem_state(_) -> + hello. + +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. diff --git a/lib/ssl/src/tls_server_connection_1_3.erl b/lib/ssl/src/tls_server_connection_1_3.erl index f00cf12d7472..b5f4770a1f04 100644 --- a/lib/ssl/src/tls_server_connection_1_3.erl +++ b/lib/ssl/src/tls_server_connection_1_3.erl @@ -117,12 +117,6 @@ init([?SERVER_ROLE, Sender, Host, Port, Socket, Options, User, CbInfo]) -> 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_gen_statem:handle_trusted_certs_db(State), - tls_gen_connection:close(Reason, Socket, Transport, undefined); terminate(Reason, StateName, State) -> ssl_gen_statem:terminate(Reason, StateName, State). @@ -144,7 +138,7 @@ code_change(_OldVsn, StateName, State, _) -> initial_hello(enter, _, State) -> {keep_state, State}; initial_hello(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + tls_server_connection:initial_hello(Type, Event, State). %%-------------------------------------------------------------------- -spec config_error(gen_statem:event_type(), @@ -154,7 +148,7 @@ initial_hello(Type, Event, State) -> config_error(enter, _, State) -> {keep_state, State}; config_error(Type, Event, State) -> - ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + ssl_gen_statem:config_error(Type, Event, State). %%-------------------------------------------------------------------- -spec user_hello(gen_statem:event_type(), @@ -166,30 +160,35 @@ user_hello(enter, _, State) -> user_hello({call, From}, cancel, State) -> gen_statem:reply(From, ok), ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), - ?FUNCTION_NAME, State); + user_hello, State); user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, - #state{handshake_env = #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv, + #state{handshake_env = + #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv, ssl_options = Options0} = State0) -> try ssl:update_options(NewOptions, ?SERVER_ROLE, Options0) of Options = #{versions := Versions} -> State = ssl_gen_statem:ssl_config(Options, ?SERVER_ROLE, State0), case ssl_handshake:select_supported_version(ClientVersions, Versions) of ?TLS_1_3 -> - {next_state, start, State#state{start_or_recv_from = From, - handshake_env = HSEnv#handshake_env{continue_status = continue}}, + {next_state, start, + State#state{start_or_recv_from = From, + handshake_env = HSEnv#handshake_env{continue_status = continue}}, [{{timeout, handshake}, Timeout, close}]}; undefined -> - ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State); + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), + user_hello, State); _Else -> - {next_state, hello, State#state{start_or_recv_from = From, - handshake_env = HSEnv#handshake_env{continue_status = continue}}, - [{change_callback_module, tls_connection}, + {next_state, hello, + State#state{start_or_recv_from = From, + handshake_env = HSEnv#handshake_env{continue_status = continue}}, + [{change_callback_module, tls_server_connection}, {{timeout, handshake}, Timeout, close}]} end catch throw:{error, Reason} -> gen_statem:reply(From, {error, Reason}), - ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0) + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), + user_hello, State0) end; user_hello(Type, Msg, State) -> tls_gen_connection_1_3:user_hello(Type, Msg, State). @@ -201,17 +200,17 @@ user_hello(Type, Msg, State) -> %%-------------------------------------------------------------------- start(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, start, State,[]}; start(internal = Type, #change_cipher_spec{} = Msg, #state{handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) -> case ssl_handshake:init_handshake_history() of Hist -> %% First message must always be client hello - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State); + ssl_gen_statem:handle_common_event(Type, Msg, start, State); _ -> - tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State) + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, start, State) end; start(internal = Type, #change_cipher_spec{} = Msg, State) -> - tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, start, State); start(internal, #client_hello{extensions = #{client_hello_versions := #client_hello_versions{versions = ClientVersions} }} = Hello, @@ -221,7 +220,7 @@ start(internal, #client_hello{extensions = #{client_hello_versions := handle_client_hello(Hello, State); false -> ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), - ?FUNCTION_NAME, State) + start, State) end; start(internal, #client_hello{extensions = #{client_hello_versions := #client_hello_versions{versions = ClientVersions} @@ -237,22 +236,23 @@ start(internal, #client_hello{} = Hello, handle_client_hello(Hello, State); start(internal, #client_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, %% so it is a previous version hello. - ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0); + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), start, State0); start(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, start, State); start(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, start, State). %%-------------------------------------------------------------------- -spec negotiated(gen_statem:event_type(), - {start_handshake, #pre_shared_key_server_hello{} | undefined} | term(), #state{}) -> + {start_handshake, #pre_shared_key_server_hello{} | undefined} | + term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- negotiated(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, negotiated, State,[]}; negotiated(internal = Type, #change_cipher_spec{} = Msg, State) -> - tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, negotiated, State); negotiated(internal, {start_handshake, _} = Message, State0) -> case send_hello_flight(Message, State0) of #alert{} = Alert -> @@ -261,9 +261,9 @@ negotiated(internal, {start_handshake, _} = Message, State0) -> {next_state, NextState, State, []} end; negotiated(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, negotiated, State); negotiated(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, negotiated, State). %%-------------------------------------------------------------------- -spec wait_cert(gen_statem:event_type(), @@ -301,9 +301,9 @@ wait_cv(Type, Msg, State) -> %%-------------------------------------------------------------------- wait_finished(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_finished, State,[]}; wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) -> - tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, wait_finished, State); wait_finished(internal, #finished{verify_data = VerifyData}, State0) -> {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), @@ -324,12 +324,12 @@ wait_finished(internal, [{{timeout, handshake}, cancel}]) catch {Ref, #alert{} = Alert} -> - ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0) + ssl_gen_statem:handle_own_alert(Alert, wait_finished, State0) end; wait_finished(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_finished, State); wait_finished(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_finished, State). %%-------------------------------------------------------------------- -spec wait_eoed(gen_statem:event_type(), @@ -338,9 +338,9 @@ wait_finished(Type, Msg, State) -> %%-------------------------------------------------------------------- wait_eoed(enter, _, State0) -> State = tls_gen_connection_1_3:handle_middlebox(State0), - {next_state, ?FUNCTION_NAME, State,[]}; + {next_state, wait_eoed, State,[]}; wait_eoed(internal = Type, #change_cipher_spec{} = Msg, State) -> - tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, wait_eoed, State); wait_eoed(internal, #end_of_early_data{}, #state{handshake_env = HsEnv0} = State0) -> try State = ssl_record:step_encryption_state_read(State0), @@ -353,9 +353,9 @@ wait_eoed(internal, #end_of_early_data{}, #state{handshake_env = HsEnv0} = State wait_eoed, State0) end; wait_eoed(info, Msg, State) -> - tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Msg, wait_eoed, State); wait_eoed(Type, Msg, State) -> - ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + ssl_gen_statem:handle_common_event(Type, Msg, wait_eoed, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), @@ -460,7 +460,8 @@ do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers, MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) -> ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), - HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum}, + HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = + MaxFragEnum}, State1#state{handshake_env = HsEnv1, session = Session, connection_states = ConnectionStates1}; @@ -469,8 +470,10 @@ do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers, end, State3 = case maps:get(keep_secrets, Opts, false) of - true -> tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random); - false -> State2 + true -> + tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random); + false -> + State2 end, State4 = tls_handshake_1_3:update_start_state(State3, @@ -604,14 +607,17 @@ validate_cookie(#cookie{cookie = Cookie0}, #state{ssl_options = #{cookie := true validate_cookie(_,_) -> {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}. -session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State, negotiated}, _) -> +session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State, + negotiated}, _) -> {ok, {State, negotiated, undefined}}; % Resumption prohibited -session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined = PSK) +session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, + negotiated}, undefined = PSK) when Tickets =/= disabled -> {ok, {State, negotiated, PSK}}; % No resumption session_resumption({#state{ssl_options = #{session_tickets := Tickets}, handshake_env = #handshake_env{ - early_data_accepted = false}} = State0, negotiated}, PSKInfo) + early_data_accepted = false}} = State0, + negotiated}, PSKInfo) when Tickets =/= disabled -> % Resumption but early data prohibited State1 = tls_gen_connection_1_3:handle_resumption(State0, ok), {Index, PSK, PeerCert} = PSKInfo, @@ -620,7 +626,8 @@ session_resumption({#state{ssl_options = #{session_tickets := Tickets}, {ok, {State, negotiated, {Index, PSK}}}; session_resumption({#state{ssl_options = #{session_tickets := Tickets}, handshake_env = #handshake_env{ - early_data_accepted = true}} = State0, negotiated}, PSKInfo) + early_data_accepted = true}} = State0, + negotiated}, PSKInfo) when Tickets =/= disabled -> % Resumption with early data allowed State1 = tls_gen_connection_1_3:handle_resumption(State0, ok), %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed. @@ -665,11 +672,13 @@ maybe_send_session_ticket(#state{connection_states = ConnectionStates, {State, _} = Connection:send_handshake(Ticket, State0), maybe_send_session_ticket(State, N - 1). -new_session_ticket(Tracker, HKDF, RMS, #state{ssl_options = #{session_tickets := stateful_with_cert}, - session = #session{peer_certificate = PeerCert}}) -> +new_session_ticket(Tracker, HKDF, RMS, + #state{ssl_options = #{session_tickets := stateful_with_cert}, + session = #session{peer_certificate = PeerCert}}) -> tls_server_session_ticket:new(Tracker, HKDF, RMS, PeerCert); -new_session_ticket(Tracker, HKDF, RMS, #state{ssl_options = #{session_tickets := stateless_with_cert}, - session = #session{peer_certificate = PeerCert}}) -> +new_session_ticket(Tracker, HKDF, RMS, + #state{ssl_options = #{session_tickets := stateless_with_cert}, + session = #session{peer_certificate = PeerCert}}) -> tls_server_session_ticket:new(Tracker, HKDF, RMS, PeerCert); new_session_ticket(Tracker, HKDF, RMS, _) -> tls_server_session_ticket:new(Tracker, HKDF, RMS, undefined). @@ -725,7 +734,8 @@ select_server_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = %% If there does not exist a default or fallback %% from previous alternatives use this alternative %% as fallback. - Fallback = {fallback, Session#session{own_certificates = Certs, private_key = Key}}, + Fallback = {fallback, + Session#session{own_certificates = Certs, private_key = Key}}, select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, CertAuths, State, default_or_fallback(Default0, Fallback)) @@ -819,7 +829,8 @@ maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Con signature_algs := SignAlgs, signature_algs_cert := SignAlgsCert} = Opts, _) -> AddCertAuth = maps:get(certificate_authorities, Opts, true), - CertificateRequest = tls_handshake_1_3:certificate_request(SignAlgs, SignAlgsCert, CertDbHandle, + CertificateRequest = tls_handshake_1_3:certificate_request(SignAlgs, SignAlgsCert, + CertDbHandle, CertDbRef, AddCertAuth), {Connection:queue_handshake(CertificateRequest, State), wait_cert}. @@ -871,7 +882,8 @@ validate_client_key_share(_ ,[]) -> ok; validate_client_key_share([], _) -> {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; -validate_client_key_share([Group | ClientGroups], [#key_share_entry{group = Group} | ClientShares]) -> +validate_client_key_share([Group | ClientGroups], + [#key_share_entry{group = Group} | ClientShares]) -> validate_client_key_share(ClientGroups, ClientShares); validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) -> validate_client_key_share(ClientGroups, ClientShares).