diff options
Diffstat (limited to 'lib/ssl/src')
65 files changed, 11419 insertions, 7508 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 03c39b4722..5edd6cb4b9 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -47,9 +47,12 @@ MODULES= \ dtls_connection \ dtls_connection_sup \ dtls_handshake \ + dtls_gen_connection \ dtls_listener_sup \ dtls_packet_demux \ dtls_record \ + dtls_server_sup\ + dtls_server_session_cache_sup\ dtls_sup \ dtls_socket \ dtls_v1 \ @@ -62,8 +65,8 @@ MODULES= \ ssl_certificate \ ssl_cipher \ ssl_cipher_format \ + ssl_client_session_cache_db \ ssl_config \ - ssl_connection \ ssl_connection_sup \ ssl_crl \ ssl_crl_cache \ @@ -72,6 +75,7 @@ MODULES= \ ssl_dist_admin_sup \ ssl_dist_connection_sup \ ssl_dist_sup \ + ssl_gen_statem \ ssl_handshake \ ssl_listen_tracker_sup \ ssl_logger \ @@ -79,20 +83,26 @@ MODULES= \ ssl_pem_cache \ ssl_pkix_db \ ssl_record \ + ssl_server_session_cache \ + ssl_server_session_cache_db \ + ssl_server_session_cache_sup \ + ssl_upgrade_server_session_cache_sup \ ssl_session \ - ssl_session_cache \ ssl_srp_primes \ ssl_sup \ - ssl_v3 \ tls_bloom_filter \ + tls_dtls_connection \ tls_connection \ tls_connection_sup \ tls_connection_1_3 \ + tls_gen_connection \ tls_handshake \ tls_handshake_1_3 \ tls_record \ tls_record_1_3 \ tls_client_ticket_store \ + tls_dist_sup \ + tls_dist_server_sup \ tls_sender \ tls_server_session_ticket\ tls_server_session_ticket_sup\ @@ -142,15 +152,21 @@ DEPDIR=$(ERL_TOP)/lib/ssl/src/deps DEP_FILE=$(DEPDIR)/ssl.d $(shell mkdir -p $(dir $(DEP_FILE)) >/dev/null) +ifeq ($(TARGET), win32) + # Native path without C: ignore driveletter case + ERL_TOP_NATIVE = $(shell w32_path.sh -m $(ERL_TOP) | sed "s@[a-zA-Z]:@:@") +else + ERL_TOP_NATIVE = $(ERL_TOP) +endif + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- -EXTRA_ERLC_FLAGS = +warn_unused_vars +EXTRA_ERLC_FLAGS = +warn_unused_vars -Werror ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \ -pz $(EBIN) \ -pz $(ERL_TOP)/lib/public_key/ebin \ - $(EXTRA_ERLC_FLAGS) - + $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\" # ---------------------------------------------------- # Targets @@ -159,8 +175,9 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \ $(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) $(DEP_FILE): $(ERL_FILES) + @echo SED $(TARGET) $(ERL_TOP_NATIVE) $(gen_verbose)erlc -M $(ERL_FILES) \ - | sed "s@$(ERL_TOP)@../../..@g" \ + | sed "s@[a-zA-Z]\?$(ERL_TOP_NATIVE)@../../..@g" \ | sed "s/\.$(EMULATOR)/\.$$\(EMULATOR\)/" \ | sed 's@^dtls_@$$(EBIN)/dtls_@' \ | sed 's@^inet_@$$(EBIN)/inet_@' \ diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 975dc5fc4e..78348826e4 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -17,12 +17,105 @@ %% %% %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] / +%% 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 +%% 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_STAPELING CERTIFY <----------------------------------> ABBRIVIATED +%% +%% <- 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"). @@ -31,34 +124,31 @@ -include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). --include_lib("public_key/include/public_key.hrl"). --include_lib("kernel/include/logger.hrl"). %% Internal application API %% Setup --export([start_fsm/8, start_link/7, init/1, pids/1]). - -%% State transition handling --export([next_event/3, next_event/4, handle_protocol_record/3]). - -%% Handshake handling --export([renegotiate/2, send_handshake/2, - queue_handshake/2, queue_change_cipher/2, - reinit/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]). +-export([init/1]). -%% Alert and close handling --export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]). - -%% Data handling --export([socket/4, setopts/3, getopts/3]). +-export([renegotiate/2]). %% gen_statem state functions --export([init/3, error/3, downgrade/3, %% Initiation and take down states - hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states +-export([initial_hello/3, + config_error/3, + downgrade/3, + hello/3, + user_hello/3, + wait_ocsp_stapling/3, + certify/3, + cipher/3, + abbreviated/3, connection/3]). + %% gen_statem callbacks --export([callback_mode/0, terminate/3, code_change/4, format_status/2]). +-export([callback_mode/0, + terminate/3, + code_change/4, + format_status/2]). %%==================================================================== %% Internal application API @@ -66,373 +156,47 @@ %%==================================================================== %% Setup %%==================================================================== -start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts, - User, {CbModule, _, _, _, _} = CbInfo, - Timeout) -> - try - {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker), - ssl_connection:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error - end. - -%%-------------------------------------------------------------------- --spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> - {ok, pid()} | ignore | {error, reason()}. -%% -%% Description: Creates a gen_statem process which calls Module:init/1 to -%% initialize. -%%-------------------------------------------------------------------- -start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. - init([Role, Host, Port, Socket, Options, User, CbInfo]) -> process_flag(trap_exit, true), - State0 = #state{protocol_specific = Map} = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), + State0 = #state{protocol_specific = Map} = + initial_state(Role, Host, Port, Socket, Options, User, CbInfo), try - State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - gen_statem:enter_loop(?MODULE, [], init, State) + State = ssl_gen_statem:ssl_config(State0#state.ssl_options, + Role, State0), + gen_statem:enter_loop(?MODULE, [], initial_hello, State) catch throw:Error -> - EState = State0#state{protocol_specific = Map#{error => Error}}, - gen_statem:enter_loop(?MODULE, [], error, EState) - end. - -pids(_) -> - [self()]. - -%%==================================================================== -%% State transition handling -%%==================================================================== -next_record(#state{handshake_env = - #handshake_env{unprocessed_handshake_events = N} = HsEnv} - = State) when N > 0 -> - {no_record, State#state{handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; -next_record(#state{protocol_buffers = - #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]} - = Buffers, - connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) -> - CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read), - case dtls_record:replay_detect(CT, CurrentRead) of - false -> - decode_cipher_text(State#state{connection_states = ConnectionStates}) ; - true -> - %% Ignore replayed record - next_record(State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnectionStates}) - end; -next_record(#state{protocol_buffers = - #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]} - = Buffers, - connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State) - when Epoch > CurrentEpoch -> - %% TODO Buffer later Epoch message, drop it for now - next_record(State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnectionStates}); -next_record(#state{protocol_buffers = - #protocol_buffers{dtls_cipher_texts = [ _ | Rest]} - = Buffers, - connection_states = ConnectionStates} = State) -> - %% Drop old epoch message - next_record(State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnectionStates}); -next_record(#state{static_env = #static_env{role = server, - socket = {Listener, {Client, _}}}} = State) -> - dtls_packet_demux:active_once(Listener, Client, self()), - {no_record, State}; -next_record(#state{protocol_specific = #{active_n_toggle := true, - active_n := N} = ProtocolSpec, - static_env = #static_env{role = client, - socket = {_Server, Socket} = DTLSSocket, - close_tag = CloseTag, - transport_cb = Transport}} = State) -> - case dtls_socket:setopts(Transport, Socket, [{active,N}]) of - ok -> - {no_record, State#state{protocol_specific = - ProtocolSpec#{active_n_toggle => false}}}; - _ -> - self() ! {CloseTag, DTLSSocket}, - {no_record, State} - end; -next_record(State) -> - {no_record, State}. - -next_event(StateName, Record, State) -> - next_event(StateName, Record, State, []). - -next_event(StateName, no_record, - #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case next_record(State0) of - {no_record, State} -> - ssl_connection:hibernate_after(StateName, State, Actions); - {#ssl_tls{epoch = CurrentEpoch, - type = ?HANDSHAKE, - version = Version} = Record, State1} -> - State = dtls_version(StateName, Version, State1), - {next_state, StateName, State, - [{next_event, internal, {protocol_record, Record}} | Actions]}; - {#ssl_tls{epoch = CurrentEpoch} = Record, State} -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; - {#ssl_tls{epoch = Epoch, - type = ?HANDSHAKE, - version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake - {#ssl_tls{epoch = Epoch, - type = ?CHANGE_CIPHER_SPEC, - version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - {#ssl_tls{epoch = _Epoch, - version = _Version}, State} -> - %% TODO maybe buffer later epoch - next_event(StateName, no_record, State, Actions); - {#alert{} = Alert, State} -> - Version = State#state.connection_env#connection_env.negotiated_version, - handle_own_alert(Alert, Version, StateName, State) - end; -next_event(connection = StateName, Record, - #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case Record of - #ssl_tls{epoch = CurrentEpoch, - type = ?HANDSHAKE, - version = Version} = Record -> - State = dtls_version(StateName, Version, State0), - {next_state, StateName, State, - [{next_event, internal, {protocol_record, Record}} | Actions]}; - #ssl_tls{epoch = CurrentEpoch} -> - {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]}; - #ssl_tls{epoch = Epoch, - type = ?HANDSHAKE, - version = _Version} when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake - #ssl_tls{epoch = Epoch, - type = ?CHANGE_CIPHER_SPEC, - version = _Version} when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - _ -> - next_event(StateName, no_record, State0, Actions) - end; -next_event(StateName, Record, - #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case Record of - #ssl_tls{epoch = CurrentEpoch, - version = Version} = Record -> - State = dtls_version(StateName, Version, State0), - {next_state, StateName, State, - [{next_event, internal, {protocol_record, Record}} | Actions]}; - #ssl_tls{epoch = _Epoch, - version = _Version} = _Record -> - %% TODO maybe buffer later epoch - next_event(StateName, no_record, State0, Actions); - #alert{} = Alert -> - Version = State0#state.connection_env#connection_env.negotiated_version, - handle_own_alert(Alert, Version, StateName, State0) + EState = State0#state{protocol_specific = + Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], config_error, EState) end. - -%%% DTLS record protocol level application data messages - -handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) -> - case ssl_connection:read_application_data(Data, State0) of - {stop, _, _} = Stop-> - Stop; - {Record, State1} -> - {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1), - ssl_connection:hibernate_after(StateName, State, Actions) - end; -%%% DTLS record protocol level handshake messages -handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, - fragment = Data}, - StateName, - #state{protocol_buffers = Buffers0, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = Options} = State) -> - try - case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of - {[], Buffers} -> - next_event(StateName, no_record, State#state{protocol_buffers = Buffers}); - {Packets, Buffers} -> - HsEnv = State#state.handshake_env, - Events = dtls_handshake_events(Packets), - {next_state, StateName, - State#state{protocol_buffers = Buffers, - handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events - = unprocessed_events(Events)}}, Events} - end - catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State) - end; -%%% DTLS record protocol level change cipher messages -handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> - {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; -%%% DTLS record protocol level Alert messages -handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - case decode_alerts(EncAlerts) of - Alerts = [_|_] -> - handle_alerts(Alerts, {next_state, StateName, State}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State) - end; -%% Ignore unknown TLS record level protocol messages -handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> - {next_state, StateName, State, []}. - %%==================================================================== -%% Handshake handling +%% Handshake %%==================================================================== - renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) -> %% Handle same way as if server requested %% the renegotiation - State = reinit_handshake_data(State0), + 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} = send_handshake(HelloRequest, State1), - next_event(hello, no_record, State, Actions ++ MoreActions). - -send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> - #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), - send_handshake_flight(queue_handshake(Handshake, State), Epoch). - -queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := HsBuffer0, - change_cipher_spec := undefined, - next_sequence := Seq} = Flight0, - ssl_options = #{log_level := LogLevel}} = State) -> - Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), - Hist = update_handshake_history(Handshake0, Handshake, Hist0), - ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), - - State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0], - next_sequence => Seq +1}, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}; - -queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0, - next_sequence := Seq} = Flight0, - ssl_options = #{log_level := LogLevel}} = State) -> - Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), - Hist = update_handshake_history(Handshake0, Handshake, Hist0), - ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), - - State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0], - next_sequence => Seq +1}, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}. - -queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, - connection_states = ConnectionStates0} = State) -> - ConnectionStates = - dtls_record:next_epoch(ConnectionStates0, write), - State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher}, - connection_states = ConnectionStates}. - -reinit(State) -> - %% To be API compatible with TLS NOOP here - reinit_handshake_data(State). -reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag}, - protocol_buffers = Buffers, - protocol_specific = PS, - handshake_env = HsEnv} = State) -> - State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), - public_key_info = undefined, - premaster_secret = undefined}, - protocol_specific = PS#{flight_state => initial_flight_state(DataTag)}, - flight_buffer = new_flight(), - protocol_buffers = - Buffers#protocol_buffers{ - dtls_handshake_next_seq = 0, - dtls_handshake_next_fragments = [], - dtls_handshake_later_fragments = [] - }}. - -select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> - SNI; -select_sni_extension(_) -> - undefined. - -empty_connection_state(ConnectionEnd, BeastMitigation) -> - Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), - dtls_record:empty_connection_state(Empty). - -%%==================================================================== -%% Alert and close handling -%%==================================================================== -encode_alert(#alert{} = Alert, Version, ConnectionStates) -> - dtls_record:encode_alert_record(Alert, Version, ConnectionStates). - -send_alert(Alert, #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - - connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0) -> - {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), - send(Transport, Socket, BinMsg), - ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - State0#state{connection_states = ConnectionStates}. - -send_alert_in_connection(Alert, State) -> - _ = send_alert(Alert, State), - ok. - -close(downgrade, _,_,_,_) -> - ok; -%% Other -close(_, Socket, Transport, _,_) -> - dtls_socket:close(Transport,Socket). - -protocol_name() -> - "DTLS". - -%%==================================================================== -%% Data handling -%%==================================================================== - -send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> % Server socket - dtls_socket:send(Transport, Socket, Data); -send(Transport, Socket, Data) -> % Client socket - dtls_socket:send(Transport, Socket, Data). - -socket(Pid, Transport, Socket, _Tracker) -> - dtls_socket:socket(Pid, Transport, Socket, ?MODULE). - -setopts(Transport, Socket, Other) -> - dtls_socket:setopts(Transport, Socket, Other). - -getopts(Transport, Socket, Tag) -> - dtls_socket:getopts(Transport, Socket, Tag). + {State, MoreActions} = dtls_gen_connection:send_handshake(HelloRequest, State1), + dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions). %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec init(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -init(enter, _, State) -> +initial_hello(enter, _, State) -> {keep_state, State}; -init({call, From}, {start, Timeout}, +initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{host = Host, port = Port, role = client, @@ -441,48 +205,49 @@ init({call, From}, {start, Timeout}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, connection_env = CEnv, ssl_options = #{versions := Versions} = SslOpts, - session = #session{own_certificate = Cert} = NewSession, + session = #session{own_certificates = OwnCerts} = NewSession, connection_states = ConnectionStates0 } = State0) -> Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation, Cert), + Session#session.session_id, Renegotiation, OwnCerts), + 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}}), - {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), + 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}, - next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]); -init({call, _} = Type, Event, #state{static_env = #static_env{role = server}, - protocol_specific = PS} = State) -> - Result = gen_handshake(?FUNCTION_NAME, Type, Event, - State#state{protocol_specific = PS#{current_cookie_secret => dtls_v1:cookie_secret(), + 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 = PS} = State) -> + Result = ssl_gen_statem:?FUNCTION_NAME(Type, Event, + State#state{protocol_specific = + PS#{current_cookie_secret => dtls_v1:cookie_secret(), previous_cookie_secret => <<>>, ignored_alerts => 0, max_ignored_alerts => 10}}), erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), Result; -init(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). +initial_hello(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- --spec error(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -error(enter, _, State) -> +config_error(enter, _, State) -> {keep_state, State}; -error({call, From}, {start, _Timeout}, - #state{protocol_specific = #{error := Error}} = State) -> - {stop_and_reply, {shutdown, normal}, - [{reply, From, {error, Error}}], State}; -error({call, _} = Call, Msg, State) -> - gen_handshake(?FUNCTION_NAME, Call, Msg, State); -error(_, _, _) -> - {keep_state_and_data, [postpone]}. +config_error(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- -spec hello(gen_statem:event_type(), @@ -503,90 +268,114 @@ hello(internal, #client_hello{cookie = <<>>, handshake_env = HsEnv, connection_env = CEnv, protocol_specific = #{current_cookie_secret := Secret}} = State0) -> - {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), - State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), - {State, Actions} = send_handshake(VerifyRequest, State1), - next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{ - tls_handshake_history = - ssl_handshake:init_handshake_history()}}, - Actions); -hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client, - host = Host, - port = Port}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, - connection_env = CEnv, - ssl_options = SslOpts, - session = #session{own_certificate = Cert, session_id = Id}, - connection_states = ConnectionStates0 - } = State0) -> - + case 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); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version,?FUNCTION_NAME, State0) + end; +hello(internal, #hello_verify_request{cookie = Cookie}, + #state{static_env = #static_env{role = client, + host = Host, + port = Port}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState0} = HsEnv, + connection_env = CEnv, + ssl_options = #{ocsp_stapling := OcspStaplingOpt, + ocsp_nonce := OcspNonceOpt} = SslOpts, + session = #session{own_certificates = OwnCerts, + session_id = Id}, + connection_states = ConnectionStates0 + } = State0) -> + OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt), Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, - SslOpts, Id, Renegotiation, Cert), + SslOpts, Id, Renegotiation, OwnCerts, 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()}}), + HsEnv#handshake_env{tls_handshake_history + = ssl_handshake:init_handshake_history(), + ocsp_stapling_state = + OcspState0#{ocsp_nonce => OcspNonce}}}), - {State2, Actions} = send_handshake(Hello, State1), + {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1), + State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version} % RequestedVersion }, - next_event(?FUNCTION_NAME, no_record, State, Actions); -hello(internal, #client_hello{extensions = Extensions} = Hello, + dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions); +hello(internal, #client_hello{extensions = Extensions, client_version = ClientVersion} = Hello, #state{ssl_options = #{handshake := hello}, handshake_env = HsEnv, - start_or_recv_from = From} = State) -> - {next_state, user_hello, State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{hello = Hello}}, - [{reply, From, {ok, Extensions}}]}; + start_or_recv_from = From} = State0) -> + case tls_dtls_connection:handle_sni_extension(State0, Hello) of + #state{} = State -> + {next_state, user_hello, State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{hello = Hello}}, + [{reply, From, {ok, Extensions}}]}; + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, ?FUNCTION_NAME, State0) + end; hello(internal, #server_hello{extensions = Extensions} = Hello, - #state{ssl_options = #{handshake := hello}, + #state{ssl_options = #{ + handshake := hello}, handshake_env = HsEnv, start_or_recv_from = From} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{hello = Hello}}, - [{reply, From, {ok, Extensions}}]}; - + handshake_env = HsEnv#handshake_env{ + hello = Hello}}, + [{reply, From, {ok, Extensions}}]}; hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server, transport_cb = Transport, socket = Socket}, protocol_specific = #{current_cookie_secret := Secret, previous_cookie_secret := PSecret} - } = State0) -> + } = State) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), case dtls_handshake:cookie(Secret, IP, Port, Hello) of Cookie -> - handle_client_hello(Hello, State0); + handle_client_hello(Hello, State); _ -> case dtls_handshake:cookie(PSecret, IP, Port, Hello) of Cookie -> - handle_client_hello(Hello, State0); + handle_client_hello(Hello, State); _ -> %% Handle bad cookie as new cookie request RFC 6347 4.1.2 - hello(internal, Hello#client_hello{cookie = <<>>}, State0) + hello(internal, Hello#client_hello{cookie = <<>>}, State) end end; hello(internal, #server_hello{} = Hello, #state{ static_env = #static_env{role = client}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, + handshake_env = #handshake_env{ + renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState0} = HsEnv, connection_env = #connection_env{negotiated_version = ReqVersion}, connection_states = ConnectionStates0, + session = #session{session_id = OldId}, ssl_options = SslOptions} = State) -> - case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); - {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> - ssl_connection:handle_session(Hello, - Version, NewId, ConnectionStates, ProtoExt, Protocol, State) + case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); + {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} -> + 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)}}) end; hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) -> %% Initial hello should not be in handshake history @@ -595,8 +384,9 @@ 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} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), + {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) -> @@ -635,6 +425,21 @@ 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(). @@ -645,10 +450,11 @@ certify(enter, _, State0) -> certify(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); certify(internal = Type, #server_hello_done{} = Event, State) -> - ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE); + gen_handshake(?FUNCTION_NAME, Type, Event, prepare_flight(State)); certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> - {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), + {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) -> @@ -669,17 +475,16 @@ cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event, #state{connection_states = ConnectionStates0} = State) -> ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), - ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE); + gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates}); cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates, protocol_specific = PS} = State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, - prepare_flight(State#state{connection_states = ConnectionStates, - protocol_specific = PS#{flight_state => connection}}), - ?MODULE); + gen_handshake(?FUNCTION_NAME, Type, Event, + prepare_flight(State#state{connection_states = ConnectionStates, + protocol_specific = PS#{flight_state => connection}})); cipher(state_timeout, Event, State) -> handle_state_timeout(Event, ?FUNCTION_NAME, State); cipher(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), @@ -698,7 +503,7 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho }, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, connection_env = CEnv, - session = #session{own_certificate = Cert} = Session0, + session = #session{own_certificates = OwnCerts} = Session0, ssl_options = #{versions := Versions} = SslOpts, connection_states = ConnectionStates0, protocol_specific = PS @@ -706,16 +511,20 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation, Cert), + Session#session.session_id, Renegotiation, OwnCerts), Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, Versions), State1 = prepare_flight(State0), - {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), - State = State2#state{protocol_specific = PS#{flight_state => initial_flight_state(DataTag)}, + {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}, - 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) -> + 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 @@ -725,20 +534,21 @@ connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{ro {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}, +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 = send_alert(Alert, State0), - {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), - next_event(?FUNCTION_NAME, Record, State); + 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({call, From}, {application_data, Data}, State) -> try send_application_data(Data, From, ?FUNCTION_NAME, State) catch throw:Error -> - ssl_connection:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) + ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) end; connection(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %%TODO does this make sense for DTLS ? %%-------------------------------------------------------------------- @@ -748,7 +558,7 @@ connection(Type, Event, State) -> downgrade(enter, _, State) -> {keep_state, State}; downgrade(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- %% gen_statem callbacks @@ -757,40 +567,29 @@ callback_mode() -> [state_functions, state_enter]. terminate(Reason, StateName, State) -> - ssl_connection:terminate(Reason, StateName, State). + ssl_gen_statem:terminate(Reason, StateName, State). code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. format_status(Type, Data) -> - ssl_connection:format_status(Type, Data). + ssl_gen_statem:format_status(Type, Data). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- initial_state(Role, Host, Port, Socket, - {#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, _}, User, + {#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, Trackers}, User, {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> #{beast_mitigation := BeastMitigation} = SSLOptions, ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation), - - SessionCacheCb = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - Cb; - _ -> - ssl_session_cache - end, - InternalActiveN = case application:get_env(ssl, internal_active_n) of - {ok, N} when is_integer(N) -> - N; - _ -> - ?INTERNAL_ACTIVE_N - end, + #{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 = ?MODULE, + protocol_cb = dtls_gen_connection, data_tag = DataTag, close_tag = CloseTag, error_tag = ErrorTag, @@ -798,7 +597,8 @@ initial_state(Role, Host, Port, Socket, host = Host, port = Port, socket = Socket, - session_cache_cb = SessionCacheCb + session_cache_cb = SessionCacheCb, + trackers = Trackers }, #state{static_env = InitStatEnv, @@ -812,253 +612,100 @@ initial_state(Role, Host, Port, Socket, %% We do not want to save the password in the state so that %% could be written in the clear into error logs. ssl_options = SSLOptions#{password => undefined}, - session = #session{is_resumable = new}, + 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(), + flight_buffer = dtls_gen_connection:new_flight(), protocol_specific = #{active_n => InternalActiveN, active_n_toggle => true, - flight_state => initial_flight_state(DataTag)} + flight_state => dtls_gen_connection:initial_flight_state(DataTag)} }. -initial_flight_state(udp)-> - {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}; -initial_flight_state(_) -> - reliable. - -next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ - dtls_record_buffer = Buf0, - dtls_cipher_texts = CT0} = Buffers, - connection_env = #connection_env{negotiated_version = Version}, - static_env = #static_env{data_tag = DataTag}, - ssl_options = SslOpts} = State0) -> - case dtls_record:get_dtls_records(Data, - {DataTag, StateName, Version, - [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]}, - Buf0, SslOpts) of - {Records, Buf1} -> - CT1 = CT0 ++ Records, - next_record(State0#state{protocol_buffers = - Buffers#protocol_buffers{dtls_record_buffer = Buf1, - dtls_cipher_texts = CT1}}); - #alert{} = Alert -> - Alert - end. - - -dtls_handshake_events(Packets) -> - lists:map(fun(Packet) -> - {next_event, internal, {handshake, Packet}} - end, Packets). - -decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers, - connection_states = ConnStates0} = State) -> - case dtls_record:decode_cipher_text(CT, ConnStates0) of - {Plain, ConnStates} -> - {Plain, State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end. - -dtls_version(hello, Version, #state{static_env = #static_env{role = server}, - connection_env = CEnv} = State) -> - State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version -dtls_version(_,_, State) -> - State. - -handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, - #state{connection_states = ConnectionStates0, - static_env = #static_env{port = Port, - session_cache = Cache, - session_cache_cb = CacheCb}, - handshake_env = #handshake_env{kex_algorithm = KeyExAlg, - renegotiation = {Renegotiation, _}, - negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = CEnv, - session = #session{own_certificate = Cert} = Session0, - ssl_options = SslOpts} = State0) -> - - case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, - ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of - #alert{} = Alert -> - handle_own_alert(Alert, ClientVersion, hello, State0); - {Version, {Type, Session}, - ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> - Protocol = case Protocol0 of - undefined -> CurrentProtocol; - _ -> Protocol0 - end, - State = prepare_flight(State0#state{connection_states = ConnectionStates, - connection_env = CEnv#connection_env{negotiated_version = Version}, - handshake_env = HsEnv#handshake_env{ - hashsign_algorithm = HashSign, +handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) -> + case tls_dtls_connection:handle_sni_extension(State0, Hello) of + #state{connection_states = ConnectionStates0, + static_env = #static_env{trackers = Trackers}, + handshake_env = #handshake_env{kex_algorithm = KeyExAlg, + renegotiation = {Renegotiation, _}, + negotiated_protocol = CurrentProtocol} = HsEnv, + connection_env = CEnv, + session = #session{own_certificates = OwnCerts} = Session0, + ssl_options = SslOpts} = State1 -> + SessionTracker = proplists:get_value(session_id_tracker, Trackers), + case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, + ConnectionStates0, OwnCerts, KeyExAlg}, Renegotiation) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State1); + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + + State = prepare_flight(State0#state{connection_states = ConnectionStates, + connection_env = CEnv#connection_env{negotiated_version = Version}, + handshake_env = HsEnv#handshake_env{ + hashsign_algorithm = HashSign, client_hello_version = ClientVersion, - negotiated_protocol = Protocol}, - session = Session}), - - ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt}, - State, ?MODULE) - end. + negotiated_protocol = Protocol}, + session = Session}), + {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]} + end; + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State0) + end. -%% raw data from socket, unpack records -handle_info({Protocol, _, _, _, Data}, StateName, - #state{static_env = #static_env{role = Role, - data_tag = Protocol}} = State0) -> - case next_dtls_record(Data, StateName, State0) of - {Record, State} -> - next_event(StateName, Record, State); - #alert{} = Alert -> - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), - {stop, {shutdown, own_alert}, State0} - end; - -handle_info({PassiveTag, Socket}, StateName, - #state{static_env = #static_env{socket = {_, Socket}, - passive_tag = PassiveTag}, - protocol_specific = PS} = State) -> - next_event(StateName, no_record, - State#state{protocol_specific = PS#{active_n_toggle => true}}); - -handle_info({CloseTag, Socket}, StateName, - #state{static_env = #static_env{ - role = Role, - socket = Socket, - close_tag = CloseTag}, - connection_env = #connection_env{negotiated_version = Version}, - socket_options = #socket_options{active = Active}, - protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, - protocol_specific = PS} = State) -> - %% Note that as of DTLS 1.2 (TLS 1.1), - %% failure to properly close a connection no longer requires that a - %% session not be resumed. This is a change from DTLS 1.0 to conform - %% with widespread implementation practice. - case (Active == false) andalso (CTs =/= []) of - false -> - case Version of - {254, N} when N =< 253 -> - ok; - _ -> - %% As invalidate_sessions here causes performance issues, - %% we will conform to the widespread implementation - %% practice and go aginst the spec - %%invalidate_session(Role, Host, Port, Session) - ok - end, - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown, transport_closed}, State}; - true -> - %% Fixes non-delivery of final DTLS record in {active, once}. - %% Basically allows the application the opportunity to set {active, once} again - %% and then receive the final message. - next_event(StateName, no_record, State#state{ - protocol_specific = PS#{active_n_toggle => true}}) - end; - -handle_info(new_cookie_secret, StateName, - #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) -> - erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), - {next_state, StateName, State#state{protocol_specific = - CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), - previous_cookie_secret => Secret}}}; -handle_info(Msg, StateName, State) -> - ssl_connection:StateName(info, Msg, State, ?MODULE). - handle_state_timeout(flight_retransmission_timeout, StateName, #state{protocol_specific = #{flight_state := {retransmit, _NextTimeout}}} = State0) -> - {State1, Actions0} = send_handshake_flight(State0, - retransmit_epoch(StateName, State0)), - {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0), + {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, + retransmit_epoch(StateName, State0)), + {next_state, StateName, State, Actions} = + dtls_gen_connection:next_event(StateName, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}. -handle_alerts([], Result) -> - Result; -handle_alerts(_, {stop, _, _} = Stop) -> - Stop; -handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); -handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). - -handle_own_alert(Alert, Version, StateName, - #state{static_env = #static_env{data_tag = udp, - role = Role}, - ssl_options = #{log_level := LogLevel}} = State0) -> - case ignore_alert(Alert, State0) of - {true, State} -> - log_ignore_alert(LogLevel, StateName, Alert, Role), - {next_state, StateName, State}; - {false, State} -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State) - end; -handle_own_alert(Alert, Version, StateName, State) -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State). - -encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) -> - Fragments = lists:map(fun(Handshake) -> - dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize) - end, Flight), - dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates). -encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) -> - dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates). - -decode_alerts(Bin) -> - ssl_alert:decode(Bin). gen_handshake(StateName, Type, Event, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try ssl_connection:StateName(Type, Event, State, ?MODULE) of + try tls_dtls_connection:StateName(Type, Event, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), Version, StateName, State) end. gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try dtls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), Version, StateName, State) end; gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try dtls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), Version, StateName, State) end. -unprocessed_events(Events) -> - %% The first handshake event will be processed immediately - %% as it is entered first in the event queue and - %% when it is processed there will be length(Events)-1 - %% handshake events left to process before we should - %% process more TLS-records received on the socket. - erlang:length(Events)-1. -update_handshake_history(#hello_verify_request{}, _, Hist) -> - Hist; -update_handshake_history(_, Handshake, Hist) -> - ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)). prepare_flight(#state{flight_buffer = Flight, connection_states = ConnectionStates0, protocol_buffers = @@ -1069,11 +716,6 @@ prepare_flight(#state{flight_buffer = Flight, protocol_buffers = Buffers#protocol_buffers{ dtls_handshake_next_fragments = [], dtls_handshake_later_fragments = []}}. -new_flight() -> - #{next_sequence => 0, - handshakes => [], - change_cipher_spec => undefined, - handshakes_after_change_cipher_spec => []}. next_flight(Flight) -> Flight#{handshakes => [], @@ -1099,122 +741,11 @@ new_timeout(N) when N =< 30000 -> new_timeout(_) -> 60000. -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := Flight, - change_cipher_spec := undefined}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - %% TODO remove hardcoded Max size - {Encoded, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight), Version, 1400, Epoch, ConnectionStates0), - send(Transport, Socket, Encoded), - ssl_logger:debug(LogLevel, outbound, 'record', Encoded), - {State0#state{connection_states = ConnectionStates}, []}; - -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := [_|_] = Flight0, - change_cipher_spec := ChangeCipher, - handshakes_after_change_cipher_spec := []}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - {HsBefore, ConnectionStates1} = - encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch, ConnectionStates0), - {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1), - - send(Transport, Socket, [HsBefore, EncChangeCipher]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), - ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), - {State0#state{connection_states = ConnectionStates}, []}; - -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := [_|_] = Flight0, - change_cipher_spec := ChangeCipher, - handshakes_after_change_cipher_spec := Flight1}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - {HsBefore, ConnectionStates1} = - encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch-1, ConnectionStates0), - {EncChangeCipher, ConnectionStates2} = - encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1), - {HsAfter, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates2), - send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), - ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), - {State0#state{connection_states = ConnectionStates}, []}; - -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := [], - change_cipher_spec := ChangeCipher, - handshakes_after_change_cipher_spec := Flight1}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - {EncChangeCipher, ConnectionStates1} = - encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0), - {HsAfter, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates1), - send(Transport, Socket, [EncChangeCipher, HsAfter]), - ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), - {State0#state{connection_states = ConnectionStates}, []}. - retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) -> #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), Epoch. -ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N, - max_ignored_alerts := N}} = State) -> - {false, State}; -ignore_alert(#alert{level = ?FATAL} = Alert, - #state{protocol_specific = #{ignored_alerts := N} = PS} = State) -> - case is_ignore_alert(Alert) of - true -> - {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}}; - false -> - {false, State} - end; -ignore_alert(_, State) -> - {false, State}. - -%% RFC 6347 4.1.2.7. Handling Invalid Records -%% recommends to silently ignore invalid DTLS records when -%% upd is the transport. Note we do not support compression so no need -%% include ?DECOMPRESSION_FAILURE -is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) -> - true; -is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) -> - true; -is_ignore_alert(#alert{description = ?DECODE_ERROR}) -> - true; -is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) -> - true; -is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) -> - true; -is_ignore_alert(_) -> - false. - -log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) -> - ssl_logger:log(info, - Level, #{alert => Alert, - alerter => ignored, - statename => StateName, - role => Role, - protocol => protocol_name()}, Location). - send_application_data(Data, From, _StateName, #state{static_env = #static_env{socket = Socket, transport_cb = Transport}, @@ -1232,12 +763,12 @@ send_application_data(Data, From, _StateName, {Msgs, ConnectionStates} = dtls_record:encode_data(Data, Version, ConnectionStates0), State = State0#state{connection_states = ConnectionStates}, - case send(Transport, Socket, Msgs) of + case dtls_gen_connection:send(Transport, Socket, Msgs) of ok -> ssl_logger:debug(LogLevel, outbound, 'record', Msgs), - ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]); + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]); Result -> - ssl_connection:hibernate_after(connection, State, [{reply, From, Result}]) + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}]) end end. diff --git a/lib/ssl/src/dtls_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl index 7d7be5743d..4c5c0a490f 100644 --- a/lib/ssl/src/dtls_connection_sup.erl +++ b/lib/ssl/src/dtls_connection_sup.erl @@ -57,10 +57,10 @@ init(_O) -> MaxT = 3600, Name = undefined, % As simple_one_for_one is used. - StartFunc = {dtls_connection, start_link, []}, + StartFunc = {ssl_gen_statem, start_link, []}, Restart = temporary, % E.g. should not be restarted Shutdown = 4000, - Modules = [dtls_connection, ssl_connection], + Modules = [ssl_gen_statem, dtls_connection], Type = worker, ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl new file mode 100644 index 0000000000..2032d77074 --- /dev/null +++ b/lib/ssl/src/dtls_gen_connection.erl @@ -0,0 +1,685 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: +%%---------------------------------------------------------------------- +-module(dtls_gen_connection). + +-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"). + +%% Setup +-export([start_fsm/8, + pids/1]). + +%% Handshake handling +-export([send_handshake/2, + send_handshake_flight/2, + queue_handshake/2, + queue_change_cipher/2, + reinit/1, + reinit_handshake_data/1, + select_sni_extension/1, + empty_connection_state/2]). + +%% State transition handling +-export([next_event/3, + next_event/4, + handle_protocol_record/3, + new_flight/0, + initial_flight_state/1 + ]). + +%% Data handling +-export([send/3, + socket/4, + setopts/3, + getopts/3, + handle_info/3]). + +%% Alert and close handling +-export([send_alert/2, + send_alert_in_connection/2, + close/5, + protocol_name/0]). +%%==================================================================== +%% Internal application API +%%==================================================================== +%%==================================================================== +%% Setup +%%==================================================================== +start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts, + User, {CbModule, _, _, _, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker), + ssl_gen_statem:handshake(SslSocket, Timeout) + catch + error:{badmatch, {error, _} = Error} -> + Error + end. + +pids(_) -> + [self()]. + +%%==================================================================== +%% State transition handling +%%==================================================================== +next_record(#state{handshake_env = + #handshake_env{unprocessed_handshake_events = N} = HsEnv} + = State) when N > 0 -> + {no_record, State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]} + = Buffers, + connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) -> + CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read), + case dtls_record:replay_detect(CT, CurrentRead) of + false -> + decode_cipher_text(State#state{connection_states = ConnectionStates}) ; + true -> + %% Ignore replayed record + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}) + end; +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]} + = Buffers, + connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State) + when Epoch > CurrentEpoch -> + %% TODO Buffer later Epoch message, drop it for now + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [ _ | Rest]} + = Buffers, + connection_states = ConnectionStates} = State) -> + %% Drop old epoch message + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); +next_record(#state{static_env = #static_env{role = server, + socket = {Listener, {Client, _}}}} = State) -> + dtls_packet_demux:active_once(Listener, Client, self()), + {no_record, State}; +next_record(#state{protocol_specific = #{active_n_toggle := true, + active_n := N} = ProtocolSpec, + static_env = #static_env{role = client, + socket = {_Server, Socket} = DTLSSocket, + close_tag = CloseTag, + transport_cb = Transport}} = State) -> + case dtls_socket:setopts(Transport, Socket, [{active,N}]) of + ok -> + {no_record, State#state{protocol_specific = + ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, DTLSSocket}, + {no_record, State} + end; +next_record(State) -> + {no_record, State}. + +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). + +next_event(StateName, no_record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case next_record(State0) of + {no_record, State} -> + ssl_gen_statem:hibernate_after(StateName, State, Actions); + {#ssl_tls{epoch = CurrentEpoch, + type = ?HANDSHAKE, + version = Version} = Record, State1} -> + State = dtls_version(StateName, Version, State1), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#ssl_tls{epoch = CurrentEpoch} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#ssl_tls{epoch = Epoch, + type = ?HANDSHAKE, + version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake + {#ssl_tls{epoch = Epoch, + type = ?CHANGE_CIPHER_SPEC, + version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + {#ssl_tls{epoch = _Epoch, + version = _Version}, State} -> + %% TODO maybe buffer later epoch + next_event(StateName, no_record, State, Actions); + {#alert{} = Alert, State} -> + Version = State#state.connection_env#connection_env.negotiated_version, + handle_own_alert(Alert, Version, StateName, State) + end; +next_event(connection = StateName, Record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case Record of + #ssl_tls{epoch = CurrentEpoch, + type = ?HANDSHAKE, + version = Version} = Record -> + State = dtls_version(StateName, Version, State0), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = CurrentEpoch} -> + {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = Epoch, + type = ?HANDSHAKE, + version = _Version} when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake + #ssl_tls{epoch = Epoch, + type = ?CHANGE_CIPHER_SPEC, + version = _Version} when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + _ -> + next_event(StateName, no_record, State0, Actions) + end; +next_event(StateName, Record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case Record of + #ssl_tls{epoch = CurrentEpoch, + version = Version} = Record -> + State = dtls_version(StateName, Version, State0), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = _Epoch, + version = _Version} = _Record -> + %% TODO maybe buffer later epoch + next_event(StateName, no_record, State0, Actions); + #alert{} = Alert -> + Version = State0#state.connection_env#connection_env.negotiated_version, + handle_own_alert(Alert, Version, StateName, State0) + end. + +initial_flight_state(udp)-> + {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}; +initial_flight_state(_) -> + reliable. + +new_flight() -> + #{next_sequence => 0, + handshakes => [], + change_cipher_spec => undefined, + handshakes_after_change_cipher_spec => []}. + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := Flight, + change_cipher_spec := undefined}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {Encoded, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight), Version, MaxSize, Epoch, ConnectionStates0), + send(Transport, Socket, Encoded), + ssl_logger:debug(LogLevel, outbound, 'record', Encoded), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := [_|_] = Flight0, + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := []}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {HsBefore, ConnectionStates1} = + encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0), + {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1), + + send(Transport, Socket, [HsBefore, EncChangeCipher]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), + ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := [_|_] = Flight0, + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := Flight1}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {HsBefore, ConnectionStates1} = + encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch-1, ConnectionStates0), + {EncChangeCipher, ConnectionStates2} = + encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1), + {HsAfter, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates2), + send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), + ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := [], + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := Flight1}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {EncChangeCipher, ConnectionStates1} = + encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0), + {HsAfter, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates1), + send(Transport, Socket, [EncChangeCipher, HsAfter]), + ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), + {State0#state{connection_states = ConnectionStates}, []}. + +%%% DTLS record protocol level application data messages + +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) -> + case ssl_gen_statem:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State1} -> + {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1), + ssl_gen_statem:hibernate_after(StateName, State, Actions) + end; +%%% DTLS record protocol level handshake messages +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, + fragment = Data}, + StateName, + #state{protocol_buffers = Buffers0, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = Options} = State) -> + try + case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of + {[], Buffers} -> + next_event(StateName, no_record, State#state{protocol_buffers = Buffers}); + {Packets, Buffers} -> + HsEnv = State#state.handshake_env, + Events = dtls_handshake_events(Packets), + {next_state, StateName, + State#state{protocol_buffers = Buffers, + handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events + = unprocessed_events(Events)}}, Events} + end + catch throw:#alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State) + end; +%%% DTLS record protocol level change cipher messages +handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% DTLS record protocol level Alert messages +handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + case decode_alerts(EncAlerts) of + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + #alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State) + end; +%% Ignore unknown TLS record level protocol messages +handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. + +%%==================================================================== +%% Handshake handling +%%==================================================================== +send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> + #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), + send_handshake_flight(queue_handshake(Handshake, State), Epoch). + +queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := HsBuffer0, + change_cipher_spec := undefined, + next_sequence := Seq} = Flight0, + ssl_options = #{log_level := LogLevel}} = State) -> + Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), + Hist = update_handshake_history(Handshake0, Handshake, Hist0), + ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), + + State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0], + next_sequence => Seq +1}, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}; + +queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0, + next_sequence := Seq} = Flight0, + ssl_options = #{log_level := LogLevel}} = State) -> + Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), + Hist = update_handshake_history(Handshake0, Handshake, Hist0), + ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), + + State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0], + next_sequence => Seq +1}, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}. + +queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, + connection_states = ConnectionStates0} = State) -> + ConnectionStates = + dtls_record:next_epoch(ConnectionStates0, write), + State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher}, + connection_states = ConnectionStates}. + +reinit(State) -> + %% To be API compatible with TLS NOOP here + reinit_handshake_data(State). +reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag}, + protocol_buffers = Buffers, + protocol_specific = PS, + handshake_env = HsEnv} = State) -> + State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), + public_key_info = undefined, + premaster_secret = undefined}, + protocol_specific = PS#{flight_state => initial_flight_state(DataTag)}, + flight_buffer = new_flight(), + protocol_buffers = + Buffers#protocol_buffers{ + dtls_handshake_next_seq = 0, + dtls_handshake_next_fragments = [], + dtls_handshake_later_fragments = [] + }}. + +select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> + SNI; +select_sni_extension(_) -> + undefined. + +empty_connection_state(ConnectionEnd, BeastMitigation) -> + Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + dtls_record:empty_connection_state(Empty). +%%==================================================================== +%% Alert and close handling +%%==================================================================== +encode_alert(#alert{} = Alert, Version, ConnectionStates) -> + dtls_record:encode_alert_record(Alert, Version, ConnectionStates). + +send_alert(Alert, #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + + connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0) -> + {BinMsg, ConnectionStates} = + encode_alert(Alert, Version, ConnectionStates0), + send(Transport, Socket, BinMsg), + ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), + State0#state{connection_states = ConnectionStates}. + +send_alert_in_connection(Alert, State) -> + _ = send_alert(Alert, State), + ok. + +close(downgrade, _,_,_,_) -> + ok; +%% Other +close(_, Socket, Transport, _,_) -> + dtls_socket:close(Transport,Socket). + +protocol_name() -> + "DTLS". + +%%==================================================================== +%% Data handling +%%==================================================================== +send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> + %% Server socket + dtls_socket:send(Transport, Socket, Data); +send(Transport, Socket, Data) -> % Client socket + dtls_socket:send(Transport, Socket, Data). + +socket(Pid, Transport, Socket, _Tracker) -> + dtls_socket:socket(Pid, Transport, Socket, ?MODULE). + +setopts(Transport, Socket, Other) -> + dtls_socket:setopts(Transport, Socket, Other). + +getopts(Transport, Socket, Tag) -> + dtls_socket:getopts(Transport, Socket, Tag). + +%% raw data from socket, unpack records +handle_info({Protocol, _, _, _, Data}, StateName, + #state{static_env = #static_env{role = Role, + data_tag = Protocol}} = State0) -> + case next_dtls_record(Data, StateName, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), + {stop, {shutdown, own_alert}, State0} + end; + +handle_info({PassiveTag, Socket}, StateName, + #state{static_env = #static_env{socket = {_, Socket}, + passive_tag = PassiveTag}, + protocol_specific = PS} = State) -> + next_event(StateName, no_record, + State#state{protocol_specific = PS#{active_n_toggle => true}}); + +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{ + role = Role, + socket = Socket, + close_tag = CloseTag}, + connection_env = #connection_env{negotiated_version = Version}, + socket_options = #socket_options{active = Active}, + protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, + protocol_specific = PS} = State) -> + %% Note that as of DTLS 1.2 (TLS 1.1), + %% failure to properly close a connection no longer requires that a + %% session not be resumed. This is a change from DTLS 1.0 to conform + %% with widespread implementation practice. + case (Active == false) andalso (CTs =/= []) of + false -> + case Version of + {254, N} when N =< 253 -> + ok; + _ -> + %% As invalidate_sessions here causes performance issues, + %% we will conform to the widespread implementation + %% practice and go aginst the spec + %%invalidate_session(Role, Host, Port, Session) + ok + end, + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown, transport_closed}, State}; + true -> + %% Fixes non-delivery of final DTLS record in {active, once}. + %% Basically allows the application the opportunity to set {active, once} again + %% and then receive the final message. + next_event(StateName, no_record, State#state{ + protocol_specific = PS#{active_n_toggle => true}}) + end; + +handle_info(new_cookie_secret, StateName, + #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) -> + erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), + {next_state, StateName, State#state{protocol_specific = + CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), + previous_cookie_secret => Secret}}}; +handle_info(Msg, StateName, State) -> + ssl_gen_statem:handle_info(Msg, StateName, State). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +dtls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). + +unprocessed_events(Events) -> + %% The first handshake event will be processed immediately + %% as it is entered first in the event queue and + %% when it is processed there will be length(Events)-1 + %% handshake events left to process before we should + %% process more TLS-records received on the socket. + erlang:length(Events)-1. + +encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) -> + Fragments = lists:map(fun(Handshake) -> + dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize) + end, Flight), + dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates). + +encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) -> + dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates). + +update_handshake_history(#hello_verify_request{}, _, Hist) -> + Hist; +update_handshake_history(_, Handshake, Hist) -> + ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)). + +next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ + dtls_record_buffer = Buf0, + dtls_cipher_texts = CT0} = Buffers, + connection_env = #connection_env{negotiated_version = Version}, + static_env = #static_env{data_tag = DataTag}, + ssl_options = SslOpts} = State0) -> + case dtls_record:get_dtls_records(Data, + {DataTag, StateName, Version, + [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]}, + Buf0, SslOpts) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(State0#state{protocol_buffers = + Buffers#protocol_buffers{dtls_record_buffer = Buf1, + dtls_cipher_texts = CT1}}); + #alert{} = Alert -> + Alert + end. + + + +decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers, + connection_states = ConnStates0} = State) -> + case dtls_record:decode_cipher_text(CT, ConnStates0) of + {Plain, ConnStates} -> + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnStates}}; + #alert{} = Alert -> + {Alert, State} + end. + +decode_alerts(Bin) -> + ssl_alert:decode(Bin). + +handle_alerts([], Result) -> + Result; +handle_alerts(_, {stop, _, _} = Stop) -> + Stop; +handle_alerts([Alert | Alerts], {next_state, 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_own_alert(Alert, Version, StateName, + #state{static_env = #static_env{data_tag = udp, + role = Role}, + ssl_options = #{log_level := LogLevel}} = State0) -> + case ignore_alert(Alert, State0) of + {true, State} -> + log_ignore_alert(LogLevel, StateName, Alert, Role), + {next_state, StateName, State}; + {false, State} -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State) + end; +handle_own_alert(Alert, Version, StateName, State) -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State). +ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N, + max_ignored_alerts := N}} = State) -> + {false, State}; +ignore_alert(#alert{level = ?FATAL} = Alert, + #state{protocol_specific = #{ignored_alerts := N} = PS} = State) -> + case is_ignore_alert(Alert) of + true -> + {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}}; + false -> + {false, State} + end; +ignore_alert(_, State) -> + {false, State}. + +%% RFC 6347 4.1.2.7. Handling Invalid Records +%% recommends to silently ignore invalid DTLS records when +%% upd is the transport. Note we do not support compression so no need +%% include ?DECOMPRESSION_FAILURE +is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) -> + true; +is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) -> + true; +is_ignore_alert(#alert{description = ?DECODE_ERROR}) -> + true; +is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) -> + true; +is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) -> + true; +is_ignore_alert(_) -> + false. + +log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) -> + ssl_logger:log(info, + Level, #{alert => Alert, + alerter => ignored, + statename => StateName, + role => Role, + protocol => protocol_name()}, Location). + +dtls_version(hello, Version, #state{static_env = #static_env{role = server}, + connection_env = CEnv} = State) -> + State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version +dtls_version(_,_, State) -> + State. diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 892a76bd04..af053ef48c 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -30,16 +30,16 @@ -include("ssl_alert.hrl"). %% Handshake handling --export([client_hello/7, client_hello/8, cookie/4, hello/4, +-export([client_hello/7, client_hello/9, cookie/4, hello/5, hello/4, hello_verify_request/2]). - + %% Handshake encoding -export([fragment_handshake/2, encode_handshake/3]). %% Handshake decodeing -export([get_dtls_handshake/4]). --type dtls_handshake() :: #client_hello{} | #hello_verify_request{} | +-type dtls_handshake() :: #client_hello{} | #hello_verify_request{} | ssl_handshake:ssl_handshake(). %%==================================================================== @@ -47,20 +47,20 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(), - ssl_options(), binary(), boolean(), der_cert()) -> + ssl_options(), binary(), boolean(), [der_cert()]) -> #client_hello{}. %% %% Description: Creates a client hello message. %%-------------------------------------------------------------------- client_hello(Host, Port, ConnectionStates, SslOpts, - Id, Renegotiation, OwnCert) -> + Id, Renegotiation, OwnCerts) -> %% First client hello (two sent in DTLS ) uses empty Cookie client_hello(Host, Port, <<>>, ConnectionStates, SslOpts, - Id, Renegotiation, OwnCert). + Id, Renegotiation, OwnCerts, undefined). %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(), - ssl_options(), binary(),boolean(), der_cert()) -> + ssl_options(), binary(),boolean(), [der_cert()], binary() | undefined) -> #client_hello{}. %% %% Description: Creates a client hello message. @@ -69,7 +69,7 @@ client_hello(_Host, _Port, Cookie, ConnectionStates, #{versions := Versions, ciphers := UserSuites, fallback := Fallback} = SslOpts, - Id, Renegotiation, _OwnCert) -> + Id, Renegotiation, _OwnCert, OcspNonce) -> Version = dtls_record:highest_protocol_version(Versions), Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = maps:get(security_parameters, Pending), @@ -79,7 +79,7 @@ client_hello(_Host, _Port, Cookie, ConnectionStates, Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites, SslOpts, ConnectionStates, Renegotiation, undefined, - undefined), + undefined, OcspNonce), #client_hello{session_id = Id, client_version = Version, @@ -97,15 +97,16 @@ hello(#server_hello{server_version = Version, random = Random, compression_method = Compression, session_id = SessionId, extensions = HelloExt}, #{versions := SupportedVersions} = SslOpt, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, OldId) -> + IsNew = ssl_session:is_new(OldId, SessionId), case dtls_record:is_acceptable_version(Version, SupportedVersions) of true -> handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, - ConnectionStates0, Renegotiation); + ConnectionStates0, Renegotiation, IsNew); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) - end; + end. hello(#client_hello{client_version = ClientVersion} = Hello, #{versions := Versions} = SslOpts, Info, Renegotiation) -> @@ -120,7 +121,7 @@ cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor}, CookieData = [address_to_bin(Address, Port), <<?BYTE(Major), ?BYTE(Minor)>>, Random, SessionId, CipherSuites, CompressionMethods], - crypto:hmac(sha, Key, CookieData). + crypto:mac(hmac, sha, Key, CookieData). %%-------------------------------------------------------------------- -spec hello_verify_request(binary(), ssl_record:ssl_version()) -> #hello_verify_request{}. %% @@ -173,27 +174,28 @@ handle_client_hello(Version, signature_algs := SupportedHashSigns, eccs := SupportedECCs, honor_ecc_order := ECCOrder} = SslOpts, - {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, + {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _}, Renegotiation) -> + OwnCert = ssl_handshake:select_own_cert(OwnCerts), case dtls_record:is_acceptable_version(Version, Versions) of true -> Curves = maps:get(elliptic_curves, HelloExt, undefined), ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), TLSVersion = dtls_v1:corresponding_tls_version(Version), AvailableHashSigns = ssl_handshake:available_signature_algs( - ClientHashSigns, SupportedHashSigns, Cert,TLSVersion), + ClientHashSigns, SupportedHashSigns, OwnCert,TLSVersion), ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, - Port, Session0#session{ecc = ECCCurve}, TLSVersion, - SslOpts, Cache, CacheCb, Cert), + SessIdTracker, Session0#session{ecc = ECCCurve}, TLSVersion, + SslOpts, OwnCert), case CipherSuite of no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); _ -> #{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, Cert, KeyExAlg, + case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, OwnCert, KeyExAlg, SupportedHashSigns, TLSVersion) of #alert{} = Alert -> Alert; @@ -212,7 +214,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, try ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites, HelloExt, dtls_v1:corresponding_tls_version(Version), SslOpts, Session0, - ConnectionStates0, Renegotiation) of + ConnectionStates0, Renegotiation, + Session0#session.is_resumable) of {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} catch throw:Alert -> @@ -220,13 +223,13 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, end. handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, - Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) -> try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite, Compression, HelloExt, dtls_v1:corresponding_tls_version(Version), - SslOpt, ConnectionStates0, Renegotiation) of - {ConnectionStates, ProtoExt, Protocol} -> - {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + SslOpt, ConnectionStates0, Renegotiation, IsNew) of + {ConnectionStates, ProtoExt, Protocol, OcspState} -> + {Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState} catch throw:Alert -> Alert end. diff --git a/lib/ssl/src/dtls_listener_sup.erl b/lib/ssl/src/dtls_listener_sup.erl index ab1c5eee20..4f46407290 100644 --- a/lib/ssl/src/dtls_listener_sup.erl +++ b/lib/ssl/src/dtls_listener_sup.erl @@ -30,8 +30,8 @@ %% API -export([start_link/0]). -export([start_child/1, - lookup_listner/1, - register_listner/2]). + lookup_listener/2, + register_listener/3]). %% Supervisor callback -export([init/1]). @@ -44,32 +44,34 @@ start_link() -> start_child(Args) -> supervisor:start_child(?MODULE, Args). - -lookup_listner(0) -> - undefined; -lookup_listner(Port) -> - try ets:lookup(dtls_listener_sup, Port) of - [{Port, {Owner, Handler}}] -> + +lookup_listener(IP, Port) -> + try ets:lookup(dtls_listener_sup, {IP, Port}) of + [] -> + undefined; + [{{IP, Port}, {Owner, Handler}}] -> case erlang:is_process_alive(Handler) of true -> - case (Owner =/= undefined) andalso erlang:is_process_alive(Owner) of + case (Owner =/= undefined) andalso + erlang:is_process_alive(Owner) of true -> + %% Trying to bind port that is already bound {error, already_listening}; false -> + %% Re-open same listen socket when the handler + %% is dead. {ok, Handler} end; false -> - ets:delete(dtls_listener_sup, Port), + ets:delete(dtls_listener_sup, {IP, Port}), undefined - end; - [] -> - undefined + end catch _:_ -> undefined end. -register_listner(OwnerAndListner, Port) -> - ets:insert(dtls_listener_sup, {Port, OwnerAndListner}). +register_listener(OwnerAndListner, IP, Port) -> + ets:insert(dtls_listener_sup, {{IP, Port}, OwnerAndListner}). %%%========================================================================= %%% Supervisor callback diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index 892f29449d..f9660ea1b6 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -59,7 +59,8 @@ dtls_processes = kv_new(), accepters = queue:new(), first, - close + close, + session_id_tracker }). %%%=================================================================== @@ -101,27 +102,19 @@ getstat(PacketSocket, Opts) -> %%% gen_server callbacks %%%=================================================================== -init([Port, {TransportModule, _,_,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) -> - try - {ok, Socket} = TransportModule:open(Port, InetOptions), - InternalActiveN = case application:get_env(ssl, internal_active_n) of - {ok, N} when is_integer(N) -> - N; - _ -> - ?INTERNAL_ACTIVE_N - end, - - {ok, #state{active_n = InternalActiveN, - port = Port, - first = true, - transport = TransportInfo, - dtls_options = DTLSOptions, - emulated_options = EmOpts, - listener = Socket, - close = false}} - catch _:_ -> - {stop, {shutdown, {error, closed}}} - end. +init([Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) -> + InternalActiveN = get_internal_active_n(), + {ok, SessionIdHandle} = session_id_tracker(Socket, DTLSOptions), + {ok, #state{active_n = InternalActiveN, + port = Port0, + first = true, + transport = TransportInfo, + dtls_options = DTLSOptions, + emulated_options = EmOpts, + listener = Socket, + close = false, + session_id_tracker = SessionIdHandle}}. + handle_call({accept, _}, _, #state{close = true} = State) -> {reply, {error, closed}, State}; @@ -279,9 +272,10 @@ setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes, dtls_options = DTLSOpts, port = Port, listener = Socket, + session_id_tracker = Tracker, emulated_options = EmOpts} = State) -> ConnArgs = [server, "localhost", Port, {self(), {Client, Socket}}, - {DTLSOpts, EmOpts, dtls_listener}, User, dtls_socket:default_cb_info()], + {DTLSOpts, EmOpts, [{session_id_tracker, Tracker}]}, User, dtls_socket:default_cb_info()], case dtls_connection_sup:start_child(ConnArgs) of {ok, Pid} -> erlang:monitor(process, Pid), @@ -358,3 +352,17 @@ emulated_opts_list( Opts, [mode | Rest], Acc) -> emulated_opts_list(Opts, [active | Rest], Acc) -> emulated_opts_list(Opts, Rest, [{active, Opts#socket_options.active} | Acc]). +%% Regardless of the option reuse_sessions we need the session_id_tracker +%% to generate session ids, but no sessions will be stored unless +%% reuse_sessions = true. +session_id_tracker(Listner,_) -> + dtls_server_session_cache_sup:start_child(Listner). + +get_internal_active_n() -> + case application:get_env(ssl, internal_active_n) of + {ok, N} when is_integer(N) -> + N; + _ -> + ?INTERNAL_ACTIVE_N + end. + diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index ee0ce2d22a..16542a8eb3 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -215,8 +215,26 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) -> %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- encode_data(Data, Version, ConnectionStates) -> - #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), - encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates). + #{epoch := Epoch, max_fragment_length := MaxFragmentLength} + = ssl_record:current_connection_state(ConnectionStates, write), + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, + case iolist_size(Data) of + N when N > MaxLength -> + Frags = tls_record:split_iovec(erlang:iolist_to_iovec(Data), MaxLength), + {RevCipherText, ConnectionStates1} = + lists:foldl(fun(Frag, {Acc, CS0}) -> + {CipherText, CS1} = + encode_plain_text(?APPLICATION_DATA, Version, Epoch, Frag, CS0), + {[CipherText|Acc], CS1} + end, {[], ConnectionStates}, Frags), + {lists:reverse(RevCipherText), ConnectionStates1}; + _ -> + encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates) + end. encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) -> Write0 = get_connection_state_by_epoch(Epoch, ConnectionStates, write), @@ -393,7 +411,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> mac_secret => undefined, secure_renegotiation => undefined, client_verify_data => undefined, - server_verify_data => undefined + server_verify_data => undefined, + max_fragment_length => undefined }. get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), diff --git a/lib/ssl/src/dtls_server_session_cache_sup.erl b/lib/ssl/src/dtls_server_session_cache_sup.erl new file mode 100644 index 0000000000..65fbb34918 --- /dev/null +++ b/lib/ssl/src/dtls_server_session_cache_sup.erl @@ -0,0 +1,63 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Supervisor for a listen options tracker +%%---------------------------------------------------------------------- +-module(dtls_server_session_cache_sup). + +-behaviour(supervisor). + +-include("ssl_internal.hrl"). + +%% API +-export([start_link/0]). +-export([start_child/1]). + +%% Supervisor callback +-export([init/1]). + +-define(DEFAULT_MAX_SESSION_CACHE, 1000). +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). +start_child(Listener) -> + supervisor:start_child(?MODULE, [Listener | [ssl_config:pre_1_3_session_opts(server)]]). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 0, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {ssl_server_session_cache, start_link, []}, + Restart = temporary, % E.g. should not be restarted + Shutdown = 4000, + Modules = [ssl_server_session_cache], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. diff --git a/lib/ssl/src/dtls_server_sup.erl b/lib/ssl/src/dtls_server_sup.erl new file mode 100644 index 0000000000..7ec6db3984 --- /dev/null +++ b/lib/ssl/src/dtls_server_sup.erl @@ -0,0 +1,75 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(dtls_server_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + DTLSListeners = dtls_listeners_spec(), + %% Add SessionTracker if we add DTLS-1.3 + Pre_1_3SessionTracker = ssl_server_session_child_spec(), + + {ok, {{one_for_all, 10, 3600}, [DTLSListeners, + Pre_1_3SessionTracker + ]}}. + + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +dtls_listeners_spec() -> + Name = dtls_listener, + StartFunc = {dtls_listener_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +ssl_server_session_child_spec() -> + Name = dtls_server_session_cache_sup, + StartFunc = {dtls_server_session_cache_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [dtls_server_session_cache_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl index 87a4f7ce09..f1569f5069 100644 --- a/lib/ssl/src/dtls_socket.erl +++ b/lib/ssl/src/dtls_socket.erl @@ -46,32 +46,26 @@ send(Transport, {{IP,Port},Socket}, Data) -> Transport:send(Socket, IP, Port, Data). -listen(Port, #config{transport_info = TransportInfo, - ssl = SslOpts, - emulated = EmOpts0, - inet_user = Options} = Config) -> - - Result = case dtls_listener_sup:lookup_listner(Port) of - undefined -> - Result0 = {ok, Listner0} = dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts0, #socket_options{}), - Options ++ internal_inet_values(), SslOpts]), - dtls_listener_sup:register_listner({self(), Listner0}, Port), - Result0; - {ok, Listner0} = Result0 -> - dtls_packet_demux:new_owner(Listner0), - dtls_packet_demux:set_all_opts(Listner0, {Options, emulated_socket_options(EmOpts0, #socket_options{}), SslOpts}), - dtls_listener_sup:register_listner({self(), Listner0}, Port), - Result0; - Result0 -> - Result0 - end, - case Result of - {ok, Listner} -> - Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Listner, Port}}}}, - check_active_n(EmOpts0, Socket), - {ok, Socket}; - Err -> - Err +listen(Port, #config{inet_ssl = SockOpts, + ssl = SslOpts, + emulated = EmOpts, + inet_user = Options} = Config) -> + IP = proplists:get_value(ip, SockOpts, {0,0,0,0}), + case dtls_listener_sup:lookup_listener(IP, Port) of + undefined -> + start_new_listener(IP, Port, Config); + {ok, Listener} -> + dtls_packet_demux:new_owner(Listener), + dtls_packet_demux:set_all_opts( + Listener, {Options, + emulated_socket_options(EmOpts, + #socket_options{}), + SslOpts}), + dtls_listener_sup:register_listener({self(), Listener}, + IP, Port), + {ok, create_dtls_socket(Config, Listener, Port)}; + Error -> + Error end. accept(dtls, #config{transport_info = {Transport,_,_,_,_}, @@ -91,7 +85,7 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo inet_ssl = SocketOpts}, Timeout) -> case Transport:open(0, SocketOpts ++ internal_inet_values()) of {ok, Socket} -> - ssl_connection:connect(ConnectionCb, Address, Port, {{Address, Port},Socket}, + ssl_gen_statem:connect(ConnectionCb, Address, Port, {{Address, Port},Socket}, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, self(), CbInfo, Timeout); @@ -99,8 +93,11 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo Error end. -close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port}}}}) -> - dtls_listener_sup:register_listner({undefined, Pid}, Port), +close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port0}, + inet_ssl = SockOpts}}}) -> + IP = proplists:get_value(ip, SockOpts, {0,0,0,0}), + Port = get_real_port(Pid, Port0), + dtls_listener_sup:register_listener({undefined, Pid}, IP, Port), dtls_packet_demux:close(Pid). close(_, dtls) -> @@ -110,10 +107,11 @@ close(gen_udp, {_Client, _Socket}) -> close(Transport, {_Client, Socket}) -> Transport:close(Socket). -socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) -> +socket(Pids, gen_udp = Transport, + PeerAndSock = {{_Host, _Port}, _Socket}, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility - fd = {Transport, Socket, ConnectionCb}}; + fd = {Transport, PeerAndSock, ConnectionCb}}; socket(Pids, Transport, Socket, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility @@ -178,14 +176,19 @@ getstat(gen_udp, Pid, Options) when is_pid(Pid) -> dtls_packet_demux:getstat(Pid, Options); getstat(gen_udp, {_,{_, Socket}}, Options) -> inet:getstat(Socket, Options); +getstat(gen_udp, {_, Socket}, Options) -> + inet:getstat(Socket, Options); getstat(gen_udp, Socket, Options) -> inet:getstat(Socket, Options); getstat(Transport, Socket, Options) -> Transport:getstat(Socket, Options). + peername(_, undefined) -> {error, enotconn}; peername(gen_udp, {_, {Client, _Socket}}) -> {ok, Client}; +peername(gen_udp, {Client, _Socket}) -> + {ok, Client}; peername(Transport, Socket) -> Transport:peername(Socket). sockname(gen_udp, {_, {_,Socket}}) -> @@ -269,3 +272,59 @@ validate_inet_option(active, Value) throw({error, {options, {active,Value}}}); validate_inet_option(_, _) -> ok. + +get_real_port(Listener, Port0) when is_pid(Listener) andalso + is_integer(Port0) -> + case Port0 of + 0 -> + {ok, {_, NewPort}} = dtls_packet_demux:sockname(Listener), + NewPort; + _ -> + Port0 + end. + +start_new_listener(IP, Port0, + #config{transport_info = {TransportModule, _,_,_,_}, + inet_user = Options} = Config) -> + InetOptions = Options ++ internal_inet_values(), + case TransportModule:open(Port0, InetOptions) of + {ok, Socket} -> + Port = case Port0 of + 0 -> + {ok, P} = inet:port(Socket), + P; + _ -> + Port0 + end, + start_dtls_packet_demux(Config, IP, Port, Socket); + {error, eaddrinuse} -> + {error, already_listening}; + Error -> + Error + end. + +start_dtls_packet_demux(#config{ + transport_info = + {TransportModule, _,_,_,_} = TransportInfo, + emulated = EmOpts0, + ssl = SslOpts} = Config, IP, Port, Socket) -> + EmOpts = emulated_socket_options(EmOpts0, #socket_options{}), + case dtls_listener_sup:start_child([Port, TransportInfo, EmOpts, + SslOpts, Socket]) of + {ok, Multiplexer} -> + ok = TransportModule:controlling_process(Socket, Multiplexer), + dtls_listener_sup:register_listener({self(), Multiplexer}, + IP, Port), + DTLSSocket = create_dtls_socket(Config, Multiplexer, Port), + {ok, DTLSSocket}; + Error -> + Error + end. + +create_dtls_socket(#config{emulated = EmOpts} = Config, + Listener, Port) -> + Socket = #sslsocket{ + pid = {dtls, Config#config{dtls_handler = {Listener, Port}}}}, + check_active_n(EmOpts, Socket), + Socket. + diff --git a/lib/ssl/src/dtls_sup.erl b/lib/ssl/src/dtls_sup.erl index 2e72c10ba0..acc4415a9f 100644 --- a/lib/ssl/src/dtls_sup.erl +++ b/lib/ssl/src/dtls_sup.erl @@ -44,17 +44,25 @@ start_link() -> %%%========================================================================= init([]) -> - DTLSConnetionManager = dtls_connection_manager_child_spec(), - DTLSListeners = dtls_listeners_spec(), + DTLSConnectionManager = dtls_connection_manager_child_spec(), + DTLSServers = dtls_server_spec(), - {ok, {{one_for_one, 10, 3600}, [DTLSConnetionManager, - DTLSListeners + {ok, {{one_for_one, 10, 3600}, [DTLSConnectionManager, + DTLSServers ]}}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +dtls_server_spec() -> + Name = dtls_servers, + StartFunc = {dtls_server_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [dtls_server_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. dtls_connection_manager_child_spec() -> Name = dtls_connection, @@ -66,11 +74,3 @@ dtls_connection_manager_child_spec() -> Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -dtls_listeners_spec() -> - Name = dtls_listener, - StartFunc = {dtls_listener_sup, start_link, []}, - Restart = permanent, - Shutdown = 4000, - Modules = [], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl index 96ce4d493a..5ca0cd6904 100644 --- a/lib/ssl/src/inet6_tls_dist.erl +++ b/lib/ssl/src/inet6_tls_dist.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2015-2017. All Rights Reserved. +%% Copyright Ericsson AB 2015-2020. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ -module(inet6_tls_dist). -export([childspecs/0]). --export([listen/1, accept/1, accept_connection/5, - setup/5, close/1, select/1]). +-export([listen/2, accept/1, accept_connection/5, + setup/5, close/1, select/1, address/0]). childspecs() -> inet_tls_dist:childspecs(). @@ -31,8 +31,11 @@ childspecs() -> select(Node) -> inet_tls_dist:gen_select(inet6_tcp, Node). -listen(Name) -> - inet_tls_dist:gen_listen(inet6_tcp, Name). +address() -> + inet_tls_dist:gen_address(inet6_tcp). + +listen(Name, Host) -> + inet_tls_dist:gen_listen(inet6_tcp, Name, Host). accept(Listen) -> inet_tls_dist:gen_accept(inet6_tcp, Listen). diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index 8d9b92361b..eaa481f119 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2019. All Rights Reserved. +%% Copyright Ericsson AB 2011-2020. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -22,12 +22,12 @@ -module(inet_tls_dist). -export([childspecs/0]). --export([listen/1, accept/1, accept_connection/5, - setup/5, close/1, select/1, is_node_name/1]). +-export([listen/2, accept/1, accept_connection/5, + setup/5, close/1, select/1, address/0, is_node_name/1]). %% Generalized dist API --export([gen_listen/2, gen_accept/2, gen_accept_connection/6, - gen_setup/6, gen_close/2, gen_select/2]). +-export([gen_listen/3, gen_accept/2, gen_accept_connection/6, + gen_setup/6, gen_close/2, gen_select/2, gen_address/1]). -export([nodelay/0]). @@ -63,6 +63,14 @@ gen_select(Driver, Node) -> false end. +%% ------------------------------------------------------------ +%% Get the address family that this distribution uses +%% ------------------------------------------------------------ +address() -> + gen_address(inet_tcp). +gen_address(Driver) -> + inet_tcp_dist:gen_address(Driver). + %% ------------------------------------------------------------------------- is_node_name(Node) -> @@ -193,11 +201,11 @@ split_stat([], R, W, P) -> %% ------------------------------------------------------------------------- -listen(Name) -> - gen_listen(inet_tcp, Name). +listen(Name, Host) -> + gen_listen(inet_tcp, Name, Host). -gen_listen(Driver, Name) -> - case inet_tcp_dist:gen_listen(Driver, Name) of +gen_listen(Driver, Name, Host) -> + case inet_tcp_dist:gen_listen(Driver, Name, Host) of {ok, {Socket, Address, Creation}} -> inet:setopts(Socket, [{packet, 4}, {nodelay, true}]), {ok, {Socket, Address#net_address{protocol=tls}, Creation}}; @@ -758,8 +766,8 @@ nodelay() -> get_ssl_options(Type) -> try ets:lookup(ssl_dist_opts, Type) of - [{Type, Opts}] -> - [{erl_dist, true} | Opts]; + [{Type, Opts0}] -> + [{erl_dist, true} | dist_defaults(Opts0)]; _ -> get_ssl_dist_arguments(Type) catch @@ -770,11 +778,18 @@ get_ssl_options(Type) -> get_ssl_dist_arguments(Type) -> case init:get_argument(ssl_dist_opt) of {ok, Args} -> - [{erl_dist, true} | ssl_options(Type, lists:append(Args))]; + [{erl_dist, true} | dist_defaults(ssl_options(Type, lists:append(Args)))]; _ -> [{erl_dist, true}] end. +dist_defaults(Opts) -> + case proplists:get_value(versions, Opts, undefined) of + undefined -> + [{versions, ['tlsv1.2']} | Opts]; + _ -> + Opts + end. ssl_options(_Type, []) -> []; diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 90c96b2be1..78bbcc8c04 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -11,8 +11,8 @@ tls_record_1_3, tls_socket, tls_v1, - ssl_v3, tls_connection_sup, + tls_gen_connection, tls_sender, tls_server_sup, tls_server_session_ticket_sup, @@ -26,15 +26,19 @@ dtls_socket, dtls_v1, dtls_connection_sup, + dtls_gen_connection, dtls_packet_demux, dtls_listener_sup, dtls_sup, + dtls_server_sup, + dtls_server_session_cache_sup, %% API ssl, %% Main API ssl_session_cache_api, %% Both TLS/SSL and DTLS + tls_dtls_connection, ssl_config, - ssl_connection, + ssl_gen_statem, ssl_handshake, ssl_record, ssl_cipher, @@ -50,9 +54,15 @@ ssl_dist_sup, ssl_dist_connection_sup, ssl_dist_admin_sup, + tls_dist_sup, + tls_dist_server_sup, %% SSL/TLS session and cert handling ssl_session, - ssl_session_cache, + ssl_client_session_cache_db, + ssl_server_session_cache, + ssl_server_session_cache_db, + ssl_server_session_cache_sup, + ssl_upgrade_server_session_cache_sup, ssl_manager, ssl_pem_cache, ssl_pkix_db, @@ -74,5 +84,5 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-3.5","public_key-1.7.2","kernel-6.0", + {runtime_dependencies, ["stdlib-3.12","public_key-1.8","kernel-6.0", "erts-10.0","crypto-4.2", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index ae4d60b6ed..b0faa738d3 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,6 +1,7 @@ %% -*- erlang -*- {"%VSN%", [ + {<<"10\\..*">>, [{restart_application, ssl}]}, {<<"9\\..*">>, [{restart_application, ssl}]}, {<<"8\\..*">>, [{restart_application, ssl}]}, {<<"7\\..*">>, [{restart_application, ssl}]}, @@ -10,6 +11,7 @@ {<<"3\\..*">>, [{restart_application, ssl}]} ], [ + {<<"10\\..*">>, [{restart_application, ssl}]}, {<<"9\\..*">>, [{restart_application, ssl}]}, {<<"8\\..*">>, [{restart_application, ssl}]}, {<<"7\\..*">>, [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index ca6b65e8db..f4f8f7cc9d 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -20,7 +20,8 @@ %% -%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl +%%% Purpose : Main API module for the SSL application that implements TLS and DTLS +%%% SSL is a legacy name. -module(ssl). @@ -28,12 +29,16 @@ -include("ssl_internal.hrl"). -include("ssl_api.hrl"). --include("ssl_internal.hrl"). -include("ssl_record.hrl"). -include("ssl_cipher.hrl"). -include("ssl_handshake.hrl"). -include("ssl_srp.hrl"). +%% Needed to make documentation rendering happy +-ifndef(VSN). +-define(VSN,"unknown"). +-endif. + %% Application handling -export([start/0, start/1, @@ -100,9 +105,15 @@ suite_to_openssl_str/1, str_to_suite/1]). --deprecated({ssl_accept, 1, eventually}). --deprecated({ssl_accept, 2, eventually}). --deprecated({ssl_accept, 3, eventually}). +-deprecated({ssl_accept, '_', "use ssl_handshake/1,2,3 instead"}). + +-deprecated({cipher_suites, 0, "use cipher_suites/2,3 instead"}). +-deprecated({cipher_suites, 1, "use cipher_suites/2,3 instead"}). + +-removed([{negotiated_next_protocol,1, + "use ssl:negotiated_protocol/1 instead"}]). +-removed([{connection_info,1, + "use ssl:connection_information/[1,2] instead"}]). -export_type([socket/0, sslsocket/0, @@ -153,7 +164,7 @@ -type protocol_version() :: tls_version() | dtls_version(). % exported -type tls_version() :: 'tlsv1.2' | 'tlsv1.3' | tls_legacy_version(). -type dtls_version() :: 'dtlsv1.2' | dtls_legacy_version(). --type tls_legacy_version() :: tlsv1 | 'tlsv1.1' | sslv3. +-type tls_legacy_version() :: tlsv1 | 'tlsv1.1' . -type dtls_legacy_version() :: 'dtlsv1'. -type verify_type() :: verify_none | verify_peer. -type cipher() :: aes_128_cbc | @@ -290,7 +301,7 @@ %% ------------------------------------------------------------------------------------------------------- -type common_option() :: {protocol, protocol()} | {handshake, handshake_completion()} | - {cert, cert()} | + {cert, cert() | [cert()]} | {certfile, cert_pem()} | {key, key()} | {keyfile, key_pem()} | @@ -300,6 +311,7 @@ {signature_algs_cert, signature_schemes()} | {supported_groups, supported_groups()} | {secure_renegotiate, secure_renegotiation()} | + {keep_secrets, keep_secrets()} | {depth, allowed_cert_chain_length()} | {verify_fun, custom_verify()} | {crl_check, crl_check()} | @@ -315,7 +327,8 @@ {beast_mitigation, beast_mitigation()} | {ssl_imp, ssl_imp()} | {session_tickets, session_tickets()} | - {key_update_at, key_update_at()}. + {key_update_at, key_update_at()} | + {middlebox_comp_mode, middlebox_comp_mode()}. -type protocol() :: tls | dtls. -type handshake_completion() :: hello | full. @@ -335,12 +348,15 @@ -type cipher_filters() :: list({key_exchange | cipher | mac | prf, algo_filter()}). % exported -type algo_filter() :: fun((kex_algo()|cipher()|hash()|aead|default_prf) -> true | false). +-type keep_secrets() :: boolean(). -type secure_renegotiation() :: boolean(). -type allowed_cert_chain_length() :: integer(). -type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: any()}. -type crl_check() :: boolean() | peer | best_effort. --type crl_cache_opts() :: [any()]. +-type crl_cache_opts() :: {Module :: atom(), + {DbHandle :: internal | term(), + Args :: list()}}. -type handshake_size() :: integer(). -type hibernate_after() :: timeout(). -type root_fun() :: fun(). @@ -354,7 +370,7 @@ -type srp_identity() :: {Username :: string(), Password :: string()}. -type psk_identity() :: string(). -type log_alert() :: boolean(). --type logging_level() :: logger:level(). +-type logging_level() :: logger:level() | none | all. -type client_session_tickets() :: disabled | manual | auto. -type server_session_tickets() :: disabled | stateful | stateless. -type session_tickets() :: client_session_tickets() | server_session_tickets(). @@ -367,6 +383,9 @@ bloom_filter_hash_functions(), %% k - number of hash functions bloom_filter_bits()}. %% m - number of bits in bit vector -type use_ticket() :: [binary()]. +-type middlebox_comp_mode() :: boolean(). +-type client_early_data() :: binary(). +-type server_early_data() :: disabled | enabled. %% ------------------------------------------------------------------------------------------------------- @@ -380,14 +399,19 @@ {psk_identity, client_psk_identity()} | {srp_identity, client_srp_identity()} | {server_name_indication, sni()} | + {max_fragment_length, max_fragment_length()} | {customize_hostname_check, customize_hostname_check()} | {signature_algs, client_signature_algs()} | {fallback, fallback()} | {session_tickets, client_session_tickets()} | - {use_ticket, use_ticket()}. + {use_ticket, use_ticket()} | + {early_data, client_early_data()}. + %% {ocsp_stapling, ocsp_stapling()} | + %% {ocsp_responder_certs, ocsp_responder_certs()} | + %% {ocsp_nonce, ocsp_nonce()}. -type client_verify_type() :: verify_type(). --type client_reuse_session() :: session_id(). +-type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}. -type client_reuse_sessions() :: boolean() | save. -type client_cacerts() :: [public_key:der_encoded()]. -type client_cafile() :: file:filename(). @@ -402,9 +426,13 @@ -type client_srp_identity() :: srp_identity(). -type customize_hostname_check() :: list(). -type sni() :: HostName :: hostname() | disable. +-type max_fragment_length() :: undefined | 512 | 1024 | 2048 | 4096. -type client_signature_algs() :: signature_algs(). -type fallback() :: boolean(). -type ssl_imp() :: new | old. +%% -type ocsp_stapling() :: boolean(). +%% -type ocsp_responder_certs() :: [public_key:der_encoded()]. +%% -type ocsp_nonce() :: boolean(). %% ------------------------------------------------------------------------------------------------------- @@ -427,7 +455,9 @@ {client_renegotiation, client_renegotiation()}| {signature_algs, server_signature_algs()} | {session_tickets, server_session_tickets()} | - {anti_replay, anti_replay()}. + {anti_replay, anti_replay()} | + {cookie, cookie()} | + {early_data, server_early_data()}. -type server_cacerts() :: [public_key:der_encoded()]. -type server_cafile() :: file:filename(). @@ -446,6 +476,7 @@ -type honor_cipher_order() :: boolean(). -type honor_ecc_order() :: boolean(). -type client_renegotiation() :: boolean(). +-type cookie() :: boolean(). %% ------------------------------------------------------------------------------------------------------- -type prf_random() :: client_random | server_random. % exported -type protocol_extensions() :: #{renegotiation_info => binary(), @@ -453,10 +484,38 @@ alpn => app_level_protocol(), srp => binary(), next_protocol => app_level_protocol(), + max_frag_enum => 1..4, ec_point_formats => [0..2], elliptic_curves => [public_key:oid()], sni => hostname()}. % exported %% ------------------------------------------------------------------------------------------------------- +-type connection_info() :: [common_info() | curve_info() | ssl_options_info() | security_info()]. +-type common_info() :: {protocol, protocol_version()} | + {session_id, session_id()} | + {session_resumption, boolean()} | + {selected_cipher_suite, erl_cipher_suite()} | + {sni_hostname, term()} | + {srp_username, term()}. +-type curve_info() :: {ecc, {named_curve, term()}}. +-type ssl_options_info() :: tls_option(). +-type security_info() :: {client_random, binary()} | + {server_random, binary()} | + {master_secret, binary()}. +-type connection_info_items() :: [connection_info_item()]. +-type connection_info_item() :: protocol | + session_id | + session_resumption | + selected_cipher_suite | + sni_hostname | + srp_username | + ecc | + client_random | + server_random | + master_secret | + keylog | + tls_options_name(). +-type tls_options_name() :: atom(). +%% ------------------------------------------------------------------------------------------------------- %%%-------------------------------------------------------------------- %%% API @@ -499,7 +558,7 @@ stop() -> TCPSocket :: socket(), TLSOptions :: [tls_client_option()]. -connect(Socket, SslOptions) when is_port(Socket) -> +connect(Socket, SslOptions) -> connect(Socket, SslOptions, infinity). -spec connect(TCPSocket, TLSOptions, Timeout) -> @@ -516,24 +575,21 @@ connect(Socket, SslOptions) when is_port(Socket) -> Port :: inet:port_number(), TLSOptions :: [tls_client_option()]. -connect(Socket, SslOptions0, Timeout) when is_port(Socket), +connect(Socket, SslOptions0, Timeout) when is_list(SslOptions0) andalso (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + CbInfo = handle_option_cb_info(SslOptions0, tls), - Transport = element(1, CbInfo), - EmulatedOptions = tls_socket:emulated_options(), - {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), - try handle_options(SslOptions0 ++ SocketValues, client) of - {ok, Config} -> - tls_socket:upgrade(Socket, Config, Timeout) + try handle_options(Transport, Socket, SslOptions0, client, undefined) of + {ok, Config} -> + tls_socket:upgrade(Socket, Config, Timeout) catch - _:{error, Reason} -> + _:{error, Reason} -> {error, Reason} - end; + end; connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). - -spec connect(Host, Port, TLSOptions, Timeout) -> {ok, sslsocket()} | {ok, sslsocket(),Ext :: protocol_extensions()} | @@ -548,9 +604,9 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout try {ok, Config} = handle_options(Options, client, Host), case Config#config.connection_cb of - tls_connection -> + tls_gen_connection -> tls_socket:connect(Host,Port,Config,Timeout); - dtls_connection -> + dtls_gen_connection -> dtls_socket:connect(Host,Port,Config,Timeout) end catch @@ -599,9 +655,9 @@ transport_accept(#sslsocket{pid = {ListenSocket, #config{connection_cb = ConnectionCb} = Config}}, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> case ConnectionCb of - tls_connection -> + tls_gen_connection -> tls_socket:accept(ListenSocket, Config, Timeout); - dtls_connection -> + dtls_gen_connection -> dtls_socket:accept(ListenSocket, Config, Timeout) end. @@ -680,7 +736,7 @@ handshake(ListenSocket) -> handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - ssl_connection:handshake(Socket, Timeout); + ssl_gen_statem:handshake(Socket, Timeout); %% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to %% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns @@ -688,9 +744,8 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim %% %% If Socket is an sslsocket(): provides extra SSL/TLS/DTLS options to those %% specified in ssl:listen/2 and then performs the SSL/TLS/DTLS handshake. -handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> +handshake(ListenSocket, SslOptions) -> handshake(ListenSocket, SslOptions, infinity). - -spec handshake(Socket, Options, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | @@ -710,7 +765,7 @@ handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when try Tracker = proplists:get_value(option_tracker, Trackers), {ok, EmOpts, _} = tls_socket:get_all_opts(Tracker), - ssl_connection:handshake(Socket, {SslOpts, + ssl_gen_statem:handshake(Socket, {SslOpts, tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout) catch Error = {error, _Reason} -> Error @@ -719,31 +774,28 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout) (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> try {ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid), - ssl_connection:handshake(Socket, {SslOpts, + ssl_gen_statem:handshake(Socket, {SslOpts, tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout) catch Error = {error, _Reason} -> Error end; -handshake(Socket, SslOptions, Timeout) when is_port(Socket), - (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> +handshake(Socket, SslOptions, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> CbInfo = handle_option_cb_info(SslOptions, tls), - Transport = element(1, CbInfo), - EmulatedOptions = tls_socket:emulated_options(), - {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), ConnetionCb = connection_cb(SslOptions), - try handle_options(SslOptions ++ SocketValues, server) of - {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} -> - ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()), - {ok, Port} = tls_socket:port(Transport, Socket), - ssl_connection:handshake(ConnetionCb, Port, Socket, + try handle_options(Transport, Socket, SslOptions, server, undefined) of + {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} -> + ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()), + {ok, Port} = tls_socket:port(Transport, Socket), + {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts), + ssl_gen_statem:handshake(ConnetionCb, Port, Socket, {SslOpts, - tls_socket:emulated_socket_options(EmOpts, #socket_options{}), undefined}, + tls_socket:emulated_socket_options(EmOpts, #socket_options{}), + [{session_id_tracker, SessionIdHandle}]}, self(), CbInfo, Timeout) catch - Error = {error, _Reason} -> Error - end. - + Error = {error, _Reason} -> Error + end. %%-------------------------------------------------------------------- -spec handshake_continue(HsSocket, Options) -> @@ -771,14 +823,14 @@ handshake_continue(Socket, SSLOptions) -> %% Description: Continues the handshke possible with newly supplied options. %%-------------------------------------------------------------------- handshake_continue(Socket, SSLOptions, Timeout) -> - ssl_connection:handshake_continue(Socket, SSLOptions, Timeout). + ssl_gen_statem:handshake_continue(Socket, SSLOptions, Timeout). %%-------------------------------------------------------------------- -spec handshake_cancel(#sslsocket{}) -> any(). %% %% Description: Cancels the handshakes sending a close alert. %%-------------------------------------------------------------------- handshake_cancel(Socket) -> - ssl_connection:handshake_cancel(Socket). + ssl_gen_statem:handshake_cancel(Socket). %%-------------------------------------------------------------------- -spec close(SslSocket) -> ok | {error, Reason} when @@ -788,7 +840,7 @@ handshake_cancel(Socket) -> %% Description: Close an ssl connection %%-------------------------------------------------------------------- close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT}); + ssl_gen_statem:close(Pid, {close, ?DEFAULT_TIMEOUT}); close(#sslsocket{pid = {dtls, #config{dtls_handler = {_, _}}}} = DTLSListen) -> dtls_socket:close(DTLSListen); close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}) -> @@ -806,7 +858,7 @@ close(#sslsocket{pid = [TLSPid|_]}, {Pid, Timeout} = DownGrade) when is_pid(TLSPid), is_pid(Pid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - case ssl_connection:close(TLSPid, {close, DownGrade}) of + case ssl_gen_statem:close(TLSPid, {close, DownGrade}) of ok -> %% In normal close {error, closed} is regarded as ok, as it is not interesting which side %% that got to do the actual close. But in the downgrade case only {ok, Port} is a sucess. {error, closed}; @@ -815,7 +867,7 @@ close(#sslsocket{pid = [TLSPid|_]}, end; close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - ssl_connection:close(TLSPid, {close, Timeout}); + ssl_gen_statem:close(TLSPid, {close, Timeout}); close(#sslsocket{pid = {dtls = ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) -> dtls_socket:close(Transport, ListenSocket); close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) -> @@ -829,7 +881,7 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_} %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) -> - ssl_connection:send(Pid, Data); + ssl_gen_statem:send(Pid, Data); send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) -> tls_sender:send_data(Pid, erlang:iolist_to_iovec(Data)); send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) -> @@ -862,7 +914,7 @@ recv(Socket, Length) -> recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> - ssl_connection:recv(Pid, Length, Timeout); + ssl_gen_statem:recv(Pid, Length, Timeout); recv(#sslsocket{pid = {dtls,_}}, _, _) -> {error,enotconn}; recv(#sslsocket{pid = {Listen, @@ -880,7 +932,7 @@ recv(#sslsocket{pid = {Listen, %% or once. %%-------------------------------------------------------------------- controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> - ssl_connection:new_user(Pid, NewOwner); + ssl_gen_statem:new_user(Pid, NewOwner); controlling_process(#sslsocket{pid = {dtls, _}}, NewOwner) when is_pid(NewOwner) -> ok; %% Meaningless but let it be allowed to conform with TLS @@ -895,14 +947,12 @@ controlling_process(#sslsocket{pid = {Listen, %%-------------------------------------------------------------------- -spec connection_information(SslSocket) -> {ok, Result} | {error, reason()} when SslSocket :: sslsocket(), - Result :: [{OptionName, OptionValue}], - OptionName :: atom(), - OptionValue :: any(). + Result :: connection_info(). %% %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - case ssl_connection:connection_information(Pid, false) of + case ssl_gen_statem:connection_information(Pid, false) of {ok, Info} -> {ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]}; Error -> @@ -916,15 +966,13 @@ connection_information(#sslsocket{pid = {dtls,_}}) -> %%-------------------------------------------------------------------- -spec connection_information(SslSocket, Items) -> {ok, Result} | {error, reason()} when SslSocket :: sslsocket(), - Items :: [OptionName], - Result :: [{OptionName, OptionValue}], - OptionName :: atom(), - OptionValue :: any(). + Items :: connection_info_items(), + Result :: connection_info(). %% %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) -> - case ssl_connection:connection_information(Pid, include_security_info(Items)) of + case ssl_gen_statem:connection_information(Pid, include_security_info(Items)) of {ok, Info} -> {ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items), Value =/= undefined]}; @@ -960,7 +1008,7 @@ peername(#sslsocket{pid = {dtls,_}}) -> %% Description: Returns the peercert. %%-------------------------------------------------------------------- peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - case ssl_connection:peer_certificate(Pid) of + case ssl_gen_statem:peer_certificate(Pid) of {ok, undefined} -> {error, no_peercert}; Result -> @@ -981,7 +1029,7 @@ peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> %% protocol has been negotiated will return {error, protocol_not_negotiated} %%-------------------------------------------------------------------- negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - ssl_connection:negotiated_protocol(Pid). + ssl_gen_statem:negotiated_protocol(Pid). %%-------------------------------------------------------------------- -spec cipher_suites() -> [old_cipher_suite()] | [string()]. @@ -1005,47 +1053,46 @@ cipher_suites(all) -> [ssl_cipher_format:suite_legacy(Suite) || Suite <- available_suites(all)]. %%-------------------------------------------------------------------- --spec cipher_suites(Supported, Version) -> ciphers() when - Supported :: default | all | anonymous, +-spec cipher_suites(Description, Version) -> ciphers() when + Description :: default | all | exclusive | anonymous, Version :: protocol_version(). %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- -cipher_suites(Base, Version) when Version == 'tlsv1.3'; +cipher_suites(Description, Version) when Version == 'tlsv1.3'; Version == 'tlsv1.2'; Version == 'tlsv1.1'; - Version == tlsv1; - Version == sslv3 -> - cipher_suites(Base, tls_record:protocol_version(Version)); -cipher_suites(Base, Version) when Version == 'dtlsv1.2'; + Version == tlsv1 -> + cipher_suites(Description, tls_record:protocol_version(Version)); +cipher_suites(Description, Version) when Version == 'dtlsv1.2'; Version == 'dtlsv1'-> - cipher_suites(Base, dtls_record:protocol_version(Version)); -cipher_suites(Base, Version) -> - [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Base, Version)]. + cipher_suites(Description, dtls_record:protocol_version(Version)); +cipher_suites(Description, Version) -> + [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Description, Version)]. %%-------------------------------------------------------------------- --spec cipher_suites(Supported, Version, rfc | openssl) -> [string()] when - Supported :: default | all | anonymous, +-spec cipher_suites(Description, Version, rfc | openssl) -> [string()] when + Description :: default | all | exclusive | anonymous, Version :: protocol_version(). %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- -cipher_suites(Base, Version, StringType) when Version == 'tlsv1.2'; - Version == 'tlsv1.1'; - Version == tlsv1; - Version == sslv3 -> - cipher_suites(Base, tls_record:protocol_version(Version), StringType); -cipher_suites(Base, Version, StringType) when Version == 'dtlsv1.2'; +cipher_suites(Description, Version, StringType) when Version == 'tlsv1.3'; + Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1 -> + cipher_suites(Description, tls_record:protocol_version(Version), StringType); +cipher_suites(Description, Version, StringType) when Version == 'dtlsv1.2'; Version == 'dtlsv1'-> - cipher_suites(Base, dtls_record:protocol_version(Version), StringType); -cipher_suites(Base, Version, rfc) -> - [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite)) - || Suite <- supported_suites(Base, Version)]; -cipher_suites(Base, Version, openssl) -> - [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite)) - || Suite <- supported_suites(Base, Version)]. + cipher_suites(Description, dtls_record:protocol_version(Version), StringType); +cipher_suites(Description, Version, rfc) -> + [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite)) + || Suite <- supported_suites(Description, Version)]; +cipher_suites(Description, Version, openssl) -> + [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite)) + || Suite <- supported_suites(Description, Version)]. %%-------------------------------------------------------------------- -spec filter_cipher_suites(Suites, Filters) -> Ciphers when @@ -1076,7 +1123,7 @@ filter_cipher_suites(Suites, Filters0) -> %% Description: Make <Preferred> suites become the most prefered %% suites that is put them at the head of the cipher suite list %% and remove them from <Suites> if present. <Preferred> may be a -%% list of cipher suits or a list of filters in which case the +%% list of cipher suites or a list of filters in which case the %% filters are use on Suites to extract the the preferred %% cipher list. %% -------------------------------------------------------------------- @@ -1121,8 +1168,6 @@ eccs() -> %% Description: returns the curves supported for a given version of %% ssl/tls. %%-------------------------------------------------------------------- -eccs(sslv3) -> - []; eccs('dtlsv1') -> eccs('tlsv1.1'); eccs('dtlsv1.2') -> @@ -1161,7 +1206,7 @@ groups(default) -> %% Description: Gets options %%-------------------------------------------------------------------- getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> - ssl_connection:get_opts(Pid, OptionTags); + ssl_gen_statem:get_opts(Pid, OptionTags); getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) -> try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of {ok, _} = Result -> @@ -1199,11 +1244,11 @@ setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Opt Options -> case proplists:get_value(packet, Options, undefined) of undefined -> - ssl_connection:set_opts(Pid, Options); + ssl_gen_statem:set_opts(Pid, Options); PacketOpt -> case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of ok -> - ssl_connection:set_opts(Pid, Options); + ssl_gen_statem:set_opts(Pid, Options); Error -> Error end @@ -1216,7 +1261,7 @@ setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0) try proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Options0) of Options -> - ssl_connection:set_opts(Pid, Options) + ssl_gen_statem:set_opts(Pid, Options) catch _:_ -> {error, {options, {not_a_proplist, Options0}}} @@ -1292,7 +1337,7 @@ shutdown(#sslsocket{pid = {Listen, #config{transport_info = Info}}}, shutdown(#sslsocket{pid = {dtls,_}},_) -> {error, enotconn}; shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) -> - ssl_connection:shutdown(Pid, How). + ssl_gen_statem:shutdown(Pid, How). %%-------------------------------------------------------------------- -spec sockname(SslSocket) -> @@ -1315,23 +1360,37 @@ sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket,_,_}}) when is_pid(P %%--------------------------------------------------------------- -spec versions() -> [VersionInfo] when VersionInfo :: {ssl_app, string()} | - {supported | available, [tls_version()]} | - {supported_dtls | available_dtls, [dtls_version()]}. + {supported | available | implemented, [tls_version()]} | + {supported_dtls | available_dtls | implemented_dtls, [dtls_version()]}. %% %% Description: Returns a list of relevant versions. %%-------------------------------------------------------------------- versions() -> - TLSVsns = tls_record:supported_protocol_versions(), - DTLSVsns = dtls_record:supported_protocol_versions(), - SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- TLSVsns], - SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns], - AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS, - AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS, - [{ssl_app, "9.2"}, {supported, SupportedTLSVsns}, + ConfTLSVsns = tls_record:supported_protocol_versions(), + ConfDTLSVsns = dtls_record:supported_protocol_versions(), + ImplementedTLSVsns = ?ALL_AVAILABLE_VERSIONS, + ImplementedDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS, + + TLSCryptoSupported = fun(Vsn) -> + tls_record:sufficient_crypto_support(Vsn) + end, + DTLSCryptoSupported = fun(Vsn) -> + tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(Vsn)) + end, + SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- ConfTLSVsns, TLSCryptoSupported(Vsn)], + SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- ConfDTLSVsns, DTLSCryptoSupported(Vsn)], + + AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version(Vsn))], + AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version(Vsn))], + + [{ssl_app, ?VSN}, + {supported, SupportedTLSVsns}, {supported_dtls, SupportedDTLSVsns}, {available, AvailableTLSVsns}, - {available_dtls, AvailableDTLSVsns}]. - + {available_dtls, AvailableDTLSVsns}, + {implemented, ImplementedTLSVsns}, + {implemented_dtls, ImplementedDTLSVsns} + ]. %%--------------------------------------------------------------- -spec renegotiate(SslSocket) -> ok | {error, reason()} when @@ -1343,12 +1402,12 @@ renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid), is_pid(Sender) -> case tls_sender:renegotiate(Sender) of {ok, Write} -> - tls_connection:renegotiation(Pid, Write); + tls_dtls_connection:renegotiation(Pid, Write); Error -> Error end; renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) -> - ssl_connection:renegotiation(Pid); + tls_dtls_connection:renegotiation(Pid); renegotiate(#sslsocket{pid = {dtls,_}}) -> {error, enotconn}; renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> @@ -1372,7 +1431,7 @@ update_keys(#sslsocket{pid = [Pid, Sender |_]}, Type0) when is_pid(Pid) andalso read_write -> update_requested end, - tls_connection:send_key_update(Sender, Type); + tls_connection_1_3:send_key_update(Sender, Type); update_keys(_, Type) -> {error, {illegal_parameter, Type}}. @@ -1389,7 +1448,7 @@ update_keys(_, Type) -> %%-------------------------------------------------------------------- prf(#sslsocket{pid = [Pid|_]}, Secret, Label, Seed, WantedLength) when is_pid(Pid) -> - ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength); + tls_dtls_connection:prf(Pid, Secret, Label, Seed, WantedLength); prf(#sslsocket{pid = {dtls,_}}, _,_,_,_) -> {error, enotconn}; prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> @@ -1499,6 +1558,8 @@ available_suites(all) -> Version = tls_record:highest_protocol_version([]), ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)). +supported_suites(exclusive, {3,Minor}) -> + tls_v1:exclusive_suites(Minor); supported_suites(default, Version) -> ssl_cipher:suites(Version); supported_suites(all, Version) -> @@ -1506,49 +1567,41 @@ supported_suites(all, Version) -> supported_suites(anonymous, Version) -> ssl_cipher:anonymous_suites(Version). -do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_connection) -> +do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_gen_connection) -> tls_socket:listen(Transport, Port, Config); -do_listen(Port, Config, dtls_connection) -> +do_listen(Port, Config, dtls_gen_connection) -> dtls_socket:listen(Port, Config). - - -spec handle_options([any()], client | server) -> {ok, #config{}}; ([any()], ssl_options()) -> ssl_options(). handle_options(Opts, Role) -> - handle_options(Opts, Role, undefined). + handle_options(undefined, undefined, Opts, Role, undefined). +handle_options(Opts, Role, InheritedSslOpts) -> + handle_options(undefined, undefined, Opts, Role, InheritedSslOpts). %% Handle ssl options at handshake, handshake_continue -handle_options(Opts0, Role, InheritedSslOpts) when is_map(InheritedSslOpts) -> +handle_options(_, _, Opts0, Role, InheritedSslOpts) when is_map(InheritedSslOpts) -> {SslOpts, _} = expand_options(Opts0, ?RULES), process_options(SslOpts, InheritedSslOpts, #{role => Role, rules => ?RULES}); %% Handle all options in listen, connect and handshake -handle_options(Opts0, Role, Host) -> - {SslOpts0, SockOpts} = expand_options(Opts0, ?RULES), - +handle_options(Transport, Socket, Opts0, Role, Host) -> + {SslOpts0, SockOpts0} = expand_options(Opts0, ?RULES), + %% Ensure all options are evaluated at startup SslOpts1 = add_missing_options(SslOpts0, ?RULES), - SslOpts = #{protocol := Protocol, - versions := Versions} + SslOpts = #{protocol := Protocol} = process_options(SslOpts1, #{}, #{role => Role, host => Host, rules => ?RULES}), - - case Versions of - [{3, 0}] -> - reject_alpn_next_prot_options(SslOpts0); - _ -> - ok - end, - + %% Handle special options - {Sock, Emulated} = emulated_options(Protocol, SockOpts), + {Sock, Emulated} = emulated_options(Transport, Socket, Protocol, SockOpts0), ConnetionCb = connection_cb(Protocol), CbInfo = handle_option_cb_info(Opts0, Protocol), @@ -1598,7 +1651,9 @@ handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = validate_option(Option, default_value(Option, Rules)), OptionsMap#{Option => Value}; handle_option(anti_replay = Option, Value0, - #{session_tickets := SessionTickets} = OptionsMap, #{rules := Rules}) -> + #{session_tickets := SessionTickets, + versions := Versions} = OptionsMap, #{rules := Rules}) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), assert_option_dependency(Option, session_tickets, [SessionTickets], [stateless]), case SessionTickets of stateless -> @@ -1607,6 +1662,13 @@ handle_option(anti_replay = Option, Value0, _ -> OptionsMap#{Option => default_value(Option, Rules)} end; +handle_option(beast_mitigation = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(beast_mitigation = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts, verify := Verify, verify_fun := VerifyFun} = OptionsMap, _Env) @@ -1625,19 +1687,48 @@ handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts, handle_option(cacertfile = Option, Value0, OptionsMap, _Env) -> Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; -handle_option(ciphers = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := Rules}) -> - Value = handle_cipher_option(default_value(Option, Rules), HighestVersion), +handle_option(ciphers = Option, unbound, #{versions := Versions} = OptionsMap, #{rules := Rules}) -> + Value = handle_cipher_option(default_value(Option, Rules), Versions), OptionsMap#{Option => Value}; -handle_option(ciphers = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> - Value = handle_cipher_option(Value0, HighestVersion), +handle_option(ciphers = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + Value = handle_cipher_option(Value0, Versions), OptionsMap#{Option => Value}; handle_option(client_renegotiation = Option, unbound, OptionsMap, #{role := Role}) -> Value = default_option_role(server, true, Role), OptionsMap#{Option => Value}; -handle_option(client_renegotiation = Option, Value0, OptionsMap, #{role := Role}) -> +handle_option(client_renegotiation = Option, Value0, + #{versions := Versions} = OptionsMap, #{role := Role}) -> assert_role(server_only, Role, Option, Value0), + assert_option_dependency(Option, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; +handle_option(early_data = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets, + versions := Versions} = OptionsMap, + #{role := server = Role}) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), + assert_option_dependency(Option, session_tickets, [SessionTickets], + [stateful, stateless]), + Value = validate_option(Option, Value0, Role), + OptionsMap#{Option => Value}; +handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets, + use_ticket := UseTicket, + versions := Versions} = OptionsMap, + #{role := client = Role}) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), + assert_option_dependency(Option, session_tickets, [SessionTickets], + [manual, auto]), + case UseTicket of + undefined when SessionTickets =/= auto -> + throw({error, {options, dependency, {Option, use_ticket}}}); + _ -> + ok + end, + Value = validate_option(Option, Value0, Role), + OptionsMap#{Option => Value}; handle_option(eccs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) -> Value = handle_eccs_option(eccs(), HighestVersion), OptionsMap#{Option => Value}; @@ -1651,6 +1742,14 @@ handle_option(fallback = Option, Value0, OptionsMap, #{role := Role}) -> assert_role(client_only, Role, Option, Value0), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; +handle_option(cookie = Option, unbound, OptionsMap, #{role := Role}) -> + Value = default_option_role(server, true, Role), + OptionsMap#{Option => Value}; +handle_option(cookie = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), + assert_role(server_only, Role, Option, Value0), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(honor_cipher_order = Option, unbound, OptionsMap, #{role := Role}) -> Value = default_option_role(server, false, Role), OptionsMap#{Option => Value}; @@ -1675,13 +1774,50 @@ handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsM assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; +handle_option(next_protocols_advertised = Option, unbound, OptionsMap, + #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(next_protocols_advertised = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(next_protocols_advertised, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(next_protocol_selector = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = default_value(Option, Rules), OptionsMap#{Option => Value}; -handle_option(next_protocol_selector = Option, Value0, OptionsMap, _Env) -> +handle_option(next_protocol_selector = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(client_preferred_next_protocols, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = make_next_protocol_selector( validate_option(client_preferred_next_protocols, Value0)), OptionsMap#{Option => Value}; +handle_option(padding_check = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(padding_check = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(psk_identity = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(secure_renegotiate = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(secure_renegotiate= Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(secure_renegotiate, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) -> Value = case Role of @@ -1691,14 +1827,20 @@ handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) -> fun(_, _, _, _) -> true end end, OptionsMap#{Option => Value}; -handle_option(reuse_session = Option, Value0, OptionsMap, _Env) -> +handle_option(reuse_session = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(reuse_session, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; %% TODO: validate based on role handle_option(reuse_sessions = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = validate_option(Option, default_value(Option, Rules)), OptionsMap#{Option => Value}; -handle_option(reuse_sessions = Option, Value0, OptionsMap, _Env) -> +handle_option(reuse_sessions = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(reuse_sessions, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Host, @@ -1708,13 +1850,13 @@ handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Ho handle_option(server_name_indication = Option, Value0, OptionsMap, _Env) -> Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; -handle_option(session_tickets = Option, unbound, OptionsMap, #{rules := Rules}) -> - Value = validate_option(Option, default_value(Option, Rules)), +handle_option(session_tickets = Option, unbound, OptionsMap, #{role := Role, + rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules), Role), OptionsMap#{Option => Value}; handle_option(session_tickets = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) -> assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), - assert_role_value(Role, Option, Value0, [disabled, stateful, stateless], [disabled, manual, auto]), - Value = validate_option(Option, Value0), + Value = validate_option(Option, Value0, Role), OptionsMap#{Option => Value}; handle_option(signature_algs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{role := Role}) -> Value = @@ -1752,25 +1894,48 @@ handle_option(sni_fun = Option, Value0, OptionsMap, _Env) -> throw({error, {conflict_options, [sni_fun, sni_hosts]}}) end, OptionsMap#{Option => Value}; +handle_option(srp_identity = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(srp_identity = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(srp_identity, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(supported_groups = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) -> Value = handle_supported_groups_option(groups(default), HighestVersion), OptionsMap#{Option => Value}; -handle_option(supported_groups = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> +handle_option(supported_groups = Option, Value0, + #{versions := [HighestVersion|_] = Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), Value = handle_supported_groups_option(Value0, HighestVersion), OptionsMap#{Option => Value}; +handle_option(use_ticket = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(use_ticket = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(user_lookup_fun = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(user_lookup_fun = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) -> handle_verify_option(default_value(Option, Rules), OptionsMap); handle_option(verify = _Option, Value, OptionsMap, _Env) -> handle_verify_option(Value, OptionsMap); - handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, #{rules := Rules}) - when Verify =:= verify_none orelse - Verify =:= 0 -> + when Verify =:= verify_none -> OptionsMap#{Option => default_value(Option, Rules)}; handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env) - when Verify =:= verify_peer orelse - Verify =:= 1 orelse - Verify =:= 2 -> + when Verify =:= verify_peer -> OptionsMap#{Option => undefined}; handle_option(verify_fun = Option, Value0, OptionsMap, _Env) -> Value = validate_option(Option, Value0), @@ -1815,13 +1980,11 @@ maybe_map_key_internal(client_preferred_next_protocols) -> maybe_map_key_internal(K) -> K. - maybe_map_key_external(next_protocol_selector) -> client_preferred_next_protocols; maybe_map_key_external(K) -> K. - check_dependencies(K, OptionsMap, Env) -> Rules = maps:get(rules, Env), Deps = get_dependencies(K, Rules), @@ -1854,10 +2017,10 @@ dependecies_already_defined(L, OptionsMap) -> expand_options(Opts0, Rules) -> Opts1 = proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Opts0), - assert_proplist(Opts1), + Opts2 = handle_option_format(Opts1, []), %% Remove depricated ssl_imp option - Opts = proplists:delete(ssl_imp, Opts1), + Opts = proplists:delete(ssl_imp, Opts2), AllOpts = maps:keys(Rules), SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end, Opts, @@ -1866,8 +2029,9 @@ expand_options(Opts0, Rules) -> cb_info, client_preferred_next_protocols, %% next_protocol_selector log_alert]), %% obsoleted by log_level - - SslOpts = {Opts -- SockOpts, [], length(Opts -- SockOpts)}, + + SslOpts0 = Opts -- SockOpts, + SslOpts = {SslOpts0, [], length(SslOpts0)}, {SslOpts, SockOpts}. @@ -1886,7 +2050,6 @@ add_missing_options({L0, S, _C}, Rules) -> L = lists:foldl(Fun, L0, AllOpts), {L, S, length(L)}. - default_value(Key, Rules) -> {Default, _} = maps:get(Key, Rules, {undefined, []}), Default. @@ -1903,204 +2066,236 @@ assert_role(server_only, _, _, undefined) -> assert_role(Type, _, Key, _) -> throw({error, {option, Type, Key}}). - -assert_role_value(client, Option, Value, _, ClientValues) -> - case lists:member(Value, ClientValues) of - true -> - ok; - false -> - %% throw({error, {option, client, Option, Value, ClientValues}}) - throw({error, {options, role, {Option, {Value, {client, ClientValues}}}}}) - end; -assert_role_value(server, Option, Value, ServerValues, _) -> - case lists:member(Value, ServerValues) of - true -> - ok; - false -> - %% throw({error, {option, server, Option, Value, ServerValues}}) - throw({error, {options, role, {Option, {Value, {server, ServerValues}}}}}) - end. - - assert_option_dependency(Option, OptionDep, Values0, AllowedValues) -> - %% special handling for version - Values = - case OptionDep of - versions -> - lists:map(fun tls_record:protocol_version/1, Values0); - _ -> - Values0 - end, - Set1 = sets:from_list(Values), - Set2 = sets:from_list(AllowedValues), - case sets:size(sets:intersection(Set1, Set2)) > 0 of + case is_dtls_configured(Values0) of true -> + %% TODO: Check option dependency for DTLS ok; false -> - %% Message = build_error_message(Option, OptionDep, AllowedValues), - %% throw({error, {options, Message}}) - throw({error, {options, dependency, {Option, {OptionDep, AllowedValues}}}}) + %% special handling for version + Values = + case OptionDep of + versions -> + lists:map(fun tls_record:protocol_version/1, Values0); + _ -> + Values0 + end, + Set1 = sets:from_list(Values), + Set2 = sets:from_list(AllowedValues), + case sets:size(sets:intersection(Set1, Set2)) > 0 of + true -> + ok; + false -> + throw({error, {options, dependency, + {Option, {OptionDep, AllowedValues}}}}) + end end. +is_dtls_configured(Versions) -> + Fun = fun (Version) when Version =:= {254, 253} orelse + Version =:= {254, 255} -> + true; + (_) -> + false + end, + lists:any(Fun, Versions). -validate_option(versions, Versions) -> - validate_versions(Versions, Versions); -validate_option(verify, Value) - when Value == verify_none; Value == verify_peer -> +validate_option(Option, Value) -> + validate_option(Option, Value, undefined). +%% +validate_option(Opt, Value, _) + when Opt =:= alpn_advertised_protocols orelse + Opt =:= alpn_preferred_protocols, + is_list(Value) -> + validate_binary_list(Opt, Value), Value; -validate_option(verify_fun, undefined) -> +validate_option(Opt, Value, _) + when Opt =:= alpn_advertised_protocols orelse + Opt =:= alpn_preferred_protocols, + Value =:= undefined -> undefined; -%% Backwards compatibility -validate_option(verify_fun, Fun) when is_function(Fun) -> - {fun(_,{bad_cert, _} = Reason, OldFun) -> - case OldFun([Reason]) of - true -> - {valid, OldFun}; - false -> - {fail, Reason} - end; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, Fun}; -validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) -> - Value; -validate_option(partial_chain, Value) when is_function(Value) -> +validate_option(anti_replay, '10k', _) -> + %% n = 10000 + %% p = 0.030003564 (1 in 33) + %% m = 72985 (8.91KiB) + %% k = 5 + {10, 5, 72985}; +validate_option(anti_replay, '100k', _) -> + %% n = 100000 + %% p = 0.03000428 (1 in 33) + %% m = 729845 (89.09KiB) + %% k = 5 + {10, 5, 729845}; +validate_option(anti_replay, Value, _) + when (is_tuple(Value) andalso + tuple_size(Value) =:= 3) -> Value; -validate_option(fail_if_no_peer_cert, Value) when is_boolean(Value) -> +validate_option(beast_mitigation, Value, _) + when Value == one_n_minus_one orelse + Value == zero_n orelse + Value == disabled -> + Value; +%% certfile must be present in some cases otherwhise it can be set +%% to the empty string. +validate_option(cacertfile, undefined, _) -> + <<>>; +validate_option(cacertfile, Value, _) + when is_binary(Value) -> + Value; +validate_option(cacertfile, Value, _) + when is_list(Value), Value =/= ""-> + binary_filename(Value); +validate_option(cacerts, Value, _) + when Value == undefined; + is_list(Value) -> Value; -validate_option(verify_client_once, Value) when is_boolean(Value) -> +validate_option(cb_info, {V1, V2, V3, V4} = Value, _) + when is_atom(V1), + is_atom(V2), + is_atom(V3), + is_atom(V4) -> Value; -validate_option(depth, Value) when is_integer(Value), - Value >= 0, Value =< 255-> +validate_option(cb_info, {V1, V2, V3, V4, V5} = Value, _) + when is_atom(V1), + is_atom(V2), + is_atom(V3), + is_atom(V4), + is_atom(V5) -> Value; -validate_option(cert, Value) when Value == undefined; - is_binary(Value) -> +validate_option(cert, Value, _) when Value == undefined; + is_list(Value)-> Value; -validate_option(certfile, undefined = Value) -> +validate_option(cert, Value, _) when Value == undefined; + is_binary(Value)-> + [Value]; +validate_option(certfile, undefined = Value, _) -> Value; -validate_option(certfile, Value) when is_binary(Value) -> +validate_option(certfile, Value, _) + when is_binary(Value) -> Value; -validate_option(certfile, Value) when is_list(Value) -> +validate_option(certfile, Value, _) + when is_list(Value) -> binary_filename(Value); - -validate_option(key, undefined) -> +validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols}, _) + when is_list(PreferredProtocols) -> + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + {Precedence, PreferredProtocols, ?NO_PROTOCOL}; +validate_option(client_preferred_next_protocols, + {Precedence, PreferredProtocols, Default} = Value, _) + when is_list(PreferredProtocols), is_binary(Default), + byte_size(Default) > 0, byte_size(Default) < 256 -> + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + Value; +validate_option(client_preferred_next_protocols, undefined, _) -> undefined; -validate_option(key, {KeyType, Value}) when is_binary(Value), - KeyType == rsa; %% Backwards compatibility - KeyType == dsa; %% Backwards compatibility - KeyType == 'RSAPrivateKey'; - KeyType == 'DSAPrivateKey'; - KeyType == 'ECPrivateKey'; - KeyType == 'PrivateKeyInfo' -> - {KeyType, Value}; -validate_option(key, #{algorithm := _} = Value) -> +validate_option(client_renegotiation, Value, _) + when is_boolean(Value) -> Value; -validate_option(keyfile, undefined) -> - <<>>; -validate_option(keyfile, Value) when is_binary(Value) -> +validate_option(cookie, Value, _) + when is_boolean(Value) -> Value; -validate_option(keyfile, Value) when is_list(Value), Value =/= "" -> - binary_filename(Value); -validate_option(key_update_at, Value) when is_integer(Value) andalso - Value > 0 -> +validate_option(crl_cache, {Cb, {_Handle, Options}} = Value, _) + when is_atom(Cb) and is_list(Options) -> Value; -validate_option(password, Value) when is_list(Value) -> +validate_option(crl_check, Value, _) + when is_boolean(Value) -> Value; - -validate_option(cacerts, Value) when Value == undefined; - is_list(Value) -> +validate_option(crl_check, Value, _) + when (Value == best_effort) or + (Value == peer) -> Value; -%% certfile must be present in some cases otherwhise it can be set -%% to the empty string. -validate_option(cacertfile, undefined) -> - <<>>; -validate_option(cacertfile, Value) when is_binary(Value) -> +validate_option(customize_hostname_check, Value, _) + when is_list(Value) -> Value; -validate_option(cacertfile, Value) when is_list(Value), Value =/= ""-> - binary_filename(Value); -validate_option(dh, Value) when Value == undefined; - is_binary(Value) -> +validate_option(depth, Value, _) + when is_integer(Value), + Value >= 0, Value =< 255-> Value; -validate_option(dhfile, undefined = Value) -> +validate_option(dh, Value, _) + when Value == undefined; + is_binary(Value) -> Value; -validate_option(dhfile, Value) when is_binary(Value) -> +validate_option(dhfile, undefined = Value, _) -> Value; -validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> +validate_option(dhfile, Value, _) + when is_binary(Value) -> + Value; +validate_option(dhfile, Value, _) + when is_list(Value), Value =/= "" -> binary_filename(Value); -validate_option(psk_identity, undefined) -> - undefined; -validate_option(psk_identity, Identity) - when is_list(Identity), Identity =/= "", length(Identity) =< 65535 -> - binary_filename(Identity); -validate_option(user_lookup_fun, undefined) -> - undefined; -validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) -> - Value; -validate_option(srp_identity, undefined) -> - undefined; -validate_option(srp_identity, {Username, Password}) - when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 -> - {unicode:characters_to_binary(Username), - unicode:characters_to_binary(Password)}; - -validate_option(reuse_session, undefined) -> - undefined; -validate_option(reuse_session, Value) when is_function(Value) -> +validate_option(early_data, Value, server) + when Value =:= disabled orelse + Value =:= enabled -> Value; -validate_option(reuse_session, Value) when is_binary(Value) -> +validate_option(early_data = Option, Value, server) -> + throw({error, + {options, role, {Option, {Value, {server, [disabled, enabled]}}}}}); +validate_option(early_data, Value, client) + when is_binary(Value) -> Value; -validate_option(reuse_sessions, Value) when is_boolean(Value) -> +validate_option(early_data = Option, Value, client) -> + throw({error, + {options, type, {Option, {Value, not_binary}}}}); +validate_option(erl_dist, Value, _) + when is_boolean(Value) -> Value; -validate_option(reuse_sessions, save = Value) -> +validate_option(fail_if_no_peer_cert, Value, _) + when is_boolean(Value) -> Value; -validate_option(secure_renegotiate, Value) when is_boolean(Value) -> +validate_option(fallback, Value, _) + when is_boolean(Value) -> Value; -validate_option(client_renegotiation, Value) when is_boolean(Value) -> +validate_option(handshake, hello = Value, _) -> Value; -validate_option(renegotiate_at, Value) when is_integer(Value) -> - erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT); - -validate_option(hibernate_after, undefined) -> %% Backwards compatibility +validate_option(handshake, full = Value, _) -> + Value; +validate_option(hibernate_after, undefined, _) -> %% Backwards compatibility infinity; -validate_option(hibernate_after, infinity) -> +validate_option(hibernate_after, infinity, _) -> infinity; -validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 -> +validate_option(hibernate_after, Value, _) + when is_integer(Value), Value >= 0 -> Value; - -validate_option(erl_dist,Value) when is_boolean(Value) -> +validate_option(honor_cipher_order, Value, _) + when is_boolean(Value) -> Value; -validate_option(Opt, Value) when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols, - is_list(Value) -> - validate_binary_list(Opt, Value), +validate_option(honor_ecc_order, Value, _) + when is_boolean(Value) -> Value; -validate_option(Opt, Value) - when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols, - Value =:= undefined -> - undefined; -validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols}) - when is_list(PreferredProtocols) -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), - {Precedence, PreferredProtocols, ?NO_PROTOCOL}; -validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols, Default} = Value) - when is_list(PreferredProtocols), is_binary(Default), - byte_size(Default) > 0, byte_size(Default) < 256 -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), +validate_option(keep_secrets, Value, _) when is_boolean(Value) -> Value; -validate_option(client_preferred_next_protocols, undefined) -> +validate_option(key, undefined, _) -> undefined; -validate_option(log_alert, true) -> - notice; -validate_option(log_alert, false) -> - warning; -validate_option(log_level, Value) when +validate_option(key, {KeyType, Value}, _) + when is_binary(Value), + KeyType == rsa; %% Backwards compatibility + KeyType == dsa; %% Backwards compatibility + KeyType == 'RSAPrivateKey'; + KeyType == 'DSAPrivateKey'; + KeyType == 'ECPrivateKey'; + KeyType == 'PrivateKeyInfo' -> + {KeyType, Value}; +validate_option(key, #{algorithm := _} = Value, _) -> + Value; +validate_option(keyfile, undefined, _) -> + <<>>; +validate_option(keyfile, Value, _) + when is_binary(Value) -> + Value; +validate_option(keyfile, Value, _) + when is_list(Value), Value =/= "" -> + binary_filename(Value); +validate_option(key_update_at, Value, _) + when is_integer(Value) andalso + Value > 0 -> + Value; +validate_option(log_level, Value, _) when is_atom(Value) andalso - (Value =:= emergency orelse + (Value =:= none orelse + Value =:= all orelse + Value =:= emergency orelse Value =:= alert orelse Value =:= critical orelse Value =:= error orelse @@ -2109,110 +2304,178 @@ validate_option(log_level, Value) when Value =:= info orelse Value =:= debug) -> Value; -validate_option(next_protocols_advertised, Value) when is_list(Value) -> - validate_binary_list(next_protocols_advertised, Value), - Value; -validate_option(next_protocols_advertised, undefined) -> +%% RFC 6066, Section 4 +validate_option(max_fragment_length, I, _) + when I == ?MAX_FRAGMENT_LENGTH_BYTES_1; + I == ?MAX_FRAGMENT_LENGTH_BYTES_2; + I == ?MAX_FRAGMENT_LENGTH_BYTES_3; + I == ?MAX_FRAGMENT_LENGTH_BYTES_4 -> + I; +validate_option(max_fragment_length, undefined, _) -> undefined; -validate_option(server_name_indication, Value) when is_list(Value) -> - %% RFC 6066, Section 3: Currently, the only server names supported are - %% DNS hostnames - %% case inet_parse:domain(Value) of - %% false -> - %% throw({error, {options, {{Opt, Value}}}}); - %% true -> - %% Value - %% end; - %% - %% But the definition seems very diffuse, so let all strings through - %% and leave it up to public_key to decide... +validate_option(max_handshake_size, Value, _) + when is_integer(Value) andalso + Value =< ?MAX_UNIT24 -> Value; -validate_option(server_name_indication, undefined) -> - undefined; -validate_option(server_name_indication, disable) -> - disable; - -validate_option(sni_hosts, []) -> - []; -validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail]) when is_list(Hostname) -> - RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined), - case RecursiveSNIOptions of - undefined -> - [{Hostname, validate_options(SSLOptions)} | validate_option(sni_hosts, Tail)]; - _ -> - throw({error, {options, {sni_hosts, RecursiveSNIOptions}}}) - end; -validate_option(sni_fun, undefined) -> +validate_option(middlebox_comp_mode, Value, _) + when is_boolean(Value) -> + Value; +validate_option(next_protocols_advertised, Value, _) when is_list(Value) -> + validate_binary_list(next_protocols_advertised, Value), + Value; +validate_option(next_protocols_advertised, undefined, _) -> undefined; -validate_option(sni_fun, Fun) when is_function(Fun) -> - Fun; -validate_option(honor_cipher_order, Value) when is_boolean(Value) -> +validate_option(ocsp_nonce, Value, _) + when Value =:= true orelse + Value =:= false -> Value; -validate_option(honor_ecc_order, Value) when is_boolean(Value) -> +%% The OCSP responders' certificates can be given as a suggestion and +%% will be used to verify the OCSP response. +validate_option(ocsp_responder_certs, Value, _) + when is_list(Value) -> + [public_key:pkix_decode_cert(CertDer, plain) || CertDer <- Value, + is_binary(CertDer)]; +validate_option(ocsp_stapling, Value, _) + when Value =:= true orelse + Value =:= false -> Value; -validate_option(padding_check, Value) when is_boolean(Value) -> +validate_option(padding_check, Value, _) + when is_boolean(Value) -> Value; -validate_option(fallback, Value) when is_boolean(Value) -> +validate_option(partial_chain, Value, _) + when is_function(Value) -> Value; -validate_option(crl_check, Value) when is_boolean(Value) -> +validate_option(password, Value, _) + when is_list(Value) -> Value; -validate_option(crl_check, Value) when (Value == best_effort) or (Value == peer) -> +validate_option(protocol, Value = tls, _) -> Value; -validate_option(crl_cache, {Cb, {_Handle, Options}} = Value) when is_atom(Cb) and is_list(Options) -> +validate_option(protocol, Value = dtls, _) -> Value; -validate_option(beast_mitigation, Value) when Value == one_n_minus_one orelse - Value == zero_n orelse - Value == disabled -> - Value; -validate_option(max_handshake_size, Value) when is_integer(Value) andalso Value =< ?MAX_UNIT24 -> +validate_option(psk_identity, undefined, _) -> + undefined; +validate_option(psk_identity, Identity, _) + when is_list(Identity), Identity =/= "", length(Identity) =< 65535 -> + binary_filename(Identity); +validate_option(renegotiate_at, Value, _) when is_integer(Value) -> + erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT); +validate_option(reuse_session, undefined, _) -> + undefined; +validate_option(reuse_session, Value, _) + when is_function(Value) -> Value; -validate_option(protocol, Value = tls) -> +validate_option(reuse_session, Value, _) + when is_binary(Value) -> Value; -validate_option(protocol, Value = dtls) -> +validate_option(reuse_session, {Id, Data} = Value, _) + when is_binary(Id) andalso + is_binary(Data) -> Value; -validate_option(handshake, hello = Value) -> +validate_option(reuse_sessions, Value, _) + when is_boolean(Value) -> Value; -validate_option(handshake, full = Value) -> +validate_option(reuse_sessions, save = Value, _) -> Value; -validate_option(customize_hostname_check, Value) when is_list(Value) -> +validate_option(secure_renegotiate, Value, _) + when is_boolean(Value) -> Value; -validate_option(cb_info, {V1, V2, V3, V4} = Value) when is_atom(V1), - is_atom(V2), - is_atom(V3), - is_atom(V4) - -> +validate_option(server_name_indication, Value, _) + when is_list(Value) -> + %% RFC 6066, Section 3: Currently, the only server names supported are + %% DNS hostnames + %% case inet_parse:domain(Value) of + %% false -> + %% throw({error, {options, {{Opt, Value}}}}); + %% true -> + %% Value + %% end; + %% + %% But the definition seems very diffuse, so let all strings through + %% and leave it up to public_key to decide... Value; -validate_option(cb_info, {V1, V2, V3, V4, V5} = Value) when is_atom(V1), - is_atom(V2), - is_atom(V3), - is_atom(V4), - is_atom(V5) - -> +validate_option(server_name_indication, undefined, _) -> + undefined; +validate_option(server_name_indication, disable, _) -> + disable; +validate_option(session_tickets, Value, server) + when Value =:= disabled orelse + Value =:= stateful orelse + Value =:= stateless -> Value; -validate_option(use_ticket, Value) when is_list(Value) -> +validate_option(session_tickets, Value, server) -> + throw({error, + {options, role, + {session_tickets, + {Value, {server, [disabled, stateful, stateless]}}}}}); +validate_option(session_tickets, Value, client) + when Value =:= disabled orelse + Value =:= manual orelse + Value =:= auto -> Value; -validate_option(session_tickets, Value) when Value =:= disabled orelse - Value =:= manual orelse - Value =:= auto orelse - Value =:= stateless orelse - Value =:= stateful -> +validate_option(session_tickets, Value, client) -> + throw({error, + {options, role, + {session_tickets, + {Value, {client, [disabled, manual, auto]}}}}}); +validate_option(sni_fun, undefined, _) -> + undefined; +validate_option(sni_fun, Fun, _) + when is_function(Fun) -> + Fun; +validate_option(sni_hosts, [], _) -> + []; +validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail], _) + when is_list(Hostname) -> + RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined), + case RecursiveSNIOptions of + undefined -> + [{Hostname, validate_options(SSLOptions)} | + validate_option(sni_hosts, Tail)]; + _ -> + throw({error, {options, {sni_hosts, RecursiveSNIOptions}}}) + end; +validate_option(srp_identity, undefined, _) -> + undefined; +validate_option(srp_identity, {Username, Password}, _) + when is_list(Username), + is_list(Password), Username =/= "", + length(Username) =< 255 -> + {unicode:characters_to_binary(Username), + unicode:characters_to_binary(Password)}; +validate_option(user_lookup_fun, undefined, _) -> + undefined; +validate_option(user_lookup_fun, {Fun, _} = Value, _) + when is_function(Fun, 3) -> + Value; +validate_option(use_ticket, Value, _) + when is_list(Value) -> Value; -validate_option(anti_replay, '10k') -> - %% n = 10000 - %% p = 0.030003564 (1 in 33) - %% m = 72985 (8.91KiB) - %% k = 5 - {10, 5, 72985}; -validate_option(anti_replay, '100k') -> - %% n = 100000 - %% p = 0.03000428 (1 in 33) - %% m = 729845 (89.09KiB) - %% k = 5 - {10, 5, 729845}; -validate_option(anti_replay, Value) when (is_tuple(Value) andalso - tuple_size(Value) =:= 3) -> +validate_option(verify, Value, _) + when Value == verify_none; Value == verify_peer -> Value; -validate_option(Opt, undefined = Value) -> +validate_option(verify_fun, undefined, _) -> + undefined; +%% Backwards compatibility +validate_option(verify_fun, Fun, _) when is_function(Fun) -> + {fun(_,{bad_cert, _} = Reason, OldFun) -> + case OldFun([Reason]) of + true -> + {valid, OldFun}; + false -> + {fail, Reason} + end; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, Fun}; +validate_option(verify_fun, {Fun, _} = Value, _) when is_function(Fun) -> + Value; +validate_option(versions, Versions, _) -> + validate_versions(Versions, Versions); +validate_option(Opt, undefined = Value, _) -> AllOpts = maps:keys(?RULES), case lists:member(Opt, AllOpts) of true -> @@ -2220,7 +2483,7 @@ validate_option(Opt, undefined = Value) -> false -> throw({error, {options, {Opt, Value}}}) end; -validate_option(Opt, Value) -> +validate_option(Opt, Value, _) -> throw({error, {options, {Opt, Value}}}). handle_cb_info({V1, V2, V3, V4}) -> @@ -2290,25 +2553,34 @@ validate_versions([], Versions) -> validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3'; Version == 'tlsv1.2'; Version == 'tlsv1.1'; - Version == tlsv1; - Version == sslv3 -> - tls_validate_versions(Rest, Versions); + Version == tlsv1 -> + case tls_record:sufficient_crypto_support(Version) of + true -> + tls_validate_versions(Rest, Versions); + false -> + throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}}) + end; validate_versions([Version | Rest], Versions) when Version == 'dtlsv1'; Version == 'dtlsv1.2'-> - dtls_validate_versions(Rest, Versions); -validate_versions([Ver| _], Versions) -> - throw({error, {options, {Ver, {versions, Versions}}}}). + DTLSVer = dtls_record:protocol_version(Version), + case tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(DTLSVer)) of + true -> + dtls_validate_versions(Rest, Versions); + false -> + throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}}) + end; +validate_versions([Version| _], Versions) -> + throw({error, {options, {Version, {versions, Versions}}}}). tls_validate_versions([], Versions) -> tls_validate_version_gap(Versions); tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3'; Version == 'tlsv1.2'; Version == 'tlsv1.1'; - Version == tlsv1; - Version == sslv3 -> + Version == tlsv1 -> tls_validate_versions(Rest, Versions); -tls_validate_versions([Ver| _], Versions) -> - throw({error, {options, {Ver, {versions, Versions}}}}). +tls_validate_versions([Version| _], Versions) -> + throw({error, {options, {Version, {versions, Versions}}}}). %% Do not allow configuration of TLS 1.3 with a gap where TLS 1.2 is not supported %% as that configuration can trigger the built in version downgrade protection @@ -2325,7 +2597,6 @@ tls_validate_version_gap(Versions) -> _ -> Versions end. - dtls_validate_versions([], Versions) -> Versions; dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1'; @@ -2345,16 +2616,21 @@ ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) -> %% some trusted certs. ca_cert_default(verify_peer, undefined, _) -> "". -emulated_options(Protocol, Opts) -> +emulated_options(undefined, undefined, Protocol, Opts) -> case Protocol of tls -> tls_socket:emulated_options(Opts); dtls -> dtls_socket:emulated_options(Opts) - end. + end; +emulated_options(Transport, Socket, Protocol, Opts) -> + EmulatedOptions = tls_socket:emulated_options(), + {ok, Original} = tls_socket:getopts(Transport, Socket, EmulatedOptions), + {Inet, Emulated0} = emulated_options(undefined, undefined, Protocol, Opts), + {Inet, lists:ukeymerge(1, Emulated0, Original)}. -handle_cipher_option(Value, Version) when is_list(Value) -> - try binary_cipher_suites(Version, Value) of +handle_cipher_option(Value, Versions) when is_list(Value) -> + try binary_cipher_suites(Versions, Value) of Suites -> Suites catch @@ -2364,37 +2640,44 @@ handle_cipher_option(Value, Version) when is_list(Value) -> throw({error, {options, {ciphers, Value}}}) end. -binary_cipher_suites(Version, []) -> +binary_cipher_suites([{3,4} = Version], []) -> + %% Defaults to all supported suites that does + %% not require explicit configuration TLS-1.3 + %% only mode. + default_binary_suites(exclusive, Version); +binary_cipher_suites([Version| _], []) -> %% Defaults to all supported suites that does %% not require explicit configuration - default_binary_suites(Version); -binary_cipher_suites(Version, [Map|_] = Ciphers0) when is_map(Map) -> + default_binary_suites(default, Version); +binary_cipher_suites(Versions, [Map|_] = Ciphers0) when is_map(Map) -> Ciphers = [ssl_cipher_format:suite_map_to_bin(C) || C <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites(Versions, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> Ciphers = [ssl_cipher_format:suite_map_to_bin(tuple_to_map(C)) || C <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites([Version |_] = Versions, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> All = ssl_cipher:all_suites(Version) ++ ssl_cipher:anonymous_suites(Version), case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of [] -> %% Defaults to all supported suites that does %% not require explicit configuration - default_binary_suites(Version); + binary_cipher_suites(Versions, []); Ciphers -> Ciphers end; -binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> +binary_cipher_suites(Versions, [Head | _] = Ciphers0) when is_list(Head) -> %% Format: ["RC4-SHA","RC4-MD5"] Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, Ciphers0) -> + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites(Versions, Ciphers0) -> %% Format: "RC4-SHA:RC4-MD5" Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")], - binary_cipher_suites(Version, Ciphers). + binary_cipher_suites(Versions, Ciphers). -default_binary_suites(Version) -> +default_binary_suites(exclusive, {_, Minor}) -> + ssl_cipher:filter_suites(tls_v1:exclusive_suites(Minor)); +default_binary_suites(default, Version) -> ssl_cipher:filter_suites(ssl_cipher:suites(Version)). tuple_to_map({Kex, Cipher, Mac}) -> @@ -2497,9 +2780,9 @@ make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) -> end. connection_cb(tls) -> - tls_connection; + tls_gen_connection; connection_cb(dtls) -> - dtls_connection; + dtls_gen_connection; connection_cb(Opts) -> connection_cb(proplists:get_value(protocol, Opts, tls)). @@ -2514,34 +2797,53 @@ binary_filename(FileName) -> Enc = file:native_name_encoding(), unicode:characters_to_binary(FileName, unicode, Enc). -assert_proplist([]) -> - true; -assert_proplist([{Key,_} | Rest]) when is_atom(Key) -> - assert_proplist(Rest); +%% Assert that basic options are on the format {Key, Value} +%% with a few exceptions and phase out log_alert +handle_option_format([], Acc) -> + lists:reverse(Acc); +handle_option_format([{log_alert, Bool} | Rest], Acc) when is_boolean(Bool) -> + case proplists:get_value(log_level, Acc ++ Rest, undefined) of + undefined -> + handle_option_format(Rest, [{log_level, + map_log_level(Bool)} | Acc]); + _ -> + handle_option_format(Rest, Acc) + end; +handle_option_format([{Key,_} = Opt | Rest], Acc) when is_atom(Key) -> + handle_option_format(Rest, [Opt | Acc]); %% Handle exceptions -assert_proplist([{raw,_,_,_} | Rest]) -> - assert_proplist(Rest); -assert_proplist([inet | Rest]) -> - assert_proplist(Rest); -assert_proplist([inet6 | Rest]) -> - assert_proplist(Rest); -assert_proplist([Value | _]) -> +handle_option_format([{raw,_,_,_} = Opt | Rest], Acc) -> + handle_option_format(Rest, [Opt | Acc]); +handle_option_format([inet = Opt | Rest], Acc) -> + handle_option_format(Rest, [Opt | Acc]); +handle_option_format([inet6 = Opt | Rest], Acc) -> + handle_option_format(Rest, [Opt | Acc]); +handle_option_format([Value | _], _) -> throw({option_not_a_key_value_tuple, Value}). - -handle_verify_option(verify_none, #{fail_if_no_peer_cert := _FailIfNoPeerCert} = OptionsMap) -> - OptionsMap#{verify => verify_none, - fail_if_no_peer_cert => false}; -handle_verify_option(verify_peer, #{fail_if_no_peer_cert := FailIfNoPeerCert} = OptionsMap) -> +map_log_level(true) -> + notice; +map_log_level(false) -> + none. + +handle_verify_option(verify_none, #{fail_if_no_peer_cert := false} = OptionsMap) -> + OptionsMap#{verify => verify_none}; +handle_verify_option(verify_none, #{fail_if_no_peer_cert := true}) -> + throw({error, {options, incompatible, + {verify, verify_none}, + {fail_if_no_peer_cert, true}}}); +%% The option 'verify' is simulated by the configured 'verify_fun' that is mostly +%% hidden from the end user. When 'verify' is set to verify_none, the option +%% 'verify_fun' is also set to a default verify-none-verify_fun when processing +%% the configuration. If 'verify' is later changed from verify_none to verify_peer, +%% the 'verify_fun' must also be changed to undefined. When 'verify_fun' is set to +%% undefined, public_key's default verify_fun will be used that performs a full +%% verification. +handle_verify_option(verify_peer, #{verify := verify_none} = OptionsMap) -> OptionsMap#{verify => verify_peer, - fail_if_no_peer_cert => FailIfNoPeerCert}; -%% Handle 0, 1, 2 for backwards compatibility -handle_verify_option(0, OptionsMap) -> - handle_verify_option(verify_none, OptionsMap); -handle_verify_option(1, OptionsMap) -> - handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => false}); -handle_verify_option(2, OptionsMap) -> - handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => true}); + verify_fun => undefined}; +handle_verify_option(verify_peer, OptionsMap) -> + OptionsMap#{verify => verify_peer}; handle_verify_option(Value, _) -> throw({error, {options, {verify, Value}}}). @@ -2567,7 +2869,7 @@ default_cb_info(dtls) -> include_security_info([]) -> false; include_security_info([Item | Items]) -> - case lists:member(Item, [client_random, server_random, master_secret]) of + case lists:member(Item, [client_random, server_random, master_secret, keylog]) of true -> true; false -> @@ -2580,25 +2882,6 @@ server_name_indication_default(Host) when is_list(Host) -> server_name_indication_default(_) -> undefined. - -reject_alpn_next_prot_options({Opts,_,_}) -> - AlpnNextOpts = [alpn_advertised_protocols, - alpn_preferred_protocols, - next_protocols_advertised, - next_protocol_selector, - client_preferred_next_protocols], - reject_alpn_next_prot_options(AlpnNextOpts, Opts). - -reject_alpn_next_prot_options([], _) -> - ok; -reject_alpn_next_prot_options([Opt| AlpnNextOpts], Opts) -> - case lists:keyfind(Opt, 1, Opts) of - {Opt, Value} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - false -> - reject_alpn_next_prot_options(AlpnNextOpts, Opts) - end. - add_filter(undefined, Filters) -> Filters; add_filter(Filter, Filters) -> diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index 41bb7efcf6..aa21c8213e 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -119,7 +119,7 @@ own_alert_txt(#alert{level = Level, description = Description, where = #{line := alert_format(Alert) -> Txt = alert_txt(Alert), - {" ~s\n ", [Txt]}. + {" ~s\n", [Txt]}. alert_txt(#alert{level = Level, description = Description, role = Role}) -> "received " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++ diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 6d718dfef9..424bf05791 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -31,7 +31,7 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([trusted_cert_and_path/4, +-export([trusted_cert_and_paths/4, certificate_chain/3, certificate_chain/4, file_to_certificats/2, @@ -50,46 +50,47 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref(), fun()) -> - {der_cert() | unknown_ca, [der_cert()]}. +-spec trusted_cert_and_paths([der_cert()], db_handle(), certdb_ref(), fun()) -> + [{der_cert() | unknown_ca | invalid_issuer | selfsigned_peer, [der_cert()]}]. %% -%% Description: Extracts the root cert (if not presents tries to -%% look it up, if not found {bad_cert, unknown_ca} will be added verification -%% errors. Returns {RootCert, Path, VerifyErrors} +%% Description: Construct input to public_key:pkix_path_validation/3, +%% If the ROOT cert is not found {bad_cert, unknown_ca} will be returned +%% instead of the ROOT cert to be handled as a path validation error +%% by the verify_fun. +%% Returns {RootCert | RootCertRelatedError, Path} +%% Note: Path = lists:reverse(Chain) -- Root, that is on the peer cert +%% always comes first in the chain but last in the path. %%-------------------------------------------------------------------- -trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -> - Path = [BinCert | _] = lists:reverse(CertChain), - OtpCert = public_key:pkix_decode_cert(BinCert, otp), - SignedAndIssuerID = - case public_key:pkix_is_self_signed(OtpCert) of - true -> - {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self), - {self, IssuerId}; - false -> - other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) - end, - - case SignedAndIssuerID of - {error, issuer_not_found} -> - %% The root CA was not sent and cannot be found. - handle_incomplete_chain(Path, PartialChainHandler); - {self, _} when length(Path) == 1 -> - {selfsigned_peer, Path}; - {_ ,{SerialNr, Issuer}} -> - case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, Issuer) of - {ok, Trusted} -> - %% Trusted must be selfsigned or it is an incomplete chain - handle_path(Trusted, Path, PartialChainHandler); - _ -> - %% Root CA could not be verified, but partial - %% chain handler may trusted a cert that we got - handle_incomplete_chain(Path, PartialChainHandler) - end - end. +trusted_cert_and_paths([Peer] = Chain, CertDbHandle, CertDbRef, PartialChainHandler) -> + OtpCert = public_key:pkix_decode_cert(Peer, otp), + case public_key:pkix_is_self_signed(OtpCert) of + true -> + [{selfsigned_peer, [Peer]}]; + false -> + [handle_incomplete_chain(Chain, PartialChainHandler, {unknown_ca, [Peer]}, + CertDbHandle, CertDbRef)] + end; +trusted_cert_and_paths(Chain, CertDbHandle, CertDbRef, PartialChainHandler) -> + %% Construct possible certificate paths from the chain certificates. + %% If the chain contains extraneous certificates there could be + %% more than one possible path such chains might be used to phase out + %% an old certificate. + Paths = paths(Chain, CertDbHandle), + lists:map(fun(Path) -> + case handle_partial_chain(Path, PartialChainHandler, CertDbHandle, CertDbRef) of + {unknown_ca, _} = Result -> + handle_incomplete_chain(Chain, + PartialChainHandler, + Result, + CertDbHandle, CertDbRef); + Result -> + Result + end + end, Paths). %%-------------------------------------------------------------------- --spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref()) -> - {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. +-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}) -> + {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}. %% %% Description: Return the certificate chain to send to peer. %%-------------------------------------------------------------------- @@ -104,7 +105,7 @@ certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> %%-------------------------------------------------------------------- -spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}, [der_cert()]) -> - {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. + {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}. %% %% Description: Create certificate chain with certs from %%-------------------------------------------------------------------- @@ -133,7 +134,7 @@ file_to_crls(File, DbHandle) -> [Bin || {'CertificateList', Bin, not_encrypted} <- List]. %%-------------------------------------------------------------------- --spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid, +-spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid | valid_peer, term()) -> {valid, term()} | {fail, tuple()} | {unknown, term()}. @@ -141,7 +142,7 @@ file_to_crls(File, DbHandle) -> %% Description: Validates ssl/tls specific extensions %%-------------------------------------------------------------------- validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage', - extnValue = KeyUse}}, UserState = {Role, _,_, _, _, _}) -> + extnValue = KeyUse}}, UserState = #{role := Role}) -> case is_valid_extkey_usage(KeyUse, Role) of true -> {valid, UserState}; @@ -152,12 +153,28 @@ validate(_, {extension, _}, UserState) -> {unknown, UserState}; validate(_, {bad_cert, _} = Reason, _) -> {fail, Reason}; -validate(_, valid, UserState) -> - {valid, UserState}; -validate(Cert, valid_peer, UserState = {client, _,_, {Hostname, Customize}, _, _}) when Hostname =/= disable -> - verify_hostname(Hostname, Customize, Cert, UserState); -validate(_, valid_peer, UserState) -> - {valid, UserState}. +validate(Cert, valid, UserState) -> + case verify_sign(Cert, UserState) of + true -> + case maps:get(cert_ext, UserState, undefined) of + undefined -> + {valid, UserState}; + _ -> + verify_cert_extensions(Cert, UserState) + end; + false -> + {fail, {bad_cert, invalid_signature}} + end; +validate(Cert, valid_peer, UserState = #{role := client, server_name := Hostname, + customize_hostname_check := Customize}) when Hostname =/= disable -> + case verify_hostname(Hostname, Customize, Cert, UserState) of + {valid, UserState} -> + validate(Cert, valid, UserState); + Error -> + Error + end; +validate(Cert, valid_peer, UserState) -> + validate(Cert, valid, UserState). %%-------------------------------------------------------------------- -spec is_valid_key_usage(list(), term()) -> boolean(). @@ -321,6 +338,10 @@ public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorith public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'rsaEncryption'}, subjectPublicKey = Key}) -> Key; +public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-RSASSA-PSS', + parameters = Params}, + subjectPublicKey = Key}) -> + {Key, Params}; public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-dsa', parameters = {params, Params}}, subjectPublicKey = Key}) -> @@ -339,31 +360,6 @@ other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) -> end end. -handle_path({BinCert, OTPCert}, Path, PartialChainHandler) -> - case public_key:pkix_is_self_signed(OTPCert) of - true -> - {BinCert, lists:delete(BinCert, Path)}; - false -> - handle_incomplete_chain(Path, PartialChainHandler) - end. - -handle_incomplete_chain(Chain, Fun) -> - case catch Fun(Chain) of - {trusted_ca, DerCert} -> - new_trusteded_chain(DerCert, Chain); - unknown_ca = Error -> - {Error, Chain}; - _ -> - {unknown_ca, Chain} - end. - -new_trusteded_chain(DerCert, [DerCert | Chain]) -> - {DerCert, Chain}; -new_trusteded_chain(DerCert, [_ | Rest]) -> - new_trusteded_chain(DerCert, Rest); -new_trusteded_chain(_, []) -> - {unknown_ca, []}. - verify_hostname({fallback, Hostname}, Customize, Cert, UserState) when is_list(Hostname) -> case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of true -> @@ -392,3 +388,211 @@ verify_hostname(Hostname, Customize, Cert, UserState) -> false -> {fail, {bad_cert, hostname_check_failed}} end. + +verify_cert_extensions(Cert, #{cert_ext := CertExts} = UserState) -> + Id = public_key:pkix_subject_id(Cert), + Extensions = maps:get(Id, CertExts, []), + verify_cert_extensions(Cert, UserState, Extensions, #{}). + +verify_cert_extensions(Cert, UserState, [], _) -> + {valid, UserState#{issuer => Cert}}; +verify_cert_extensions(Cert, #{ocsp_responder_certs := ResponderCerts, + ocsp_state := OscpState, + issuer := Issuer} = UserState, [#certificate_status{response = OcspResponsDer} | Exts], Context) -> + #{ocsp_nonce := Nonce} = OscpState, + case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponsDer, ResponderCerts, Nonce) of + valid -> + verify_cert_extensions(Cert, UserState, Exts, Context); + {bad_cert, _} = Status -> + {fail, Status} + end; +verify_cert_extensions(Cert, UserState, [_|Exts], Context) -> + %% Skip unknow extensions! + verify_cert_extensions(Cert, UserState, Exts, Context). + +verify_sign(_, #{version := {_, Minor}}) when Minor < 3 -> + %% This verification is not applicable pre TLS-1.2 + true; +verify_sign(Cert, #{signature_algs := SignAlgs, + signature_algs_cert := undefined}) -> + is_supported_signature_algorithm(Cert, SignAlgs); +verify_sign(Cert, #{signature_algs_cert := SignAlgs}) -> + is_supported_signature_algorithm(Cert, SignAlgs). + +is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm = + #'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1'}}, [{_,_}|_] = SignAlgs) -> + lists:member({sha, dsa}, SignAlgs); +is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm = SignAlg}, [{_,_}|_] = SignAlgs) -> + Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg), + {Hash, Sign, _ } = ssl_cipher:scheme_to_components(Scheme), + lists:member({pre_1_3_hash(Hash), pre_1_3_sign(Sign)}, SignAlgs); +is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm = SignAlg}, SignAlgs) -> + Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg), + lists:member(Scheme, SignAlgs). + +pre_1_3_sign(rsa_pkcs1) -> + rsa; +pre_1_3_sign(Other) -> + Other. +pre_1_3_hash(sha1) -> + sha; +pre_1_3_hash(Hash) -> + Hash. + +paths(Chain, CertDbHandle) -> + paths(Chain, Chain, CertDbHandle, []). + +paths([Root], _, _, Path) -> + [[Root | Path]]; +paths([Cert1, Cert2 | Rest], Chain, CertDbHandle, Path) -> + case public_key:pkix_is_issuer(Cert1, Cert2) of + true -> + %% Chain orded so far + paths([Cert2 | Rest], Chain, CertDbHandle, [Cert1 | Path]); + false -> + %% Chain is unorded and/or contains extraneous certificates + unorded_or_extraneous(Chain, CertDbHandle) + end. + +unorded_or_extraneous([Peer | UnorderedChain], CertDbHandle) -> + ChainCandidates = extraneous_chains(UnorderedChain), + lists:map(fun(Candidate) -> + path_candidate(Peer, Candidate, CertDbHandle) + end, + ChainCandidates). + +path_candidate(Peer, ChainCandidateCAs, CertDbHandle) -> + {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, ChainCandidateCAs}), + %% certificate_chain/4 will make sure the chain is ordered + case certificate_chain(Peer, CertDbHandle, ExtractedCerts, []) of + {ok, undefined, Chain} -> + lists:reverse(Chain); + {ok, Root, Chain} -> + [Root | lists:reverse(Chain)] + end. + +handle_partial_chain([IssuerCert| Rest] = Path, PartialChainHandler, CertDbHandle, CertDbRef) -> + case public_key:pkix_is_self_signed(IssuerCert) of + true -> %% IssuerCert = ROOT (That is ROOT was included in chain) + {ok, {SerialNr, IssuerId}} = public_key:pkix_issuer_id(IssuerCert, self), + case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of + {ok, {IssuerCert, _}} -> %% Match sent ROOT to trusted ROOT + maybe_shorten_path(Path, PartialChainHandler, {IssuerCert, Rest}); + {ok, _} -> %% Did not match trusted ROOT + maybe_shorten_path(Path, PartialChainHandler, {invalid_issuer, Path}); + _ -> + maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path}) + end; + false -> + OTPCert = public_key:pkix_decode_cert(IssuerCert, otp), + case other_issuer(OTPCert, IssuerCert, CertDbHandle, CertDbRef) of + {other, {SerialNr, IssuerId}} -> + case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of + {ok, {NewIssuerCert, _}} -> + case public_key:pkix_is_self_signed(NewIssuerCert) of + true -> %% NewIssuerCert is a trusted ROOT cert + maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler, {NewIssuerCert, Path}); + false -> + maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler, + {unknown_ca, [NewIssuerCert | Path]}) + end; + _ -> + maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path}) + end; + {error, issuer_not_found} -> + maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path}) + end + end. + +maybe_shorten_path(Path, PartialChainHandler, Default) -> + %% This function might shorthen the + %% certificate path to be validated with + %% public_key:pkix_path_validation by letting + %% the user put its trust in an intermidate cert + %% from the certifcate chain sent by the peer. + try PartialChainHandler(Path) of + {trusted_ca, Root} -> + new_trusteded_path(Root, Path, Default); + unknown_ca -> + Default + catch _:_ -> + Default + end. + +new_trusteded_path(DerCert, [DerCert | Chain], _) -> + {DerCert, Chain}; +new_trusteded_path(DerCert, [_ | Rest], Default) -> + new_trusteded_path(DerCert, Rest, Default); +new_trusteded_path(_, [], Default) -> + %% User did not pick a cert present + %% in the cert chain so ignore + Default. + +handle_incomplete_chain([PeerCert| _] = Chain0, PartialChainHandler, Default, CertDbHandle, CertDbRef) -> + %% We received an incomplete chain, that is not all certs expected to be present are present. + %% See if we have the certificates to rebuild it. + case certificate_chain(PeerCert, CertDbHandle, CertDbRef) of + {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found + handle_partial_chain(lists:reverse(Chain), PartialChainHandler, CertDbHandle, CertDbRef); + _ -> + Default + end. + +extraneous_chains(Certs) -> + %% If some certs claim to be the same cert that is have the same + %% subject field we should create a list of possible chain certs + %% for each such cert. Only one chain, if any, should be + %% verifiable using available ROOT certs. + Subjects = [{subject(Cert), Cert} || Cert <- Certs], + Duplicates = find_duplicates(Subjects), + %% Number of certs with duplicates (same subject) has been limited + %% to 4 and the maximum number of combinations is limited to 16. + build_candidates(Duplicates, 4, 16). + +build_candidates(Map, Duplicates, Combinations) -> + Subjects = maps:keys(Map), + build_candidates(Subjects, Map, Duplicates, 1, Combinations, []). +%% +build_candidates([], _, _, _, _, Acc) -> + Acc; +build_candidates([H|T], Map, Duplicates, Combinations, Max, Acc0) -> + case maps:get(H, Map) of + {Certs, Counter} when Counter > 1 andalso + Duplicates > 0 andalso + Counter * Combinations =< Max -> + case Acc0 of + [] -> + Acc = [[Cert] || Cert <- Certs], + build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc); + _Else -> + Acc = [[Cert|L] || Cert <- Certs, L <- Acc0], + build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc) + end; + {[Cert|_Throw], _Counter} -> + case Acc0 of + [] -> + Acc = [[Cert]], + build_candidates(T, Map, Duplicates, Combinations, Max, Acc); + _Else -> + Acc = [[Cert|L] || L <- Acc0], + build_candidates(T, Map, Duplicates, Combinations, Max, Acc) + end + end. + +find_duplicates(Chain) -> + find_duplicates(Chain, #{}). +%% +find_duplicates([], Acc) -> + Acc; +find_duplicates([{Subject, Cert}|T], Acc) -> + case maps:get(Subject, Acc, none) of + none -> + find_duplicates(T, Acc#{Subject => {[Cert], 1}}); + {Certs, Counter} -> + find_duplicates(T, Acc#{Subject => {[Cert|Certs], Counter + 1}}) + end. + +subject(Cert) -> + {_Serial,Subject} = public_key:pkix_subject_id(Cert), + Subject. + diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index c97884ec08..85042e8612 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -34,25 +34,53 @@ -include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/2, security_parameters/3, security_parameters_1_3/2, - cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/6, aead_decrypt/6, - suites/1, all_suites/1, crypto_support_filters/0, - chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, - srp_suites/1, srp_suites_anon/1, - rc4_suites/1, des_suites/1, rsa_suites/1, - filter/3, filter_suites/1, filter_suites/2, - hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, - random_bytes/1, calc_mac_hash/4, calc_mac_hash/6, - is_stream_ciphersuite/1, signature_scheme/1, - scheme_to_components/1, hash_size/1, effective_key_bits/1, - key_material/1, signature_algorithm_to_scheme/1]). +-export([security_parameters/2, + security_parameters/3, + security_parameters_1_3/2, + cipher_init/3, + nonce_seed/2, + decipher/6, + cipher/5, + aead_encrypt/6, + aead_decrypt/6, + suites/1, + all_suites/1, + crypto_support_filters/0, + anonymous_suites/1, + psk_suites/1, + psk_suites_anon/1, + srp_suites/1, + srp_suites_anon/1, + rc4_suites/1, + des_suites/1, + rsa_suites/1, + filter/3, + filter_suites/1, + filter_suites/2, + hash_algorithm/1, + sign_algorithm/1, + is_acceptable_hash/2, + is_fallback/1, + random_bytes/1, + calc_mac_hash/4, + calc_mac_hash/6, + is_stream_ciphersuite/1, + signature_scheme/1, + scheme_to_components/1, + hash_size/1, + effective_key_bits/1, + key_material/1, + signature_algorithm_to_scheme/1, + bulk_cipher_algorithm/1]). %% RFC 8446 TLS 1.3 -export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2, encrypt_ticket/3, - decrypt_ticket/3]). + decrypt_ticket/3, + encrypt_data/4, + decrypt_data/4]). -compile(inline). @@ -108,7 +136,7 @@ security_parameters_1_3(SecParams, CipherSuite) -> %% Description: Initializes the #cipher_state according to BCA %%------------------------------------------------------------------- cipher_init(?RC4, IV, Key) -> - State = crypto:stream_init(rc4, Key), + State = {stream_init,rc4,Key,IV}, #cipher_state{iv = IV, key = Key, state = State}; cipher_init(Type, IV, Key) when Type == ?AES_GCM; Type == ?AES_CCM -> @@ -130,45 +158,60 @@ nonce_seed(Seed, CipherState) -> -spec cipher(cipher_enum(), #cipher_state{}, binary(), iodata(), ssl_record:ssl_version()) -> {binary(), #cipher_state{}}. %% -%% Description: Encrypts the data and the MAC using chipher described +%% Description: Encrypts the data and the MAC using cipher described %% by cipher_enum() and updating the cipher state %% Used for "MAC then Cipher" suites where first an HMAC of the -%% data is calculated and the data plus the HMAC is ecncrypted. +%% data is calculated and the data plus the HMAC is encrypted. %%------------------------------------------------------------------- cipher(?NULL, CipherState, <<>>, Fragment, _Version) -> {iolist_to_binary(Fragment), CipherState}; -cipher(?RC4, CipherState = #cipher_state{state = State0}, Mac, Fragment, _Version) -> +cipher(CipherEnum, CipherState = #cipher_state{state = {stream_init,rc4,Key,_IV}}, Mac, Fragment, Version) -> + State = crypto:crypto_init(rc4, Key, true), + cipher(CipherEnum, CipherState#cipher_state{state = State}, Mac, Fragment, Version); +cipher(?RC4, CipherState = #cipher_state{state = State}, Mac, Fragment, _Version) -> GenStreamCipherList = [Fragment, Mac], - {State1, T} = crypto:stream_encrypt(State0, GenStreamCipherList), - {iolist_to_binary(T), CipherState#cipher_state{state = State1}}; + T = crypto:crypto_update(State, GenStreamCipherList), + {iolist_to_binary(T), CipherState}; cipher(?DES, CipherState, Mac, Fragment, Version) -> block_cipher(fun(Key, IV, T) -> - crypto:block_encrypt(des_cbc, Key, IV, T) + crypto:crypto_one_time(des_cbc, Key, IV, T, true) end, block_size(des_cbc), CipherState, Mac, Fragment, Version); cipher(?'3DES', CipherState, Mac, Fragment, Version) -> - block_cipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) -> - crypto:block_encrypt(des3_cbc, [K1, K2, K3], IV, T) - end, block_size(des_cbc), CipherState, Mac, Fragment, Version); + block_cipher(fun(Key, IV, T) -> + crypto:crypto_one_time(des_ede3_cbc, Key, IV, T, true) + end, block_size(des_ede3_cbc), CipherState, Mac, Fragment, Version); cipher(?AES_CBC, CipherState, Mac, Fragment, Version) -> block_cipher(fun(Key, IV, T) when byte_size(Key) =:= 16 -> - crypto:block_encrypt(aes_cbc128, Key, IV, T); + crypto:crypto_one_time(aes_128_cbc, Key, IV, T, true); (Key, IV, T) when byte_size(Key) =:= 32 -> - crypto:block_encrypt(aes_cbc256, Key, IV, T) + crypto:crypto_one_time(aes_256_cbc, Key, IV, T, true) end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version). aead_encrypt(Type, Key, Nonce, Fragment, AdditionalData, TagLen) -> - crypto:block_encrypt(aead_type(Type), Key, Nonce, {AdditionalData, Fragment, TagLen}). + crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, Fragment, AdditionalData, TagLen, true). aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AdditionalData) -> - crypto:block_decrypt(aead_type(Type), Key, Nonce, {AdditionalData, CipherText, CipherTag}). - -aead_type(?AES_GCM) -> - aes_gcm; -aead_type(?AES_CCM) -> - aes_ccm; -aead_type(?AES_CCM_8) -> - aes_ccm; -aead_type(?CHACHA20_POLY1305) -> + crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, CipherText, AdditionalData, CipherTag, false). + +aead_type(?AES_GCM, 16) -> + aes_128_gcm; +aead_type(?AES_GCM, 24) -> + aes_192_gcm; +aead_type(?AES_GCM, 32) -> + aes_256_gcm; +aead_type(?AES_CCM, 16) -> + aes_128_ccm; +aead_type(?AES_CCM, 24) -> + aes_192_ccm; +aead_type(?AES_CCM, 32) -> + aes_256_ccm; +aead_type(?AES_CCM_8, 16) -> + aes_128_ccm; +aead_type(?AES_CCM_8, 24) -> + aes_192_ccm; +aead_type(?AES_CCM_8, 32) -> + aes_256_ccm; +aead_type(?CHACHA20_POLY1305, _) -> chacha20_poly1305. build_cipher_block(BlockSz, Mac, Fragment) -> @@ -211,12 +254,16 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV, state = IV_Cache0} = CS %%------------------------------------------------------------------- decipher(?NULL, _HashSz, CipherState, Fragment, _, _) -> {Fragment, <<>>, CipherState}; -decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _, _) -> - try crypto:stream_decrypt(State0, Fragment) of - {State, Text} -> +decipher(CipherEnum, HashSz, CipherState = #cipher_state{state = {stream_init,rc4,Key,_IV}}, + Fragment, Version, PaddingCheck) -> + State = crypto:crypto_init(rc4, Key, false), + decipher(CipherEnum, HashSz, CipherState#cipher_state{state = State}, Fragment, Version, PaddingCheck); +decipher(?RC4, HashSz, CipherState = #cipher_state{state = State}, Fragment, _, _) -> + try crypto:crypto_update(State, Fragment) of + Text -> GSC = generic_stream_cipher_from_bin(Text, HashSz), #generic_stream_cipher{content = Content, mac = Mac} = GSC, - {Content, Mac, CipherState#cipher_state{state = State}} + {Content, Mac, CipherState} catch _:_ -> %% This is a DECRYPTION_FAILED but @@ -229,17 +276,17 @@ decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _, decipher(?DES, HashSz, CipherState, Fragment, Version, PaddingCheck) -> block_decipher(fun(Key, IV, T) -> - crypto:block_decrypt(des_cbc, Key, IV, T) + crypto:crypto_one_time(des_cbc, Key, IV, T, false) end, CipherState, HashSz, Fragment, Version, PaddingCheck); decipher(?'3DES', HashSz, CipherState, Fragment, Version, PaddingCheck) -> - block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) -> - crypto:block_decrypt(des3_cbc, [K1, K2, K3], IV, T) + block_decipher(fun(Key, IV, T) -> + crypto:crypto_one_time(des_ede3_cbc, Key, IV, T, false) end, CipherState, HashSz, Fragment, Version, PaddingCheck); decipher(?AES_CBC, HashSz, CipherState, Fragment, Version, PaddingCheck) -> block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 -> - crypto:block_decrypt(aes_cbc128, Key, IV, T); + crypto:crypto_one_time(aes_128_cbc, Key, IV, T, false); (Key, IV, T) when byte_size(Key) =:= 32 -> - crypto:block_decrypt(aes_cbc256, Key, IV, T) + crypto:crypto_one_time(aes_256_cbc, Key, IV, T, false) end, CipherState, HashSz, Fragment, Version, PaddingCheck). block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, @@ -277,8 +324,6 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, %% %% Description: Returns a list of supported cipher suites. %%-------------------------------------------------------------------- -suites({3, 0}) -> - ssl_v3:suites(); suites({3, Minor}) -> tls_v1:suites(Minor); suites({_, Minor}) -> @@ -286,29 +331,14 @@ suites({_, Minor}) -> all_suites({3, _} = Version) -> suites(Version) - ++ chacha_suites(Version) ++ psk_suites(Version) ++ srp_suites(Version) - ++ rc4_suites(Version) + ++ rsa_suites(Version) ++ des_suites(Version) - ++ rsa_suites(Version); + ++ rc4_suites(Version); all_suites(Version) -> dtls_v1:all_suites(Version). -%%-------------------------------------------------------------------- --spec chacha_suites(ssl_record:ssl_version() | integer()) -> - [ssl_cipher_format:cipher_suite()]. -%% -%% Description: Returns list of the chacha cipher suites, only supported -%% if explicitly set by user for now due to interop problems, proably need -%% to be fixed in crypto. -%%-------------------------------------------------------------------- -chacha_suites({3, _}) -> - [?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256]; -chacha_suites(_) -> - []. %%-------------------------------------------------------------------- -spec anonymous_suites(ssl_record:ssl_version() | integer()) -> @@ -426,8 +456,6 @@ psk_suites_anon(0) -> %% Description: Returns a list of the SRP cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- -srp_suites({3,0}) -> - []; srp_suites(_) -> [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, @@ -442,8 +470,6 @@ srp_suites(_) -> %% Description: Returns a list of the SRP anonymous cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- -srp_suites_anon({3,0}) -> - []; srp_suites_anon(_) -> [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA, @@ -505,13 +531,15 @@ rsa_suites(0) -> ?TLS_RSA_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_3DES_EDE_CBC_SHA ]; -rsa_suites(N) when N =< 4 -> +rsa_suites(N) when N >= 3 -> [ ?TLS_RSA_WITH_AES_256_GCM_SHA384, ?TLS_RSA_WITH_AES_256_CBC_SHA256, ?TLS_RSA_WITH_AES_128_GCM_SHA256, ?TLS_RSA_WITH_AES_128_CBC_SHA256 - ]. + ]; +rsa_suites(_) -> + []. %%-------------------------------------------------------------------- -spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()], @@ -645,24 +673,12 @@ is_acceptable_cipher(null, _Algos) -> true; is_acceptable_cipher(rc4_128, Algos) -> proplists:get_bool(rc4, Algos); -is_acceptable_cipher(des_cbc, Algos) -> - proplists:get_bool(des_cbc, Algos); is_acceptable_cipher('3des_ede_cbc', Algos) -> - proplists:get_bool(des_ede3, Algos); -is_acceptable_cipher(aes_128_cbc, Algos) -> - proplists:get_bool(aes_cbc128, Algos); -is_acceptable_cipher(aes_256_cbc, Algos) -> - proplists:get_bool(aes_cbc256, Algos); -is_acceptable_cipher(Cipher, Algos) - when Cipher == aes_128_gcm; - Cipher == aes_256_gcm -> - proplists:get_bool(aes_gcm, Algos); -is_acceptable_cipher(Cipher, Algos) - when Cipher == aes_128_ccm; - Cipher == aes_256_ccm; - Cipher == aes_128_ccm_8; - Cipher == aes_256_ccm_8 -> - proplists:get_bool(aes_ccm, Algos); + proplists:get_bool(des_ede3_cbc, Algos); +is_acceptable_cipher(aes_128_ccm_8, Algos) -> + proplists:get_bool(aes_128_ccm, Algos); +is_acceptable_cipher(aes_256_ccm_8, Algos) -> + proplists:get_bool(aes_256_ccm, Algos); is_acceptable_cipher(Cipher, Algos) -> proplists:get_bool(Cipher, Algos). @@ -734,8 +750,6 @@ hash_size(sha512) -> mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, _Length, _Fragment) -> <<>>; -mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - ssl_v3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment); mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) when N =:= 1; N =:= 2; N =:= 3; N =:= 4 -> tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, @@ -863,6 +877,7 @@ iv_size(Cipher) -> block_size(Cipher). block_size(Cipher) when Cipher == des_cbc; + Cipher == des_ede3_cbc; Cipher == '3des_ede_cbc' -> 8; block_size(Cipher) when Cipher == aes_128_cbc; @@ -982,8 +997,20 @@ scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}; %% Handling legacy signature algorithms scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}. - -%% TODO: Add support for ed25519, ed448, rsa_pss* +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS', + parameters = #'RSASSA-PSS-params'{ + maskGenAlgorithm = + #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = HashAlgo}}}) -> + #'HashAlgorithm'{algorithm = HashOid} = HashAlgo, + case public_key:pkix_hash_type(HashOid) of + sha256 -> + rsa_pss_pss_sha256; + sha384 -> + rsa_pss_pss_sha384; + sha512 -> + rsa_pss_pss_sha512 + end; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha256WithRSAEncryption}) -> rsa_pkcs1_sha256; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) -> @@ -1001,8 +1028,18 @@ signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'sha-1WithRSAEn signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) -> rsa_pkcs1_sha1; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA1'}) -> - ecdsa_sha1. - + ecdsa_sha1; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed25519'}) -> + eddsa_ed25519; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed448'}) -> + eddsa_ed448; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption', + parameters = ?NULL}) -> + rsa_pkcs1_sha1; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption'}) -> + rsa_pss_rsae; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS'}) -> + rsa_pss_pss. %% RFC 5246: 6.2.3.2. CBC Block Cipher %% @@ -1426,7 +1463,7 @@ decrypt_ticket(CipherFragment, Shard, IV) -> encrypt_ticket_data(Plaintext, Shard, IV) -> - AAD = additional_data(erlang:iolist_size(Plaintext) + 16), %% TagLen = 16 + AAD = additional_data(<<"ticket">>, erlang:iolist_size(Plaintext) + 16), %% TagLen = 16 {OTP, Key} = make_otp_key(Shard), {Content, CipherTag} = crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Plaintext, AAD, 16, true), <<Content/binary,CipherTag/binary,OTP/binary>>. @@ -1434,16 +1471,34 @@ encrypt_ticket_data(Plaintext, Shard, IV) -> decrypt_ticket_data(CipherFragment, Shard, IV) -> Size = byte_size(Shard), - AAD = additional_data(erlang:iolist_size(CipherFragment) - Size), + AAD = additional_data(<<"ticket">>, erlang:iolist_size(CipherFragment) - Size), + Len = byte_size(CipherFragment) - Size - 16, + case CipherFragment of + <<Encrypted:Len/binary,CipherTag:16/binary,OTP:Size/binary>> -> + Key = crypto:exor(OTP, Shard), + crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, + Encrypted, AAD, CipherTag, + false); + _ -> + error + end. + +encrypt_data(ADTag, Plaintext, Shard, IV) -> + AAD = additional_data(ADTag, erlang:iolist_size(Plaintext) + 16), %% TagLen = 16 + {OTP, Key} = make_otp_key(Shard), + {Content, CipherTag} = crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Plaintext, AAD, 16, true), + <<Content/binary,CipherTag/binary,OTP/binary>>. + +decrypt_data(ADTag, CipherFragment, Shard, IV) -> + Size = byte_size(Shard), + AAD = additional_data(ADTag, erlang:iolist_size(CipherFragment) - Size), Len = byte_size(CipherFragment) - Size - 16, <<Encrypted:Len/binary,CipherTag:16/binary,OTP:Size/binary>> = CipherFragment, Key = crypto:exor(OTP, Shard), crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Encrypted, AAD, CipherTag, false). - -additional_data(Length) -> - <<"ticket",?UINT16(Length)>>. - +additional_data(Tag, Length) -> + <<Tag/binary,?UINT16(Length)>>. make_otp_key(Shard) -> Size = byte_size(Shard), diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index 0a7c4560fb..9f2141b6f8 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -260,6 +260,18 @@ %% TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = { 0xC0, 0x0A } -define(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#0A)>>). +%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM = {0xC0,0xAC} +-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#AC)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM = {0xC0,0xAD} +-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#AD)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = {0xC0,0xAE} +-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AE)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = {0xC0,0xAF} +-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AF)>>). + %% ECDH_RSA %% TLS_ECDH_RSA_WITH_NULL_SHA = { 0xC0, 0x0B } diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl index f14b55bcf6..589b0facf8 100644 --- a/lib/ssl/src/ssl_cipher_format.erl +++ b/lib/ssl/src/ssl_cipher_format.erl @@ -77,13 +77,13 @@ suite_map_to_str(#{key_exchange := Kex, cipher := Cipher, mac := aead, prf := PRF}) -> - "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "TLS_" ++ kex_str(Kex) ++ "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ - "_" ++ string:to_upper(atom_to_list(PRF)); + prf_str("_", PRF); suite_map_to_str(#{key_exchange := Kex, cipher := Cipher, mac := Mac}) -> - "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "TLS_" ++ kex_str(Kex) ++ "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ "_" ++ string:to_upper(atom_to_list(Mac)). @@ -97,12 +97,6 @@ suite_str_to_map(SuiteStr)-> case string:split(Str0, "_WITH_") of [Rest] -> tls_1_3_suite_str_to_map(Rest); - [Prefix, Kex | Rest] when Prefix == "SPR"; - Prefix == "PSK"; - Prefix == "DHE"; - Prefix == "ECDHE" - -> - pre_tls_1_3_suite_str_to_map(Prefix ++ "_" ++ Kex, Rest); [Kex| Rest] -> pre_tls_1_3_suite_str_to_map(Kex, Rest) end. @@ -116,26 +110,36 @@ suite_map_to_openssl_str(#{key_exchange := null} = Suite) -> suite_map_to_str(Suite); suite_map_to_openssl_str(#{key_exchange := rsa = Kex, cipher := Cipher, - mac := Mac}) when Cipher == "des_cbc"; - Cipher == "3des_ede_cbc" -> + mac := aead, + prf := PRF}) when PRF =/= default_prf -> + openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ + "-" ++ string:to_upper(atom_to_list(PRF)); +suite_map_to_openssl_str(#{key_exchange := Kex, + cipher := Cipher, + mac := Mac}) when (Kex == rsa) orelse + (Kex == srp_anon) + andalso + (Cipher == "des_cbc") orelse + (Cipher == "3des_ede_cbc") -> openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ "-" ++ string:to_upper(atom_to_list(Mac)); suite_map_to_openssl_str(#{key_exchange := Kex, cipher := chacha20_poly1305 = Cipher, - mac := aead}) -> - openssl_suite_start(string:to_upper(atom_to_list(Kex))) - ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))); + mac := aead, + prf := sha256}) -> + openssl_suite_start(kex_str(Kex), Cipher) + ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))); suite_map_to_openssl_str(#{key_exchange := Kex, cipher := Cipher, mac := aead, prf := PRF}) -> - openssl_suite_start(string:to_upper(atom_to_list(Kex))) + openssl_suite_start(kex_str(Kex), Cipher) ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ - "-" ++ string:to_upper(atom_to_list(PRF)); + prf_str("-", PRF); suite_map_to_openssl_str(#{key_exchange := Kex, - cipher := Cipher, - mac := Mac}) -> - openssl_suite_start(string:to_upper(atom_to_list(Kex))) + cipher := Cipher, + mac := Mac}) -> + openssl_suite_start(kex_str(Kex), Cipher) ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ "-" ++ string:to_upper(atom_to_list(Mac)). @@ -148,14 +152,20 @@ suite_openssl_str_to_map("DES-CBC3-SHA") -> suite_str_to_map("TLS_RSA_WITH_3DES_EDE_CBC_SHA"); suite_openssl_str_to_map("SRP-DSS-DES-CBC3-SHA") -> suite_str_to_map("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA"); -suite_openssl_str_to_map("DHE-RSA-" ++ Rest) -> +suite_openssl_str_to_map("ADH" ++ Rest) -> + suite_openssl_str_to_map("DH-anon", Rest); +suite_openssl_str_to_map("AECDH" ++ Rest) -> + suite_openssl_str_to_map("ECDH-anon", Rest); +suite_openssl_str_to_map("EDH-RSA" ++ Rest) -> suite_openssl_str_to_map("DHE-RSA", Rest); -suite_openssl_str_to_map("DHE-DSS-" ++ Rest) -> +suite_openssl_str_to_map("EDH-DSS-" ++ Rest) -> suite_openssl_str_to_map("DHE-DSS", Rest); -suite_openssl_str_to_map("EDH-RSA-" ++ Rest) -> +suite_openssl_str_to_map("DHE-RSA-" ++ Rest) -> suite_openssl_str_to_map("DHE-RSA", Rest); -suite_openssl_str_to_map("EDH-DSS-" ++ Rest) -> +suite_openssl_str_to_map("DHE-DSS-" ++ Rest) -> suite_openssl_str_to_map("DHE-DSS", Rest); +suite_openssl_str_to_map("DHE-PSK-" ++ Rest) -> + suite_openssl_str_to_map("DHE-PSK", Rest); suite_openssl_str_to_map("DES" ++ _ = Rest) -> suite_openssl_str_to_map("RSA", Rest); suite_openssl_str_to_map("AES" ++ _ = Rest) -> @@ -174,8 +184,6 @@ suite_openssl_str_to_map("RSA-PSK-" ++ Rest) -> suite_openssl_str_to_map("RSA-PSK", Rest); suite_openssl_str_to_map("RSA-" ++ Rest) -> suite_openssl_str_to_map("RSA", Rest); -suite_openssl_str_to_map("DHE-PSK-" ++ Rest) -> - suite_openssl_str_to_map("DHE-PSK", Rest); suite_openssl_str_to_map("ECDHE-PSK-" ++ Rest) -> suite_openssl_str_to_map("ECDHE-PSK", Rest); suite_openssl_str_to_map("PSK-" ++ Rest) -> @@ -348,12 +356,12 @@ suite_bin_to_map(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) -> #{key_exchange => dh_anon, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) -> #{key_exchange => dh_anon, cipher => aes_256_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; %%% PSK Cipher Suites RFC 4279 suite_bin_to_map(?TLS_PSK_WITH_RC4_128_SHA) -> #{key_exchange => psk, @@ -466,7 +474,7 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CBC_SHA256) -> #{key_exchange => psk, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_PSK_WITH_AES_256_CBC_SHA384) -> #{key_exchange => psk, cipher => aes_256_cbc, @@ -476,7 +484,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) -> #{key_exchange => dhe_psk, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) -> #{key_exchange => dhe_psk, cipher => aes_256_cbc, @@ -506,7 +514,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA256) -> #{key_exchange => dhe_psk, cipher => null, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA384) -> #{key_exchange => dhe_psk, cipher => null, @@ -516,7 +524,7 @@ suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA256) -> #{key_exchange => rsa_psk, cipher => null, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA384) -> #{key_exchange => rsa_psk, cipher => null, @@ -547,7 +555,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) -> #{key_exchange => ecdhe_psk, cipher => aes_256_cbc, @@ -557,7 +565,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA256) -> #{key_exchange => ecdhe_psk, cipher => null, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) -> #{key_exchange => ecdhe_psk, cipher => null, mac => sha384, @@ -566,22 +574,22 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) -> suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_gcm, - mac => null, + mac => aead, prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384) -> #{key_exchange => ecdhe_psk, cipher => aes_256_gcm, - mac => null, + mac => aead, prf => sha384}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_ccm, - mac => null, + mac => aead, prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_ccm_8, - mac => null, + mac => aead, prf => sha256}; %%% SRP Cipher Suites RFC 5054 suite_bin_to_map(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) -> @@ -680,6 +688,26 @@ suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) -> cipher => aes_256_cbc, mac => sha, prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_128_ccm, + mac => aead, + prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_256_ccm, + mac => aead, + prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_128_ccm_8, + mac => aead, + prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_256_ccm_8, + mac => aead, + prf => default_prf}; suite_bin_to_map(?TLS_ECDH_RSA_WITH_NULL_SHA) -> #{key_exchange => ecdh_rsa, cipher => null, @@ -840,7 +868,7 @@ suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) -> suite_bin_to_map(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) -> #{key_exchange => dh_dss, cipher => aes_128_gcm, - mac => null, + mac => aead, prf => sha256}; suite_bin_to_map(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) -> #{key_exchange => dh_dss, @@ -902,42 +930,42 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM) -> #{key_exchange => psk, cipher => aes_128_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM) -> #{key_exchange => psk, cipher => aes_256_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CCM) -> #{key_exchange => dhe_psk, cipher => aes_128_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CCM) -> #{key_exchange => dhe_psk, cipher => aes_256_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM_8) -> #{key_exchange => psk, cipher => aes_128_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM_8) -> #{key_exchange => psk, cipher => aes_256_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_128_CCM_8) -> #{key_exchange => dhe_psk, cipher => aes_128_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_256_CCM_8) -> #{key_exchange => dhe_psk, cipher => aes_256_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(#{key_exchange := psk_dhe, cipher := aes_256_ccm_8, mac := aead, @@ -980,12 +1008,12 @@ suite_bin_to_map(?TLS_AES_128_CCM_SHA256) -> #{key_exchange => any, cipher => aes_128_ccm, mac => aead, + prf => sha256}; +suite_bin_to_map(?TLS_AES_128_CCM_8_SHA256) -> + #{key_exchange => any, + cipher => aes_128_ccm_8, + mac => aead, prf => sha256}. -%% suite_bin_to_map(?TLS_AES_128_CCM_8_SHA256) -> -%% #{key_exchange => any, -%% cipher => aes_128_ccm_8, -%% mac => aead, -%% prf => sha256}. %%-------------------------------------------------------------------- -spec suite_legacy(cipher_suite() | internal_erl_cipher_suite()) -> old_erl_cipher_suite(). @@ -1297,22 +1325,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_psk, %%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05 suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_128_gcm, - mac := null, + mac := aead, prf := sha256}) -> ?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256; suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_256_gcm, - mac := null, + mac := aead, prf := sha384}) -> ?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384; suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_128_ccm_8, - mac := null, + mac := aead, prf := sha256}) -> ?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256; suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_128_ccm, - mac := null, + mac := aead, prf := sha256}) -> ?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256; %%% SRP Cipher Suites RFC 5054 @@ -1393,6 +1421,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, cipher := aes_256_cbc, mac := sha}) -> ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_128_ccm, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_256_ccm, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_128_ccm_8, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_256_ccm_8, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := ecdh_rsa, cipher := null, mac := sha}) -> @@ -1616,22 +1660,22 @@ suite_map_to_bin(#{key_exchange := dhe_rsa, suite_map_to_bin(#{key_exchange := psk, cipher := aes_128_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_128_CCM; suite_map_to_bin(#{key_exchange := psk, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_128_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_PSK_WITH_AES_128_CCM; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_PSK_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := rsa, cipher := aes_128_ccm, @@ -1641,7 +1685,7 @@ suite_map_to_bin(#{key_exchange := rsa, suite_map_to_bin(#{key_exchange := rsa, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_RSA_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_128_ccm, @@ -1651,48 +1695,48 @@ suite_map_to_bin(#{key_exchange := dhe_rsa, suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_RSA_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := psk, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := psk, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_DHE_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_DHE_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := rsa, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_RSA_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := rsa, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_RSA_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_RSA_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_RSA_WITH_AES_256_CCM_8; %% TLS 1.3 Cipher Suites RFC8446 @@ -1715,12 +1759,12 @@ suite_map_to_bin(#{key_exchange := any, cipher := aes_128_ccm, mac := aead, prf := sha256}) -> - ?TLS_AES_128_CCM_SHA256. -%% suite_map_to_bin(#{key_exchange := any, -%% cipher := aes_128_ccm_8, -%% mac := aead, -%% prf := sha256}) -> -%% ?TLS_AES_128_CCM_8_SHA256. + ?TLS_AES_128_CCM_SHA256; +suite_map_to_bin(#{key_exchange := any, + cipher := aes_128_ccm_8, + mac := aead, + prf := sha256}) -> + ?TLS_AES_128_CCM_8_SHA256. tls_1_3_suite_str_to_map(CipherStr) -> @@ -1740,21 +1784,42 @@ pre_tls_1_3_suite_str_to_map(KexStr, Rest) -> cipher => Cipher, prf => Prf }. - -cipher_str_to_algs(_, CipherStr, "CCM"= End) -> %% PRE TLS 1.3 - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -cipher_str_to_algs(_, CipherStr, "8" = End) -> %% PRE TLS 1.3 - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -cipher_str_to_algs(_, CipherStr, "CHACHA20_POLY1305" = End) -> %% PRE TLS 1.3 - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -cipher_str_to_algs(_, CipherStr0, "") -> %% TLS 1.3 + +kex_str(srp_dss) -> + "SRP_SHA_DSS"; +kex_str(srp_rsa) -> + "SRP_SHA_RSA"; +kex_str(srp_anon) -> + "SRP_SHA"; +kex_str(dh_anon) -> + "DH_anon"; +kex_str(ecdh_anon) -> + "ECDH_anon"; +kex_str(Kex) -> + string:to_upper(atom_to_list(Kex)). + +prf_str(_, default_prf) -> + ""; +prf_str(Prefix, PRF) -> + Prefix ++ string:to_upper(atom_to_list(PRF)). + +cipher_str_to_algs(any, CipherStr0, "") -> %% TLS 1.3 [CipherStr, AlgStr] = string:split(CipherStr0, "_", trailing), Hash = algo_str_to_atom(AlgStr), Cipher = algo_str_to_atom(CipherStr), {Cipher, aead, Hash}; +cipher_str_to_algs(_Kex, CipherStr, "CCM"= End) -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), + {Cipher, aead, default_prf}; +cipher_str_to_algs(_Kex, CipherStr, "GCM"= End) -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), + {Cipher, aead, default_prf}; +cipher_str_to_algs(_Kex, CipherStr, "8" = End) -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), + {Cipher, aead, default_prf}; +cipher_str_to_algs(_Kex, "CHACHA20_POLY1305" = CipherStr, "") -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr), + {Cipher, aead, sha256}; cipher_str_to_algs(Kex, CipherStr, HashStr) -> %% PRE TLS 1.3 Hash = algo_str_to_atom(HashStr), Cipher = algo_str_to_atom(CipherStr), @@ -1796,88 +1861,143 @@ openssl_is_aead_cipher("CHACHA20-POLY1305") -> openssl_is_aead_cipher(CipherStr) -> case string:split(CipherStr, "-", trailing) of [_, Rest] -> - (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "8"); + (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "CCM8"); [_] -> false end. algo_str_to_atom("SRP_SHA_DSS") -> srp_dss; +algo_str_to_atom("SRP_SHA_RSA") -> + srp_rsa; +algo_str_to_atom("SRP_SHA") -> + srp_anon; +algo_str_to_atom("SRP") -> + srp_anon; algo_str_to_atom(AlgoStr) -> erlang:list_to_existing_atom(string:to_lower(AlgoStr)). +openssl_cipher_name(Kex, "3DES_EDE_CBC" ++ _) when Kex == ecdhe_psk; + Kex == srp_anon; + Kex == psk; + Kex == dhe_psk -> + "3DES-EDE-CBC"; openssl_cipher_name(_, "3DES_EDE_CBC" ++ _) -> "DES-CBC3"; openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == rsa; Kex == dhe_rsa; + Kex == dhe_dss; + Kex == ecdh_rsa; Kex == ecdhe_rsa; - Kex == ecdhe_ecdsa -> + Kex == ecdh_ecdsa; + Kex == ecdhe_ecdsa; + Kex == ecdh_anon; + Kex == dh_anon -> openssl_name_concat(CipherStr); openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == rsa; Kex == dhe_rsa; + Kex == dhe_dss; + Kex == ecdh_rsa; Kex == ecdhe_rsa; - Kex == ecdhe_ecdsa -> + Kex == ecdh_ecdsa; + Kex == ecdhe_ecdsa; + Kex == ecdh_anon; + Kex == dh_anon -> openssl_name_concat(CipherStr); -openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp; +openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp_anon; Kex == srp_rsa -> lists:append(string:replace(CipherStr, "_", "-", all)); -openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp; +openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp_anon; Kex == srp_rsa -> lists:append(string:replace(CipherStr, "_", "-", all)); openssl_cipher_name(_, "AES_128_CBC" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-CBC"; openssl_cipher_name(_, "AES_256_CBC" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-CBC"; +openssl_cipher_name(_, "AES_128_GCM_8") -> + openssl_name_concat("AES_128_GCM") ++ "-GCM8"; +openssl_cipher_name(_, "AES_256_GCM_8") -> + openssl_name_concat("AES_256_GCM") ++ "-GCM8"; +openssl_cipher_name(_, "AES_128_CCM_8") -> + openssl_name_concat("AES_128_CCM") ++ "-CCM8"; +openssl_cipher_name(_, "AES_256_CCM_8") -> + openssl_name_concat("AES_256_CCM") ++ "-CCM8"; openssl_cipher_name(_, "AES_128_GCM" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-GCM"; openssl_cipher_name(_, "AES_256_GCM" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-GCM"; +openssl_cipher_name(_, "AES_128_CCM" ++ _ = CipherStr) -> + openssl_name_concat(CipherStr) ++ "-CCM"; +openssl_cipher_name(_, "AES_256_CCM" ++ _ = CipherStr) -> + openssl_name_concat(CipherStr) ++ "-CCM"; openssl_cipher_name(_, "RC4" ++ _) -> "RC4"; openssl_cipher_name(_, CipherStr) -> lists:append(string:replace(CipherStr, "_", "-", all)). - -openssl_suite_start(Kex) -> - case openssl_kex_name(Kex) of +openssl_suite_start(Kex, Cipher) -> + case openssl_kex_name(Kex, Cipher) of "" -> ""; Name -> Name ++ "-" end. -openssl_kex_name("RSA") -> +openssl_kex_name("RSA", _) -> ""; -openssl_kex_name(Kex) -> +openssl_kex_name("DH_anon", _) -> + "ADH"; +openssl_kex_name("ECDH_anon", _) -> + "AECDH"; +openssl_kex_name("SRP_SHA", _) -> + "SRP"; +openssl_kex_name("SRP_SHA_RSA", _) -> + "SRP-RSA"; +openssl_kex_name("SRP_SHA_DSS", _) -> + "SRP-DSS"; +openssl_kex_name("DHE_RSA", Cipher) when Cipher == des_cbc; + Cipher == '3des_ede_cbc' -> + "EDH-RSA"; +openssl_kex_name(Kex, _) -> lists:append(string:replace(Kex, "_", "-", all)). - kex_name_from_openssl(Kex) -> - lists:append(string:replace(Kex, "-", "_", all)). + case lists:append(string:replace(Kex, "-", "_", all)) of + "EDH-RSA" -> + "DHE_RSA"; + "SRP" -> + "SRP_SHA"; + Str -> + Str + end. cipher_name_from_openssl("AES128") -> "AES_128_CBC"; cipher_name_from_openssl("AES256") -> "AES_256_CBC"; -cipher_name_from_openssl("AES128-CBC") -> - "AES_128_CBC"; -cipher_name_from_openssl("AES256-CBC") -> - "AES_256_CBC"; -cipher_name_from_openssl("AES-128-CBC") -> - "AES_128_CBC"; -cipher_name_from_openssl("AES-256-CBC") -> - "AES_256_CBC"; -cipher_name_from_openssl("AES128-GCM") -> - "AES_128_GCM"; -cipher_name_from_openssl("AES256-GCM") -> - "AES_256_GCM"; +cipher_name_from_openssl("AES128-CCM8") -> + "AES_128_CCM_8"; +cipher_name_from_openssl("AES256-CCM8") -> + "AES_256_CCM_8"; +cipher_name_from_openssl("AES128-" ++ Suffix) -> + "AES_128_" ++ lists:append(string:replace(Suffix, "-", "_", all)); +cipher_name_from_openssl("AES256-" ++ Suffix) -> + "AES_256_" ++ lists:append(string:replace(Suffix, "-", "_", all)); +cipher_name_from_openssl("AES128_" ++ Suffix) -> + "AES_128_" ++ Suffix; +cipher_name_from_openssl("AES256_" ++ Suffix) -> + "AES_256_" ++ Suffix; cipher_name_from_openssl("DES-CBC") -> "DES_CBC"; cipher_name_from_openssl("DES-CBC3") -> "3DES_EDE_CBC"; +cipher_name_from_openssl("3DES-EDE-CBC") -> + "3DES_EDE_CBC"; cipher_name_from_openssl("RC4") -> "RC4_128"; +cipher_name_from_openssl("CHACHA20-POLY1305") -> + "CHACHA20_POLY1305"; cipher_name_from_openssl(Str) -> - Str. + lists:append(string:replace(Str, "-", "_", all)). openssl_name_concat(Str0) -> [Str, _] = string:split(Str0, "_", trailing), @@ -1887,8 +2007,8 @@ openssl_name_concat(Str0) -> suite_openssl_str_to_map(Kex0, Rest) -> Kex = algo_str_to_atom(kex_name_from_openssl(Kex0)), - [CipherStr, AlgStr] = string:split(Rest, "-", trailing), - {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, CipherStr, AlgStr), + [Part1, Part2] = string:split(Rest, "-", trailing), + {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, Part1, Part2), #{key_exchange => Kex, mac => Mac, cipher => Cipher, @@ -1896,19 +2016,25 @@ suite_openssl_str_to_map(Kex0, Rest) -> }. %% Does only need own implementation PRE TLS 1.3 -openssl_cipher_str_to_algs(_, CipherStr, "CCM"= End) -> - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -openssl_cipher_str_to_algs(_, CipherStr, "8" = End) -> - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), +openssl_cipher_str_to_algs(_, Part1, "CCM" = End) -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, Part1, "GCM" = End) -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, Part2, "CCM8") -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-CCM-8")), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, Part2, "GCM8") -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-GCM-8")), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, "CHACHA20", "POLY1305") -> + Cipher = chacha20_poly1305, {Cipher, aead, sha256}; -openssl_cipher_str_to_algs(_, CipherStr, "POLY1305" = End) -> - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -openssl_cipher_str_to_algs(Kex, CipherStr, HashStr) -> - Hash = algo_str_to_atom(HashStr), - Cipher = algo_str_to_atom(cipher_name_from_openssl(CipherStr)), - case openssl_is_aead_cipher(CipherStr) of +openssl_cipher_str_to_algs(Kex, Part1, Part2) -> + Hash = algo_str_to_atom(Part2), + Cipher = algo_str_to_atom(cipher_name_from_openssl(string:strip(Part1, left, $-))), + case openssl_is_aead_cipher(Part1) of true -> {Cipher, aead, Hash}; false -> diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_client_session_cache_db.erl index c79ad1523b..d344294231 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_client_session_cache_db.erl @@ -19,38 +19,47 @@ %% %% --module(ssl_session_cache). +-module(ssl_client_session_cache_db). -behaviour(ssl_session_cache_api). -include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). --export([init/1, terminate/1, lookup/2, update/3, delete/2, foldl/3, - select_session/2, size/1]). +-export([init/1, + terminate/1, + lookup/2, + update/3, + delete/2, + foldl/3, + select_session/2, + size/1]). %%-------------------------------------------------------------------- -%% Description: Return table reference. Called by ssl_manager process. +%% Description: Return table reference. Called by ssl_manager process. %%-------------------------------------------------------------------- init(Options) -> ets:new(cache_name(proplists:get_value(role, Options)), [ordered_set, protected]). %%-------------------------------------------------------------------- -%% Description: Handles cache table at termination of ssl manager. +%% Description: Handles cache table at termination of ssl manager. %%-------------------------------------------------------------------- terminate(Cache) -> ets:delete(Cache). %%-------------------------------------------------------------------- -%% Description: Looks up a cach entry. Should be callable from any +%% Description: Looks up a cache entry. Should be callable from any %% process. %%-------------------------------------------------------------------- lookup(Cache, Key) -> - case ets:lookup(Cache, Key) of + try ets:lookup(Cache, Key) of [{Key, Session}] -> Session; [] -> undefined + catch + _:_ -> + undefined end. %%-------------------------------------------------------------------- @@ -61,7 +70,7 @@ update(Cache, Key, Session) -> ets:insert(Cache, {Key, Session}). %%-------------------------------------------------------------------- -%% Description: Delets a cache entry. +%% Description: Deletes a cache entry. %% Will only be called from the ssl_manager process. %%-------------------------------------------------------------------- delete(Cache, Key) -> @@ -75,15 +84,27 @@ delete(Cache, Key) -> %% is empty.Should be callable from any process %%-------------------------------------------------------------------- foldl(Fun, Acc0, Cache) -> - ets:foldl(Fun, Acc0, Cache). - + try ets:foldl(Fun, Acc0, Cache) of + Result -> + Result + catch + _:_ -> + Acc0 + end. + %%-------------------------------------------------------------------- %% Description: Selects a session that could be reused. Should be callable %% from any process. %%-------------------------------------------------------------------- -select_session(Cache, PartialKey) -> - ets:select(Cache, - [{{{PartialKey,'_'}, '$1'},[],['$1']}]). +select_session(Cache, PartialKey) -> + try ets:select(Cache, + [{{{PartialKey,'_'}, '$1'},[],['$1']}]) of + Result -> + Result + catch + _:_ -> + [] + end. %%-------------------------------------------------------------------- %% Description: Returns the cache size diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 10f95d5b3c..2832d76d42 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -26,8 +26,19 @@ -include("ssl_connection.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([init/2]). +-define(DEFAULT_MAX_SESSION_CACHE, 1000). +-export([init/2, + pre_1_3_session_opts/1, + get_max_early_data_size/0, + get_ticket_lifetime/0, + get_ticket_store_size/0, + get_internal_active_n/0 + ]). + +%%==================================================================== +%% Internal application API +%%==================================================================== init(#{erl_dist := ErlDist, key := Key, keyfile := KeyFile, @@ -44,6 +55,50 @@ init(#{erl_dist := ErlDist, DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role), {ok, Config#{private_key => PrivateKey, dh_params => DHParams}}. +pre_1_3_session_opts(Role) -> + {Cb, InitArgs} = session_cb_opts(Role), + CbOpts = #{session_cb => Cb, + session_cb_init_args => InitArgs}, + LifeTime = session_lifetime(Role), + Max = max_session_cache_size(Role), + CbOpts#{lifetime => LifeTime, max => Max}. + +get_ticket_lifetime() -> + case application:get_env(ssl, server_session_ticket_lifetime) of + {ok, Seconds} when is_integer(Seconds) andalso + Seconds =< 604800 -> %% MUST be less than 7 days + Seconds; + _ -> + 7200 %% Default 2 hours + end. + +get_ticket_store_size() -> + case application:get_env(ssl, server_session_ticket_store_size) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + 1000 + end. + +get_max_early_data_size() -> + case application:get_env(ssl, server_session_ticket_max_early_data) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + ?DEFAULT_MAX_EARLY_DATA_SIZE + end. + +get_internal_active_n() -> + case application:get_env(ssl, internal_active_n) of + {ok, N} when is_integer(N) -> + N; + _ -> + ?INTERNAL_ACTIVE_N + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== init_manager_name(false) -> put(ssl_manager, ssl_manager:name(normal)), put(ssl_pem_cache, ssl_pem_cache:name(normal)); @@ -54,7 +109,7 @@ init_manager_name(true) -> init_certificates(#{cacerts := CaCerts, cacertfile := CACertFile, certfile := CertFile, - cert := Cert, + cert := OwnCerts, crl_cache := CRLCache }, Role) -> {ok, Config} = @@ -70,31 +125,31 @@ init_certificates(#{cacerts := CaCerts, _:Reason -> file_error(CACertFile, {cacertfile, Reason}) end, - init_certificates(Cert, Config, CertFile, Role). + init_certificates(OwnCerts, Config, CertFile, Role). init_certificates(undefined, Config, <<>>, _) -> - {ok, Config#{own_certificate => undefined}}; + {ok, Config#{own_certificates => undefined}}; init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) -> try - %% Ignoring potential proxy-certificates see: - %% http://dev.globus.org/wiki/Security/ProxyFileFormat - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config#{own_certificate => OwnCert}} + %% OwnCert | [OwnCert | Chain] + OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), + {ok, Config#{own_certificates => OwnCerts}} catch _Error:_Reason -> - {ok, Config#{own_certificate => undefined}} + {ok, Config#{own_certificates => undefined}} end; init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) -> try - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config#{own_certificate => OwnCert}} + %% OwnCert | [OwnCert | Chain] + OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), + {ok, Config#{own_certificates => OwnCerts}} catch _:Reason -> file_error(CertFile, {certfile, Reason}) end; -init_certificates(Cert, Config, _, _) -> - {ok, Config#{own_certificate => Cert}}. +init_certificates(OwnCerts, Config, _, _) -> + {ok, Config#{own_certificates => OwnCerts}}. init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa; Alg == rsa; Alg == dss -> @@ -176,3 +231,67 @@ init_diffie_hellman(DbHandle,_, DHParamFile, server) -> _:Reason -> file_error(DHParamFile, {dhfile, Reason}) end. + + +session_cb_init_args(client) -> + case application:get_env(ssl, client_session_cb_init_args) of + undefined -> + case application:get_env(ssl, session_cb_init_args) of + {ok, Args} when is_list(Args) -> + Args; + _ -> + [] + end; + {ok, Args} -> + Args + end; +session_cb_init_args(server) -> + case application:get_env(ssl, server_session_cb_init_args) of + undefined -> + case application:get_env(ssl, session_cb_init_args) of + {ok, Args} when is_list(Args) -> + Args; + _ -> + [] + end; + {ok, Args} -> + Args + end. + +session_lifetime(_Role) -> + case application:get_env(ssl, session_lifetime) of + {ok, Time} when is_integer(Time) -> + Time; + _ -> + ?'24H_in_sec' + end. + +max_session_cache_size(client) -> + case application:get_env(ssl, session_cache_client_max) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + ?DEFAULT_MAX_SESSION_CACHE + end; +max_session_cache_size(server) -> + case application:get_env(ssl, session_cache_server_max) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + ?DEFAULT_MAX_SESSION_CACHE + end. + +session_cb_opts(client = Role)-> + case application:get_env(ssl, session_cb, ssl_client_session_cache_db) of + ssl_client_session_cache_db = ClientCb -> + {ClientCb, []}; + ClientCb -> + {ClientCb, session_cb_init_args(Role)} + end; +session_cb_opts(server = Role) -> + case application:get_env(ssl, session_cb, ssl_server_session_cache_db) of + ssl_server_session_cache_db = ServerCb -> + {ServerCb, []}; + ServerCb -> + {ServerCb, session_cb_init_args(Role)} + end. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl deleted file mode 100644 index 8e2e794280..0000000000 --- a/lib/ssl/src/ssl_connection.erl +++ /dev/null @@ -1,3117 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2013-2020. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also -%% tls_connection.erl and dtls_connection.erl -%%---------------------------------------------------------------------- - --module(ssl_connection). - --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"). --include_lib("public_key/include/public_key.hrl"). --include_lib("kernel/include/logger.hrl"). - -%% Setup - --export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5, - handshake_continue/3, handshake_cancel/1, - socket_control/4, socket_control/5]). - -%% User Events --export([send/2, recv/3, close/2, shutdown/2, - new_user/2, get_opts/2, set_opts/2, - peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5, - connection_information/2 - ]). - -%% Alert and close handling --export([handle_own_alert/4, handle_alert/3, - handle_normal_shutdown/3, - handle_trusted_certs_db/1, - maybe_invalidate_session/6]). - -%% Data handling --export([read_application_data/2, internal_renegotiation/2]). - -%% Help functions for tls|dtls_connection.erl --export([handle_session/7, ssl_config/3, - prepare_connection/2, hibernate_after/3]). - -%% General gen_statem state functions with extra callback argument -%% to determine if it is an SSL/TLS or DTLS gen_statem machine --export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, cipher/4, - connection/4, downgrade/4]). - -%% gen_statem callbacks --export([terminate/3, format_status/2]). - -%% Erlang Distribution export --export([dist_handshake_complete/2]). - -%%==================================================================== -%% Setup -%%==================================================================== -%%-------------------------------------------------------------------- --spec connect(tls_connection | dtls_connection, - ssl:host(), inet:port_number(), - port() | {tuple(), port()}, %% TLS | DTLS - {ssl_options(), #socket_options{}, - %% Tracker only needed on server side - undefined}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Connect to an ssl server. -%%-------------------------------------------------------------------- -connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> - try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo, - Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. -%%-------------------------------------------------------------------- --spec handshake(tls_connection | dtls_connection, - inet:port_number(), port(), - {ssl_options(), #socket_options{}, undefined | pid()}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Performs accept on an ssl listen socket. e.i. performs -%% ssl handshake. -%%-------------------------------------------------------------------- -handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> - try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User, - CbInfo, Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | - {ok, #sslsocket{}, map()}| {error, reason()}. -%% -%% Description: Starts ssl handshake. -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> - case call(Pid, {start, Timeout}) of - connected -> - {ok, Socket}; - {ok, Ext} -> - {ok, Socket, no_records(Ext)}; - Error -> - Error - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) -> - {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}. -%% -%% Description: Starts ssl handshake with some new options -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> - case call(Pid, {start, SslOptions, Timeout}) of - connected -> - {ok, Socket}; - Error -> - Error - end. - -%%-------------------------------------------------------------------- --spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()], - timeout()) -> {ok, #sslsocket{}}| {error, reason()}. -%% -%% Description: Continues handshake with new options -%%-------------------------------------------------------------------- -handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> - case call(Pid, {handshake_continue, SslOptions, Timeout}) of - connected -> - {ok, Socket}; - Error -> - Error - end. -%%-------------------------------------------------------------------- --spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}. -%% -%% Description: Cancels connection -%%-------------------------------------------------------------------- -handshake_cancel(#sslsocket{pid = [Pid|_]}) -> - case call(Pid, cancel) of - closed -> - ok; - Error -> - Error - end. -%-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Set the ssl process to own the accept socket -%%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pid, Transport) -> - socket_control(Connection, Socket, Pid, Transport, undefined). - -%-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) -> - {ok, #sslsocket{}} | {error, reason()}. -%%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pids, Transport, udp_listener) -> - %% dtls listener process must have the socket control - {ok, Connection:socket(Pids, Transport, Socket, undefined)}; - -socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; - {error, Reason} -> - {error, Reason} - end; -socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, Trackers) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; - {error, Reason} -> - {error, Reason} - end. - - -%%==================================================================== -%% User events -%%==================================================================== - -%%-------------------------------------------------------------------- --spec send(pid(), iodata()) -> ok | {error, reason()}. -%% -%% Description: Sends data over the ssl connection -%%-------------------------------------------------------------------- -send(Pid, Data) -> - call(Pid, {application_data, - %% iolist_to_iovec should really - %% be called iodata_to_iovec() - erlang:iolist_to_iovec(Data)}). - -%%-------------------------------------------------------------------- --spec recv(pid(), integer(), timeout()) -> - {ok, binary() | list()} | {error, reason()}. -%% -%% Description: Receives data when active = false -%%-------------------------------------------------------------------- -recv(Pid, Length, Timeout) -> - call(Pid, {recv, Length, Timeout}). - -%%-------------------------------------------------------------------- --spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Get the SNI hostname -%%-------------------------------------------------------------------- -connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) -> - call(Pid, {connection_information, IncludeSecrityInfo}). - -%%-------------------------------------------------------------------- --spec close(pid(), {close, Timeout::integer() | - {NewController::pid(), Timeout::integer()}}) -> - ok | {ok, port()} | {error, reason()}. -%% -%% Description: Close an ssl connection -%%-------------------------------------------------------------------- -close(ConnectionPid, How) -> - case call(ConnectionPid, How) of - {error, closed} -> - ok; - Other -> - Other - end. -%%-------------------------------------------------------------------- --spec shutdown(pid(), atom()) -> ok | {error, reason()}. -%% -%% Description: Same as gen_tcp:shutdown/2 -%%-------------------------------------------------------------------- -shutdown(ConnectionPid, How) -> - call(ConnectionPid, {shutdown, How}). - -%%-------------------------------------------------------------------- --spec new_user(pid(), pid()) -> ok | {error, reason()}. -%% -%% Description: Changes process that receives the messages when active = true -%% or once. -%%-------------------------------------------------------------------- -new_user(ConnectionPid, User) -> - call(ConnectionPid, {new_user, User}). - -%%-------------------------------------------------------------------- --spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the negotiated protocol -%%-------------------------------------------------------------------- -negotiated_protocol(ConnectionPid) -> - call(ConnectionPid, negotiated_protocol). - -%%-------------------------------------------------------------------- --spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Same as inet:getopts/2 -%%-------------------------------------------------------------------- -get_opts(ConnectionPid, OptTags) -> - call(ConnectionPid, {get_opts, OptTags}). -%%-------------------------------------------------------------------- --spec set_opts(pid(), list()) -> ok | {error, reason()}. -%% -%% Description: Same as inet:setopts/2 -%%-------------------------------------------------------------------- -set_opts(ConnectionPid, Options) -> - call(ConnectionPid, {set_opts, Options}). - -%%-------------------------------------------------------------------- --spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. -%% -%% Description: Returns the peer cert -%%-------------------------------------------------------------------- -peer_certificate(ConnectionPid) -> - call(ConnectionPid, peer_certificate). - -%%-------------------------------------------------------------------- --spec renegotiation(pid()) -> ok | {error, reason()}. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -renegotiation(ConnectionPid) -> - call(ConnectionPid, renegotiate). - -%%-------------------------------------------------------------------- --spec internal_renegotiation(pid(), ssl_record:connection_states()) -> - ok. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -internal_renegotiation(ConnectionPid, #{current_write := WriteState}) -> - gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}). - -dist_handshake_complete(ConnectionPid, DHandle) -> - gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}). - -%%-------------------------------------------------------------------- --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) -> - call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). - - -%%==================================================================== -%% Alert and close handling -%%==================================================================== -handle_own_alert(Alert0, _, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - ssl_options = #{log_level := LogLevel}} = State) -> - try %% Try to tell the other side - send_alert(Alert0, StateName, State) - catch _:_ -> %% Can crash if we are in a uninitialized state - ignore - end, - try %% Try to tell the local user - Alert = Alert0#alert{role = Role}, - log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), - handle_normal_shutdown(Alert,StateName, State) - catch _:_ -> - ok - end, - {stop, {shutdown, own_alert}, State}. - -handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, - socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, - trackers = Trackers}, - handshake_env = #handshake_env{renegotiation = {false, first}}, - start_or_recv_from = StartFrom} = State) -> - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection); - -handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, - socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, - trackers = Trackers}, - connection_env = #connection_env{user_application = {_Mon, Pid}}, - socket_options = Opts, - start_or_recv_from = RecvFrom} = State) -> - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection). - -handle_alert(#alert{level = ?FATAL} = Alert0, StateName, - #state{static_env = #static_env{role = Role, - socket = Socket, - host = Host, - port = Port, - trackers = Trackers, - transport_cb = Transport, - protocol_cb = Connection}, - connection_env = #connection_env{user_application = {_Mon, Pid}}, - ssl_options = #{log_level := LogLevel}, - start_or_recv_from = From, - session = Session, - socket_options = Opts} = State) -> - invalidate_session(Role, Host, Port, Session), - Alert = Alert0#alert{role = opposite_role(Role)}, - log_alert(LogLevel, Role, Connection:protocol_name(), - StateName, Alert), - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection), - {stop, {shutdown, normal}, State}; - -handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - downgrade= StateName, State) -> - {next_state, StateName, State, [{next_event, internal, Alert}]}; -handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0, - StateName, #state{static_env = #static_env{role = Role}} = State) -> - Alert = Alert0#alert{role = opposite_role(Role)}, - handle_normal_shutdown(Alert, StateName, State), - {stop,{shutdown, peer_close}, State}; -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, internal}}, - ssl_options = #{log_level := LogLevel}} = State) -> - Alert = Alert0#alert{role = opposite_role(Role)}, - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, Alert), - handle_normal_shutdown(Alert, StateName, State), - {stop,{shutdown, peer_close}, State}; - -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, - ssl_options = #{log_level := LogLevel} - } = State0) -> - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), - gen_statem:reply(From, {error, renegotiation_rejected}), - State = Connection:reinit_handshake_data(State0), - Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}); - -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, - ssl_options = #{log_level := LogLevel} - } = State0) -> - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), - gen_statem:reply(From, {error, renegotiation_rejected}), - %% Go back to connection! - State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}), - Connection:next_event(connection, no_record, State); - -%% Gracefully log and ignore all other warning alerts -handle_alert(#alert{level = ?WARNING} = Alert, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - ssl_options = #{log_level := LogLevel}} = State) -> - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, - Alert#alert{role = opposite_role(Role)}), - Connection:next_event(StateName, no_record, State). - -maybe_invalidate_session(undefined,_, _, _, _, _) -> - ok; -maybe_invalidate_session({3, 4},_, _, _, _, _) -> - ok; -maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 -> - maybe_invalidate_session(Type, Role, Host, Port, Session). - -%%==================================================================== -%% Data handling -%%==================================================================== -passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, - %% Assert! Erl distribution uses active sockets - connection_env = #connection_env{erl_dist_handle = undefined}} - = State0, StateName, Connection, StartTimerAction) -> - case BufferSize of - 0 -> - Connection:next_event(StateName, no_record, State0, StartTimerAction); - _ -> - case read_application_data(State0, Front, BufferSize, Rear) of - {stop, _, _} = ShutdownError -> - ShutdownError; - {Record, State} -> - case State#state.start_or_recv_from of - undefined -> - %% Cancel recv timeout as data has been delivered - Connection:next_event(StateName, Record, State, - [{{timeout, recv}, infinity, timeout}]); - _ -> - Connection:next_event(StateName, Record, State, StartTimerAction) - end - end - end. - -read_application_data( - Data, - #state{ - user_data_buffer = {Front0,BufferSize0,Rear0}, - connection_env = #connection_env{erl_dist_handle = DHandle}} = State) -> - %% - Front = Front0, - BufferSize = BufferSize0 + byte_size(Data), - Rear = [Data|Rear0], - case DHandle of - undefined -> - read_application_data(State, Front, BufferSize, Rear); - _ -> - try read_application_dist_data(DHandle, Front, BufferSize, Rear) of - Buffer -> - {no_record, State#state{user_data_buffer = Buffer}} - catch error:_ -> - {stop,disconnect, - State#state{user_data_buffer = {Front,BufferSize,Rear}}} - end - end. - - -read_application_data(#state{ - socket_options = SocketOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) -> - read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead). - -%% Pick binary from queue front, if empty wait for more data -read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> - read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin); -read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) -> - 0 = BufferSize, % Assert - {no_record, State#state{socket_options = SocketOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {Front,BufferSize,Rear}}}; -read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> - [Bin|Front] = lists:reverse(Rear), - read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin). - -read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) -> - %% Done with this binary - get next - read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead); -read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) -> - %% Decode one packet from a binary - case get_data(SocketOpts0, BytesToRead, Bin0) of - {ok, Data, Bin} -> % Send data - BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)), - read_application_data_deliver( - State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data); - {more, undefined} -> - %% We need more data, do not know how much - if - byte_size(Bin0) < BufferSize0 -> - %% We have more data in the buffer besides the first binary - concatenate all and retry - Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), - read_application_data_bin( - State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin); - true -> - %% All data is in the first binary, no use to retry - wait for more - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}} - end; - {more, Size} when Size =< BufferSize0 -> - %% We have a packet in the buffer - collect it in a binary and decode - {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]), - Bin = iolist_to_binary(Data), - read_application_data_bin( - State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin); - {more, _Size} -> - %% We do not have a packet in the buffer - wait for more - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; - passive -> - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; - {error,_Reason} -> - %% Invalid packet in packet mode - #state{ - static_env = - #static_env{ - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}} = State, - Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), - deliver_packet_error( - Connection:pids(State), Transport, Socket, SocketOpts0, - Buffer, Pid, RecvFrom, Trackers, Connection), - {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Buffer],BufferSize0,[]}}} - end. - -read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) -> - #state{ - static_env = - #static_env{ - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}} = State, - SocketOpts = - deliver_app_data( - Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection), - if - SocketOpts#socket_options.active =:= false -> - %% Passive mode, wait for active once or recv - {no_record, - State#state{ - user_data_buffer = {Front,BufferSize,Rear}, - start_or_recv_from = undefined, - bytes_to_read = undefined, - socket_options = SocketOpts - }}; - true -> %% Try to deliver more data - read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined) - end. - - -read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) -> - read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin); -read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) -> - BufferSize = 0, - {Front,BufferSize,Rear}; -read_application_dist_data(DHandle, [], BufferSize, Rear) -> - [Bin|Front] = lists:reverse(Rear), - read_application_dist_data(DHandle, Front, BufferSize, [], Bin). -%% -read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> - case Bin0 of - %% - %% START Optimization - %% It is cheaper to match out several packets in one match operation than to loop for each - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, - SizeC:32, DataC:SizeC/binary, - SizeD:32, DataD:SizeD/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD -> - %% We have 4 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - erlang:dist_ctrl_put_data(DHandle, DataC), - erlang:dist_ctrl_put_data(DHandle, DataD), - read_application_dist_data( - DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest); - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, - SizeC:32, DataC:SizeC/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB, 0 < SizeC -> - %% We have 3 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - erlang:dist_ctrl_put_data(DHandle, DataC), - read_application_dist_data( - DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest); - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB -> - %% We have 2 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - read_application_dist_data( - DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest); - %% END Optimization - %% - %% Basic one packet code path - <<Size:32, Data:Size/binary, Rest/binary>> -> - %% We have a complete packet in the first binary - 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), - read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest); - <<Size:32, FirstData/binary>> when 4+Size =< BufferSize -> - %% We have a complete packet in the buffer - %% - fetch the missing content from the buffer front - {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]), - 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), - read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear); - <<Bin/binary>> -> - %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we - %% match out the whole binary which will trick the optimization into keeping the match context - %% for the first binary contains complete packet code above - case Bin of - <<_Size:32, _InsufficientData/binary>> -> - %% We have a length field in the first binary but there is not enough data - %% in the buffer to form a complete packet - await more data - {[Bin|Front0],BufferSize,Rear0}; - <<IncompleteLengthField/binary>> when 4 < BufferSize -> - %% We do not have a length field in the first binary but the buffer - %% contains enough data to maybe form a packet - %% - fetch a tiny binary from the buffer front to complete the length field - {LengthField,Front,Rear} = - case IncompleteLengthField of - <<>> -> - iovec_from_front(4, Front0, Rear0, []); - _ -> - iovec_from_front( - 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]) - end, - LengthBin = iolist_to_binary(LengthField), - read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin); - <<IncompleteLengthField/binary>> -> - %% We do not have enough data in the buffer to even form a length field - await more data - case IncompleteLengthField of - <<>> -> - {Front0,BufferSize,Rear0}; - _ -> - {[IncompleteLengthField|Front0],BufferSize,Rear0} - end - end - end. - -iovec_from_front(0, Front, Rear, Acc) -> - {lists:reverse(Acc),Front,Rear}; -iovec_from_front(Size, [], Rear, Acc) -> - case Rear of - %% Avoid lists:reverse/1 for simple cases. - %% Case clause for [] to avoid infinite loop. - [_] -> - iovec_from_front(Size, Rear, [], Acc); - [Bin2,Bin1] -> - iovec_from_front(Size, [Bin1,Bin2], [], Acc); - [Bin3,Bin2,Bin1] -> - iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc); - [_,_,_|_] = Rear -> - iovec_from_front(Size, lists:reverse(Rear), [], Acc) - end; -iovec_from_front(Size, [Bin|Front], Rear, []) -> - case Bin of - <<Last:Size/binary>> -> % Just enough - {[Last],Front,Rear}; - <<Last:Size/binary, Rest/binary>> -> % More than enough, split here - {[Last],[Rest|Front],Rear}; - <<>> -> % Not enough, skip empty binaries - iovec_from_front(Size, Front, Rear, []); - <<_/binary>> -> % Not enough - BinSize = byte_size(Bin), - iovec_from_front(Size - BinSize, Front, Rear, [Bin]) - end; -iovec_from_front(Size, [Bin|Front], Rear, Acc) -> - case Bin of - <<Last:Size/binary>> -> % Just enough - {lists:reverse(Acc, [Last]),Front,Rear}; - <<Last:Size/binary, Rest/binary>> -> % More than enough, split here - {lists:reverse(Acc, [Last]),[Rest|Front],Rear}; - <<>> -> % Not enough, skip empty binaries - iovec_from_front(Size, Front, Rear, Acc); - <<_/binary>> -> % Not enough - BinSize = byte_size(Bin), - iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc]) - end. - - -%%==================================================================== -%% 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{session_id = OldId}, - 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(OldId, 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. - -%%-------------------------------------------------------------------- --spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}. -%%-------------------------------------------------------------------- -ssl_config(Opts, Role, #state{static_env = InitStatEnv0, - handshake_env = HsEnv, - connection_env = CEnv} = State0) -> - {ok, #{cert_db_ref := Ref, - cert_db_handle := CertDbHandle, - fileref_db_handle := FileRefHandle, - session_cache := CacheHandle, - crl_db_info := CRLDbHandle, - private_key := Key, - dh_params := DHParams, - own_certificate := OwnCert}} = - ssl_config:init(Opts, Role), - TimeStamp = erlang:monotonic_time(), - Session = State0#state.session, - - State0#state{session = Session#session{own_certificate = OwnCert, - time_stamp = TimeStamp}, - static_env = InitStatEnv0#static_env{ - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle - }, - handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams}, - connection_env = CEnv#connection_env{private_key = Key}, - ssl_options = Opts}. - -%%==================================================================== -%% gen_statem general state functions with connection cb argument -%%==================================================================== -%%-------------------------------------------------------------------- --spec init(gen_statem:event_type(), - {start, timeout()} | {start, {list(), list()}, timeout()}| term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- - -init({call, From}, {start, Timeout}, State0, Connection) -> - Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From}, - [{{timeout, handshake}, Timeout, close}]); -init({call, From}, {start, {Opts, EmOpts}, Timeout}, - #state{static_env = #static_env{role = Role}, - ssl_options = OrigSSLOptions, - socket_options = SockOpts} = State0, Connection) -> - try - SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions), - State = ssl_config(SslOpts, Role, State0), - init({call, From}, {start, Timeout}, - State#state{ssl_options = SslOpts, - socket_options = new_emulated(EmOpts, SockOpts)}, Connection) - catch throw:Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} - end; -init({call, From}, {new_user, _} = Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -init({call, From}, _Msg, _State, _Connection) -> - {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]}; -init(_Type, _Event, _State, _Connection) -> - {keep_state_and_data, [postpone]}. - -%%-------------------------------------------------------------------- --spec error(gen_statem:event_type(), - {start, timeout()} | term(), #state{}, - tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -error({call, From}, {close, _}, State, _Connection) -> - {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State}; -error({call, From}, _Msg, State, _Connection) -> - {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}. - -%%-------------------------------------------------------------------- --spec hello(gen_statem:event_type(), - #hello_request{} | #server_hello{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -hello({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) -> - do_server_hello(Type, ServerHelloExt, State, Connection); -hello(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -hello(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) -> - gen_statem:reply(From, ok), - handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), - Version, ?FUNCTION_NAME, State); -user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, - #state{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{hello = Hello}, - ssl_options = Options0} = State0, _Connection) -> - Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), - State = ssl_config(Options, Role, State0), - {next_state, hello, State#state{start_or_recv_from = From}, - [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; -user_hello(_, _, _, _) -> - {keep_state_and_data, [postpone]}. - -%%-------------------------------------------------------------------- --spec abbreviated(gen_statem:event_type(), - #hello_request{} | #finished{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -abbreviated({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - expecting_finished = true} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = - State0, Connection) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client, - get_current_prf(ConnectionStates0, write), - MasterSecret, Hist) of - verified -> - ConnectionStates = - ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), - {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{tls_handshake_history = Hist0}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = State0, Connection) -> - 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} = prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State, - Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, - expecting_next_protocol_negotiation = false}}); -abbreviated(internal, - #change_cipher_spec{type = <<1>>}, - #state{connection_states = ConnectionStates0, - handshake_env = HsEnv} = State, Connection) -> - 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(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec certify(gen_statem:event_type(), - #hello_request{} | #certificate{} | #server_key_exchange{} | - #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -certify({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -certify(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -certify(internal, #certificate{asn1_certificates = []}, - #state{static_env = #static_env{role = server}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_peer, - fail_if_no_peer_cert := true}} = - State, _) -> - Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided), - handle_own_alert(Alert, Version, ?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 := false}} = - State0, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); -certify(internal, #certificate{}, - #state{static_env = #static_env{role = server}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_none}} = - State, _) -> - Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); -certify(internal, #certificate{} = Cert, - #state{static_env = #static_env{ - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbInfo}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = Opts} = State, Connection) -> - case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, - Opts, CRLDbInfo, Role, Host) of - {PeerCert, PublicKeyInfo} -> - handle_peer_cert(Role, PeerCert, PublicKeyInfo, - State#state{client_certificate_requested = false}, Connection); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -certify(internal, #server_key_exchange{exchange_keys = Keys}, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - public_key_info = PubKeyInfo} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates} = State, Connection) - 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 otherwhise 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 -> - handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), - Version, ?FUNCTION_NAME, State) - end - end; -certify(internal, #certificate_request{}, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg}, - connection_env = #connection_env{negotiated_version = Version}} = State, _) - 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 -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), - Version, ?FUNCTION_NAME, State); -certify(internal, #certificate_request{}, - #state{static_env = #static_env{role = client}, - session = #session{own_certificate = undefined}} = State, Connection) -> - %% The client does not have a certificate and will send an empty reply, the server may fail - %% or accept the connection by its own preference. No signature algorihms needed as there is - %% no certificate to verify. - Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); -certify(internal, #certificate_request{} = CertRequest, - #state{static_env = #static_env{role = client}, - handshake_env = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{own_certificate = Cert}, - ssl_options = #{signature_algs := SupportedHashSigns}} = State, Connection) -> - case ssl_handshake:select_hashsign(CertRequest, Cert, - SupportedHashSigns, ssl:tls_version(Version)) of - #alert {} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); - NegotiatedHashSign -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{client_certificate_requested = true, - handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}}) - end; -%% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - session = #session{master_secret = undefined}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - premaster_secret = undefined, - server_psk_identity = PSKIdentity} = HsEnv, - ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection) - when KexAlg == psk -> - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - 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}, - connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - premaster_secret = undefined, - server_psk_identity = PSKIdentity} = HsEnv, - session = #session{master_secret = undefined}, - ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection) - when KexAlg == rsa_psk -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup, - RSAPremasterSecret) of - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - 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}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{premaster_secret = undefined}, - session = #session{master_secret = MasterSecret} = Session, - connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), Session, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - State = State0#state{connection_states = ConnectionStates}, - client_certify_and_key_exchange(State, Connection); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -%% Master secret is calculated from premaster_secret -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{premaster_secret = PremasterSecret}, - session = Session0, - connection_states = ConnectionStates0} = State0, Connection) -> - 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 -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -certify(internal = Type, #client_key_exchange{} = Msg, - #state{static_env = #static_env{role = server}, - client_certificate_requested = true, - ssl_options = #{fail_if_no_peer_cert := true}} = State, - Connection) -> - %% We expect a certificate here - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection); -certify(internal, #client_key_exchange{exchange_keys = Keys}, - State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg}, - connection_env = #connection_env{negotiated_version = Version}}, Connection) -> - try - certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)), - State, Connection) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -certify(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec cipher(gen_statem:event_type(), - #hello_request{} | #certificate_verify{} | #finished{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -cipher({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -cipher(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -cipher(internal, #certificate_verify{signature = Signature, - hashsign_algorithm = CertHashSign}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - kex_algorithm = KexAlg, - public_key_info = PubKeyInfo} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} - } = State, Connection) -> - - TLSVersion = ssl:tls_version(Version), - %% Use negotiated value if TLS-1.2 otherwhise 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(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -%% client must send a next protocol message if we are expecting it -cipher(internal, #finished{}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_next_protocol_negotiation = true, - negotiated_protocol = undefined}, - connection_env = #connection_env{negotiated_version = Version}} = State0, - _Connection) -> - handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); -cipher(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = Role, - host = Host, - port = Port}, - 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, Connection) -> - 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, Session0), - cipher_role(Role, Data, Session, - State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_finished = true, - expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, - expecting_next_protocol_negotiation = false}}); -cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} = - State, Connection) -> - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true}, - connection_states = ConnectionStates}); -cipher(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec connection(gen_statem:event_type(), term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -connection({call, RecvFrom}, {recv, N, Timeout}, - #state{static_env = #static_env{protocol_cb = Connection}, - socket_options = - #socket_options{active = false}} = State0, Connection) -> - passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, - [{{timeout, recv}, Timeout, timeout}]); - -connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}, - handshake_env = HsEnv} = State, - Connection) -> - Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []); -connection({call, From}, peer_certificate, - #state{session = #session{peer_certificate = Cert}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]); -connection({call, From}, {connection_information, true}, State, _) -> - Info = connection_info(State) ++ security_info(State), - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); -connection({call, From}, {connection_information, false}, State, _) -> - Info = connection_info(State), - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = undefined, - negotiated_protocol = undefined}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = undefined, - negotiated_protocol = SelectedProtocol}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, - [{reply, From, {ok, SelectedProtocol}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = SelectedProtocol, - negotiated_protocol = undefined}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, - [{reply, From, {ok, SelectedProtocol}}]); -connection({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection}, - handshake_env = HsEnv, - connection_states = ConnectionStates} - = State, Connection) -> - Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, - connection_states = ConnectionStates#{current_write => WriteState}}, []); -connection(cast, {dist_handshake_complete, DHandle}, - #state{ssl_options = #{erl_dist := true}, - connection_env = CEnv, - socket_options = SockOpts} = State0, Connection) -> - process_flag(priority, normal), - State1 = - State0#state{ - socket_options = SockOpts#socket_options{active = true}, - connection_env = CEnv#connection_env{erl_dist_handle = DHandle}, - bytes_to_read = undefined}, - {Record, State} = read_application_data(<<>>, State1), - Connection:next_event(connection, Record, State); -connection(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom} = State, Connection) -> - passive_receive(State, ?FUNCTION_NAME, Connection, []); -connection(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec downgrade(gen_statem:event_type(), term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -downgrade(Type, Event, State, Connection) -> - handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- -%% Event handling functions called by state functions to handle -%% common or unexpected events for the state. -%%-------------------------------------------------------------------- -handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, - #state{static_env = #static_env{role = client}, - handshake_env = HsEnv} = State, _) -> - %% Should not be included in handshake history - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}}, - [{next_event, internal, Handshake}]}; -handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, - #state{static_env = #static_env{role = client}}, _) - when StateName =/= connection -> - keep_state_and_data; -handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, - #state{handshake_env = #handshake_env{tls_handshake_history = Hist0}, - connection_env = #connection_env{negotiated_version = Version}} = State0, - Connection) -> - - PossibleSNI = Connection:select_sni_extension(Handshake), - %% This function handles client SNI hello extension when Handshake is - %% a client_hello, which needs to be determined by the connection callback. - %% In other cases this is a noop - case handle_sni_extension(PossibleSNI, State0) of - #state{handshake_env = HsEnv} = State -> - Hist = ssl_handshake:update_handshake_history(Hist0, Raw), - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}, - [{next_event, internal, Handshake}]}; - #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0) - end; -handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) -> - Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); -handle_common_event(timeout, hibernate, _, _, _) -> - {keep_state_and_data, [hibernate]}; -handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, - #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, - StateName, State); -handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State, _) -> - {stop_and_reply, - {shutdown, user_timeout}, - {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}}; -handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State, _) -> - {next_state, StateName, State#state{start_or_recv_from = undefined, - bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]}; -handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}, _) when - StateName =/= connection -> - {keep_state_and_data, [postpone]}; -handle_common_event(Type, Msg, StateName, #state{connection_env = - #connection_env{negotiated_version = Version}} = State, - _) -> - Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}), - handle_own_alert(Alert, Version, StateName, State). - -handle_call({application_data, _Data}, _, _, _, _) -> - %% In renegotiation priorities handshake, send data when handshake is finished - {keep_state_and_data, [postpone]}; -handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State, _Connection) -> - %% Run terminate before returning so that the reuseaddr - %% inet-option works properly - Result = terminate(Close, StateName, State), - {stop_and_reply, - {shutdown, normal}, - {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}}; -handle_call({shutdown, read_write = How}, From, StateName, - #state{static_env = #static_env{transport_cb = Transport, - socket = Socket}, - connection_env = CEnv} = State, _) -> - try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - StateName, State) of - _ -> - case Transport:shutdown(Socket, How) of - ok -> - {next_state, StateName, State#state{connection_env = - CEnv#connection_env{terminated = true}}, - [{reply, From, ok}]}; - Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, Error}, - State#state{connection_env = CEnv#connection_env{terminated = true}}} - end - catch - throw:Return -> - Return - end; -handle_call({shutdown, How0}, From, StateName, - #state{static_env = #static_env{transport_cb = Transport, - socket = Socket}} = State, _) -> - case Transport:shutdown(Socket, How0) of - ok -> - {next_state, StateName, State, [{reply, From, ok}]}; - Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State} - end; -handle_call({recv, _N, _Timeout}, From, _, - #state{socket_options = - #socket_options{active = Active}}, _) when Active =/= false -> - {keep_state_and_data, [{reply, From, {error, einval}}]}; -handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) -> - %% Doing renegotiate wait with handling request until renegotiate is - %% finished. - {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom}, - [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]}; -handle_call({new_user, User}, From, StateName, - State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) -> - NewMon = erlang:monitor(process, User), - erlang:demonitor(OldMon, [flush]), - {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}}, - [{reply, From, ok}]}; -handle_call({get_opts, OptTags}, From, _, - #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - socket_options = SockOpts}, Connection) -> - OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), - {keep_state_and_data, [{reply, From, OptsReply}]}; -handle_call({set_opts, Opts0}, From, StateName, - #state{static_env = #static_env{socket = Socket, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}, - socket_options = Opts1 - } = State0, Connection) -> - {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), - case {proplists:lookup(active, Opts0), Opts} of - {{_, N}, #socket_options{active=false}} when is_integer(N) -> - send_user( - Pid, - format_passive( - Connection:pids(State0), Transport, Socket, Trackers, Connection)); - _ -> - ok - end, - State = State0#state{socket_options = Opts}, - handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); - -handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection -> - {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; - -handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, - #state{connection_states = ConnectionStates, - connection_env = #connection_env{negotiated_version = Version}}, _) -> - #{security_parameters := SecParams} = - ssl_record:current_connection_state(ConnectionStates, read), - #security_parameters{master_secret = MasterSecret, - client_random = ClientRandom, - server_random = ServerRandom, - prf_algorithm = PRFAlgorithm} = SecParams, - Reply = try - SecretToUse = case Secret of - _ when is_binary(Secret) -> Secret; - master_secret -> MasterSecret - end, - SeedToUse = lists:reverse( - lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; - (client_random, Acc) -> [ClientRandom|Acc]; - (server_random, Acc) -> [ServerRandom|Acc] - end, [], Seed)), - ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) - catch - exit:_ -> {error, badarg}; - error:Reason -> {error, Reason} - end, - {keep_state_and_data, [{reply, From, Reply}]}; -handle_call(_,_,_,_,_) -> - {keep_state_and_data, [postpone]}. - -handle_info({ErrorTag, Socket, econnaborted}, StateName, - #state{static_env = #static_env{role = Role, - host = Host, - port = Port, - socket = Socket, - transport_cb = Transport, - error_tag = ErrorTag, - trackers = Trackers, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = Type}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - start_or_recv_from = StartFrom - } = State) when StateName =/= connection -> - - maybe_invalidate_session(Version, Type, Role, Host, Port, Session), - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers,Socket, - StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection), - {stop, {shutdown, normal}, State}; - -handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{ - role = Role, - socket = Socket, - error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(info, Level, #{description => "Socket error", - reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION), - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}), - handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown,normal}, State}; - -handle_info({'DOWN', MonitorRef, _, _, Reason}, _, - #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}, - ssl_options = #{erl_dist := true}}) -> - {stop, {shutdown, Reason}}; -handle_info({'DOWN', MonitorRef, _, _, _}, _, - #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) -> - {stop, {shutdown, normal}}; -handle_info({'EXIT', Pid, _Reason}, StateName, - #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) -> - %% It seems the user application has linked to us - %% - ignore that and let the monitor handle this - {next_state, StateName, State}; -%%% So that terminate will be run when supervisor issues shutdown -handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> - {stop, shutdown, State}; -handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> - %% Handle as transport close" - {stop,{shutdown, transport_closed}, State}; -handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> - {stop,{shutdown, Reason}, State}; - -handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}}; - -handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(notice, Level, #{description => "Unexpected INFO message", - reason => [{message, Msg}, {socket, Socket}, - {error_tag, ErrorTag}]}, ?LOCATION), - {next_state, StateName, State}. - -%%==================================================================== -%% general gen_statem callbacks -%%==================================================================== -terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) -> - %% Happens when user closes the connection using ssl:close/1 - %% we want to guarantee that Transport:close has been called - %% when ssl:close/1 returns unless it is a downgrade where - %% we want to guarantee that close alert is received before - %% returning. In both cases terminate has been run manually - %% before run by gen_statem which will end up here - ok; -terminate({shutdown, transport_closed} = Reason, - _StateName, #state{static_env = #static_env{protocol_cb = Connection, - socket = Socket, - transport_cb = Transport}} = State) -> - handle_trusted_certs_db(State), - Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate({shutdown, own_alert}, _StateName, #state{ - static_env = #static_env{protocol_cb = Connection, - socket = Socket, - transport_cb = Transport}} = State) -> - handle_trusted_certs_db(State), - case application:get_env(ssl, alert_timeout) of - {ok, Timeout} when is_integer(Timeout) -> - Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); - _ -> - Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) - end; -terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection, - transport_cb = Transport, - socket = Socket} - } = State) -> - handle_trusted_certs_db(State), - Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate(Reason, connection, #state{static_env = #static_env{ - protocol_cb = Connection, - transport_cb = Transport, - socket = Socket}, - connection_states = ConnectionStates, - ssl_options = #{padding_check := Check} - } = State) -> - handle_trusted_certs_db(State), - Alert = terminate_alert(Reason), - %% Send the termination ALERT if possible - catch (ok = Connection:send_alert_in_connection(Alert, State)), - Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); -terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, - protocol_cb = Connection, - socket = Socket} - } = State) -> - handle_trusted_certs_db(State), - Connection:close(Reason, Socket, Transport, undefined, undefined). - -format_status(normal, [_, StateName, State]) -> - [{data, [{"State", {StateName, State}}]}]; -format_status(terminate, [_, StateName, State]) -> - SslOptions = (State#state.ssl_options), - NewOptions = SslOptions#{password => ?SECRET_PRINTOUT, - cert => ?SECRET_PRINTOUT, - cacerts => ?SECRET_PRINTOUT, - key => ?SECRET_PRINTOUT, - dh => ?SECRET_PRINTOUT, - psk_identity => ?SECRET_PRINTOUT, - srp_identity => ?SECRET_PRINTOUT}, - [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, - protocol_buffers = ?SECRET_PRINTOUT, - user_data_buffer = ?SECRET_PRINTOUT, - handshake_env = ?SECRET_PRINTOUT, - connection_env = ?SECRET_PRINTOUT, - session = ?SECRET_PRINTOUT, - ssl_options = NewOptions, - flight_buffer = ?SECRET_PRINTOUT} - }}]}]. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> - Connection:send_alert_in_connection(Alert, State); -send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> - Connection:send_alert(Alert, State). - -connection_info(#state{static_env = #static_env{protocol_cb = Connection}, - handshake_env = #handshake_env{sni_hostname = SNIHostname, - resumption = Resumption}, - session = #session{session_id = SessionId, - cipher_suite = CipherSuite, ecc = ECCCurve}, - connection_env = #connection_env{negotiated_version = {_,_} = Version}, - ssl_options = Opts}) -> - RecordCB = record_cb(Connection), - CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - IsNamedCurveSuite = lists:member(KexAlg, - [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]), - CurveInfo = case ECCCurve of - {namedCurve, Curve} when IsNamedCurveSuite -> - [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}]; - _ -> - [] - end, - [{protocol, RecordCB:protocol_version(Version)}, - {session_id, SessionId}, - {session_resumption, Resumption}, - {cipher_suite, ssl_cipher_format:suite_legacy(CipherSuiteDef)}, - {selected_cipher_suite, CipherSuiteDef}, - {sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts). - -security_info(#state{connection_states = ConnectionStates}) -> - #{security_parameters := - #security_parameters{client_random = ClientRand, - server_random = ServerRand, - master_secret = MasterSecret}} = - ssl_record:current_connection_state(ConnectionStates, read), - [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]. - -do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} = - ServerHelloExt, - #state{connection_env = #connection_env{negotiated_version = Version}, - handshake_env = HsEnv, - session = #session{session_id = SessId}, - connection_states = ConnectionStates0, - ssl_options = #{versions := [HighestVersion|_]}} - = State0, Connection) 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(<<Random0:24/binary,_:8/binary>> = 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 -> %% Negotating TLS 1.2 - Down = ?RANDOM_OVERRIDE_TLS12, - <<Random0/binary,Down/binary>>; - M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <<Random0/binary,Down/binary>>; - true -> - Random - end; -override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) - when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 - if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <<Random0/binary,Down/binary>>; - 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, - connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> - try server_certify_and_key_exchange(State0, Connection) of - #state{} = State1 -> - {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) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - -resumed_server_hello(#state{session = Session, - connection_states = ConnectionStates0, - 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 -> - handle_own_alert(Alert, Version, hello, State0) - 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, - session = #session{cipher_suite = CipherSuite} = Session} = State0, - Connection) -> - 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). - -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_requested = true, - session = #session{own_certificate = OwnCert}} - = State, Connection) -> - Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), - Connection:queue_handshake(Certificate, State); -certify_client(#state{client_certificate_requested = false} = State, _) -> - State. - -verify_client_cert(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - cert_hashsign_algorithm = HashSign}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - client_certificate_requested = true, - session = #session{master_secret = MasterSecret, - own_certificate = OwnCert}} = State, Connection) -> - - case ssl_handshake:client_certificate_verify(OwnCert, 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_requested = false} = State, _) -> - State. - -client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} = - State0, Connection) -> - try do_client_certify_and_key_exchange(State0, Connection) of - State1 = #state{} -> - {State2, Actions} = finalize_handshake(State1, certify, Connection), - State = State2#state{ - %% Reinitialize - client_certificate_requested = false}, - Connection:next_event(cipher, no_record, State, Actions) - catch - throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. - -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{connection_env = #connection_env{private_key = Key}, - handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}} - = State, Connection) -> - FakeSecret = make_premaster_secret(Version, rsa), - %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret - %% and fail handshake later.RFC 5246 section 7.4.7.1. - PremasterSecret = - try ssl_handshake:premaster_secret(EncPMS, Key) of - Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> - case Secret of - <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct - <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>; - <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch - <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> - end; - _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES - FakeSecret - catch - #alert{description = ?DECRYPT_ERROR} -> - FakeSecret - end, - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); -certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, - #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}} - } = State, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); - -certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); -certify_client_key_exchange(#client_psk_identity{} = ClientKey, - #state{ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); -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}} = State0, - Connection) -> - PremasterSecret = - ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); -certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, - #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, - ssl_options = - #{user_lookup_fun := PSKLookup}} = State, - Connection) -> - PremasterSecret = - ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); -certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, - #state{connection_env = #connection_env{private_key = Key}, - ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); -certify_client_key_exchange(#client_srp_public{} = ClientKey, - #state{handshake_env = #handshake_env{srp_params = Params, - kex_keys = Key} - } = State0, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), - calculate_master_secret(PremasterSecret, State0, Connection, certify, 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_certificate = OwnCert}} = State, Connection) -> - case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of - Cert = #certificate{} -> - Connection:queue_handshake(Cert, State); - Alert = #alert{} -> - throw(Alert) - end. - -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, - 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, - connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key}, - session = 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, - private_key = PrivateKey}, - session = #session{ecc = ECCCurve}, - 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, - 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, - 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, - private_key = PrivateKey}, - session = #session{ecc = ECCCurve}, - 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, - 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, - private_key = PrivateKey}, - session = #session{srp_username = Username}, - connection_states = ConnectionStates0 - } = State0, Connection) - when KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - SrpParams = handle_srp_identity(Username, LookupFun), - Keys = case generate_srp_server_keys(SrpParams, 0) of - Alert = #alert{} -> - throw(Alert); - Keys0 = {_,_} -> - Keys0 - end, - #{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, - signature_algs := SupportedHashSigns}, - connection_states = ConnectionStates0} = State0, Connection) -> - #{security_parameters := - #security_parameters{cipher_suite = CipherSuite}} = - ssl_record:pending_connection_state(ConnectionStates0, read), - TLSVersion = ssl:tls_version(Version), - HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, - TLSVersion), - Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, - HashSigns, TLSVersion), - State = Connection:queue_handshake(Msg, State0), - State#state{client_certificate_requested = true}; - -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 -> - handle_own_alert(Alert, Version, certify, State0) - 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, _) -> - 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 -> - Alert - end. - -generate_srp_server_keys(_SrpParams, 10) -> - ?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']}) of - Keys -> - Keys - catch - error:_ -> - generate_srp_server_keys(SrpParams, N+1) - end. - -generate_srp_client_keys(_Generator, _Prime, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_client_keys(Generator, Prime, N) -> - - try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of - Keys -> - Keys - catch - error:_ -> - 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{connection_states = ConnectionStates0} = State0, - Connection) -> - ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, - ConnectionStates0), - {Record, State} = prepare_connection(State0#state{session = Session, - connection_states = ConnectionStates}, - Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]); -cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0, - Connection) -> - ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, - ConnectionStates0), - {State1, Actions} = - finalize_handshake(State0#state{connection_states = ConnectionStates1, - session = Session}, cipher, Connection), - {Record, State} = prepare_connection(State1, Connection), - 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. - -record_cb(tls_connection) -> - tls_record; -record_cb(dtls_connection) -> - dtls_record. - -call(FsmPid, Event) -> - try gen_statem:call(FsmPid, Event) - catch - exit:{noproc, _} -> - {error, closed}; - exit:{normal, _} -> - {error, closed}; - exit:{{shutdown, _},_} -> - {error, closed} - end. - -get_socket_opts(_, _,_,[], _, Acc) -> - {ok, Acc}; -get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, - [{mode, SockOpts#socket_options.mode} | Acc]); -get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) -> - case SockOpts#socket_options.packet of - {Type, headers} -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); - Type -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) - end; -get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, - [{header, SockOpts#socket_options.header} | Acc]); -get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, - [{active, SockOpts#socket_options.active} | Acc]); -get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) -> - case Connection:getopts(Transport, Socket, [Tag]) of - {ok, [Opt]} -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]); - {error, Reason} -> - {error, {options, {socket_options, Tag, Reason}}} - end; -get_socket_opts(_,_, _,Opts, _,_) -> - {error, {options, {socket_options, Opts, function_clause}}}. - -set_socket_opts(_,_,_, [], SockOpts, []) -> - {ok, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) -> - %% Set non emulated options - try ConnectionCb:setopts(Transport, Socket, Other) of - ok -> - {ok, SockOpts}; - {error, InetError} -> - {{error, {options, {socket_options, Other, InetError}}}, SockOpts} - catch - _:Error -> - %% So that inet behavior does not crash our process - {{error, {options, {socket_options, Other, Error}}}, SockOpts} - end; - -set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) - when Mode == list; Mode == binary -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{mode = Mode}, Other); -set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) - when Packet == raw; - Packet == 0; - Packet == 1; - Packet == 2; - Packet == 4; - Packet == asn1; - Packet == cdr; - Packet == sunrm; - Packet == fcgi; - Packet == tpkt; - Packet == line; - Packet == http; - Packet == httph; - Packet == http_bin; - Packet == httph_bin -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{packet = Packet}, Other); -set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other) - when is_integer(Header) -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{header = Header}, Other); -set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) -> - {{error,{options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other) - when Active == once; - Active == true; - Active == false -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{active = Active}, Other); -set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts], - SockOpts=#socket_options{active = Active0}, Other) - when Active1 >= -32768, Active1 =< 32767 -> - Active = if - is_integer(Active0), Active0 + Active1 < -32768 -> - error; - is_integer(Active0), Active0 + Active1 =< 0 -> - false; - is_integer(Active0), Active0 + Active1 > 32767 -> - error; - Active1 =< 0 -> - false; - is_integer(Active0) -> - Active0 + Active1; - true -> - Active1 - end, - case Active of - error -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; - _ -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{active = Active}, Other) - end; -set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]). - - - -hibernate_after(connection = StateName, - #state{ssl_options= #{hibernate_after := HibernateAfter}} = State, - Actions) -> - {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; -hibernate_after(StateName, State, Actions) -> - {next_state, StateName, State, Actions}. - - -terminate_alert(normal) -> - ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); -terminate_alert({Reason, _}) when Reason == close; - Reason == shutdown -> - ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); -terminate_alert(_) -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR). - -handle_trusted_certs_db(#state{ssl_options = - #{cacertfile := <<>>, cacerts := []}}) -> - %% No trusted certs specified - ok; -handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, - cert_db = CertDb}, - ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined -> - %% Certs provided as DER directly can not be shared - %% with other connections and it is safe to delete them when the connection ends. - ssl_pkix_db:remove_trusted_certs(Ref, CertDb); -handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) -> - %% Something went wrong early (typically cacertfile does not - %% exist) so there is nothing to handle - ok; -handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, - file_ref_db = RefDb}, - ssl_options = #{cacertfile := File}}) -> - case ssl_pkix_db:ref_count(Ref, RefDb, -1) of - 0 -> - ssl_manager:clean_cert_db(Ref, File); - _ -> - ok - end. - -prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate}, - start_or_recv_from = RecvFrom} = State0, Connection) - when Renegotiate =/= {false, first}, - RecvFrom =/= undefined -> - State = Connection:reinit(State0), - {no_record, ack_connection(State)}; -prepare_connection(State0, Connection) -> - State = Connection:reinit(State0), - {no_record, ack_connection(State)}. - -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer; - Initiater == internal -> - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) -> - gen_statem:reply(From, ok), - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv, - start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined -> - gen_statem:reply(StartFrom, connected), - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}, - start_or_recv_from = undefined}; -ack_connection(State) -> - State. - -session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> - Session#session{ecc = ECCurve}; -session_handle_params(_, Session) -> - Session. - -handle_session(Role = server, #{reuse_sessions := true} = SslOpts, - Host, Port, Session0) -> - register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true); -handle_session(Role = client, #{verify := verify_peer, - reuse_sessions := Reuse} = SslOpts, - Host, Port, Session0) when Reuse =/= false -> - register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, reg_type(Reuse)); -handle_session(server, _, Host, Port, Session) -> - %% Remove "session of type new" entry from session DB - ssl_manager:invalidate_session(Host, Port, Session), - Session; -handle_session(client, _,_,_, Session) -> - %% In client case there is no entry yet, so nothing to remove - Session. - -reg_type(save) -> - true; -reg_type(true) -> - unique. - -register_session(client, Host, Port, #session{is_resumable = new} = Session0, Save) -> - Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Host, Port, Session, Save), - Session; -register_session(server, _, Port, #session{is_resumable = new} = Session0, _) -> - Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Port, Session), - Session; -register_session(_, _, _, Session, _) -> - Session. %% Already registered - -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} = State) -> - Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - 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 -> - handle_own_alert(Alert, Version, hello, State) - end. - -make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; -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. - -ssl_options_list(SslOptions) -> - L = maps:to_list(SslOptions), - ssl_options_list(L, []). - -ssl_options_list([], Acc) -> - lists:reverse(Acc); -%% Skip internal options, only return user options -ssl_options_list([{protocol, _}| T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{erl_dist, _}|T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{renegotiate_at, _}|T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{ciphers = Key, Value}|T], Acc) -> - ssl_options_list(T, - [{Key, lists:map( - fun(Suite) -> - ssl_cipher_format:suite_bin_to_map(Suite) - end, Value)} - | Acc]); -ssl_options_list([{Key, Value}|T], Acc) -> - ssl_options_list(T, [{Key, Value} | Acc]). - -handle_active_option(false, connection = StateName, To, Reply, State) -> - hibernate_after(StateName, State, [{reply, To, Reply}]); - -handle_active_option(_, connection = StateName, To, _Reply, #state{static_env = #static_env{role = Role}, - connection_env = #connection_env{terminated = true}, - user_data_buffer = {_,0,_}} = State) -> - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd), - handle_normal_shutdown(Alert#alert{role = Role}, StateName, - State#state{start_or_recv_from = To}), - {stop,{shutdown, peer_close}, State}; -handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, - user_data_buffer = {_,0,_}} = State0) -> - case Connection:next_event(StateName0, no_record, State0) of - {next_state, StateName, State} -> - hibernate_after(StateName, State, [{reply, To, Reply}]); - {next_state, StateName, State, Actions} -> - hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); - {stop, _, _} = Stop -> - Stop - end; -handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) -> - %% Active once already set - {next_state, StateName, State, [{reply, To, Reply}]}; - -%% user_data_buffer nonempty -handle_active_option(_, StateName0, To, Reply, - #state{static_env = #static_env{protocol_cb = Connection}} = State0) -> - case read_application_data(<<>>, State0) of - {stop, _, _} = Stop -> - Stop; - {Record, State1} -> - %% Note: Renogotiation may cause StateName0 =/= StateName - case Connection:next_event(StateName0, Record, State1) of - {next_state, StateName, State} -> - hibernate_after(StateName, State, [{reply, To, Reply}]); - {next_state, StateName, State, Actions} -> - hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); - {stop, _, _} = Stop -> - Stop - end - end. - - -%% Picks ClientData -get_data(#socket_options{active=false}, undefined, _Bin) -> - %% Recv timed out save buffer data until next recv - passive; -get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin) - when Raw =:= raw; Raw =:= 0 -> %% Raw Mode - case Bin of - <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 -> - %% Active true or once, or passive mode recv(0) - {ok, Bin, <<>>}; - <<Data:BytesToRead/binary, Rest/binary>> -> - %% Passive Mode, recv(Bytes) - {ok, Data, Rest}; - <<_/binary>> -> - %% Passive Mode not enough data - {more, BytesToRead} - end; -get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) -> - PacketOpts = [{packet_size, Size}], - decode_packet(Type, Bin, PacketOpts). - -decode_packet({http, headers}, Buffer, PacketOpts) -> - decode_packet(httph, Buffer, PacketOpts); -decode_packet({http_bin, headers}, Buffer, PacketOpts) -> - decode_packet(httph_bin, Buffer, PacketOpts); -decode_packet(Type, Buffer, PacketOpts) -> - erlang:decode_packet(Type, Buffer, PacketOpts). - -%% Just like with gen_tcp sockets, an ssl socket that has been configured with -%% {packet, http} (or {packet, http_bin}) will automatically switch to expect -%% HTTP headers after it sees a HTTP Request or HTTP Response line. We -%% represent the current state as follows: -%% #socket_options.packet =:= http: Expect a HTTP Request/Response line -%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers -%% Note that if the user has explicitly configured the socket to expect -%% HTTP headers using the {packet, httph} option, we don't do any automatic -%% switching of states. -deliver_app_data( - CPids, Transport, Socket, - #socket_options{active=Active, packet=Type} = SOpts, - Data, Pid, From, Trackers, Connection) -> - %% - send_or_reply( - Active, Pid, From, - format_reply( - CPids, Transport, Socket, SOpts, Data, Trackers, Connection)), - SO = - case Data of - {P, _, _, _} - when ((P =:= http_request) or (P =:= http_response)), - ((Type =:= http) or (Type =:= http_bin)) -> - SOpts#socket_options{packet={Type, headers}}; - http_eoh when tuple_size(Type) =:= 2 -> - %% End of headers - expect another Request/Response line - {Type1, headers} = Type, - SOpts#socket_options{packet=Type1}; - _ -> - SOpts - end, - case Active of - once -> - SO#socket_options{active=false}; - 1 -> - send_user( - Pid, - format_passive( - CPids, Transport, Socket, Trackers, Connection)), - SO#socket_options{active=false}; - N when is_integer(N) -> - SO#socket_options{active=N - 1}; - _ -> - SO - end. - -format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet, - header = Header}, Data, _, _) -> - {ok, do_format_reply(Mode, Packet, Header, Data)}; -format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, - header = Header}, Data, Trackers, Connection) -> - {ssl, Connection:socket(CPids, Transport, Socket, Trackers), - do_format_reply(Mode, Packet, Header, Data)}. - -deliver_packet_error(CPids, Transport, Socket, - SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) -> - send_or_reply(Active, Pid, From, format_packet_error(CPids, - Transport, Socket, SO, Data, Trackers, Connection)). - -format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> - {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; -format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode}, - Data, Trackers, Connection) -> - {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers), - {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. - -do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode - header(N, Data); -do_format_reply(binary, _, _, Data) -> - Data; -do_format_reply(list, Packet, _, Data) - when Packet == http; Packet == {http, headers}; - Packet == http_bin; Packet == {http_bin, headers}; - Packet == httph; Packet == httph_bin -> - Data; -do_format_reply(list, _,_, Data) -> - binary_to_list(Data). - -format_passive(CPids, Transport, Socket, Trackers, Connection) -> - {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}. - -header(0, <<>>) -> - <<>>; -header(_, <<>>) -> - []; -header(0, Binary) -> - Binary; -header(N, Binary) -> - <<?BYTE(ByteN), NewBinary/binary>> = Binary, - [ByteN | header(N-1, NewBinary)]. - -send_or_reply(false, _Pid, From, Data) when From =/= undefined -> - gen_statem:reply(From, Data); -%% Can happen when handling own alert or tcp error/close and there is -%% no outstanding gen_fsm sync events -send_or_reply(false, no_pid, _, _) -> - ok; -send_or_reply(_, Pid, _From, Data) -> - send_user(Pid, Data). - -send_user(Pid, Msg) -> - Pid ! Msg, - ok. - -alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection); -alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection). - -alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection). - -alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined -> - %% If there is an outstanding ssl_accept | recv - %% From will be defined and send_or_reply will - %% send the appropriate error message. - ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName), - send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) -> - case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of - closed -> - send_or_reply(Active, Pid, From, - {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)}); - ReasonCode -> - send_or_reply(Active, Pid, From, - {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode}) - end. - -log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> - ssl_logger:log(notice, Level, #{protocol => ProtocolName, - role => Role, - statename => StateName, - alert => Alert, - alerter => own}, Alert#alert.where); -log_alert(Level, Role, ProtocolName, StateName, Alert) -> - ssl_logger:log(notice, Level, #{protocol => ProtocolName, - role => Role, - statename => StateName, - alert => Alert, - alerter => peer}, Alert#alert.where). - -maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) -> - invalidate_session(Role, Host, Port, Session); -maybe_invalidate_session(_, _, _, _, _) -> - ok. - -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, Port, Session) -> - ssl_manager:invalidate_session(Port, Session). - -handle_sni_extension(undefined, State) -> - State; -handle_sni_extension(#sni{hostname = Hostname}, State) -> - case is_sni_value(Hostname) of - true -> - handle_sni_extension(Hostname, State); - false -> - ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, {sni_included_trailing_dot, Hostname}) - end; -handle_sni_extension(Hostname, #state{static_env = #static_env{role = Role} = InitStatEnv0, - handshake_env = HsEnv, - connection_env = CEnv} = State0) -> - NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), - case NewOptions of - undefined -> - State0; - _ -> - {ok, #{cert_db_ref := Ref, - cert_db_handle := CertDbHandle, - fileref_db_handle := FileRefHandle, - session_cache := CacheHandle, - crl_db_info := CRLDbHandle, - private_key := Key, - dh_params := DHParams, - own_certificate := OwnCert}} = - ssl_config:init(NewOptions, Role), - State0#state{ - session = State0#state.session#session{own_certificate = OwnCert}, - static_env = InitStatEnv0#static_env{ - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle - }, - connection_env = CEnv#connection_env{private_key = Key}, - ssl_options = NewOptions, - handshake_env = HsEnv#handshake_env{sni_hostname = Hostname, - diffie_hellman_params = DHParams} - } - end. - -update_ssl_options_from_sni(#{sni_fun := SNIFun, - sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) -> - SSLOption = - case SNIFun of - undefined -> - proplists:get_value(SNIHostname, - SNIHosts); - SNIFun -> - SNIFun(SNIHostname) - end, - case SSLOption of - undefined -> - undefined; - _ -> - ssl:handle_options(SSLOption, server, OrigSSLOptions) - end. - -new_emulated([], EmOpts) -> - EmOpts; -new_emulated(NewEmOpts, _) -> - NewEmOpts. - -no_records(Extensions) -> - maps:map(fun(_, Value) -> - ssl_handshake:extension_value(Value) - end, Extensions). - -is_sni_value(Hostname) -> - case hd(lists:reverse(Hostname)) of - $. -> - false; - _ -> - true - end. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 284ded64d8..4f9584bb9f 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -36,7 +36,7 @@ -record(static_env, { role :: client | server, transport_cb :: atom(), % callback module - protocol_cb :: tls_connection | dtls_connection, + protocol_cb :: tls_gen_connection | dtls_gen_connection, data_tag :: atom(), % ex tcp. close_tag :: atom(), % ex tcp_closed error_tag :: atom(), % ex tcp_error @@ -62,10 +62,14 @@ expecting_finished = false ::boolean(), renegotiation :: undefined | {boolean(), From::term() | internal | peer}, resumption = false :: boolean(), %% TLS 1.3 + change_cipher_spec_sent = false :: boolean(), %% TLS 1.3 + sni_guided_cert_selection = false :: boolean(), %% TLS 1.3 + early_data_accepted = false :: boolean(), %% TLS 1.3 allow_renegotiate = true ::boolean(), %% Ext handling hello, %%:: #client_hello{} | #server_hello{} sni_hostname = undefined, + max_frag_enum :: undefined | {max_frag_enum, integer()}, expecting_next_protocol_negotiation = false ::boolean(), next_protocol = undefined :: undefined | binary(), alpn = undefined, %% Used in TLS 1.3 @@ -80,7 +84,10 @@ public_key_info :: ssl_handshake:public_key_info() | 'undefined', premaster_secret :: binary() | secret_printout() | 'undefined', server_psk_identity :: binary() | 'undefined', % server psk identity hint - ticket_seed + cookie_iv_shard :: {binary(), binary()} %% IV, Shard + | 'undefined', + ocsp_stapling_state = #{ocsp_stapling => false, + ocsp_expect => no_staple} }). -record(connection_env, { diff --git a/lib/ssl/src/ssl_crl.erl b/lib/ssl/src/ssl_crl.erl index 888a75bfd6..12d261bcdc 100644 --- a/lib/ssl/src/ssl_crl.erl +++ b/lib/ssl/src/ssl_crl.erl @@ -27,35 +27,40 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([trusted_cert_and_path/3]). +-export([trusted_cert_and_path/4]). -trusted_cert_and_path(CRL, {SerialNumber, Issuer},{_, {Db, DbRef}} = DbHandle) -> +trusted_cert_and_path(CRL, {SerialNumber, Issuer}, CertPath, {Db, DbRef}) -> + %% CRL issuer cert ID is known case ssl_pkix_db:lookup_trusted_cert(Db, DbRef, SerialNumber, Issuer) of undefined -> - trusted_cert_and_path(CRL, issuer_not_found, DbHandle); + %% But not found in our database + search_certpath(CRL, CertPath, Db, DbRef); {ok, {_, OtpCert}} -> {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), {ok, Root, lists:reverse(Chain)} end; -trusted_cert_and_path(CRL, issuer_not_found, {CertPath, {Db, DbRef}}) -> - case find_issuer(CRL, {certpath, - [{Der, public_key:pkix_decode_cert(Der,otp)} || Der <- CertPath]}) of - {ok, OtpCert} -> - {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), - {ok, Root, lists:reverse(Chain)}; - {error, issuer_not_found} -> - trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef}) - end; -trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef} = DbInfo) -> - case find_issuer(CRL, DbInfo) of - {ok, OtpCert} -> - {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), - {ok, Root, lists:reverse(Chain)}; - {error, issuer_not_found} -> - {error, unknown_ca} - end. +trusted_cert_and_path(CRL, issuer_not_found, CertPath, {Db, DbRef}) -> + case search_certpath(CRL, CertPath, Db, DbRef) of + {error, unknown_ca} -> + Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)), + IsIssuerFun = + fun({_Key, {_Der,ErlCertCandidate}}, Acc) -> + verify_crl_issuer(CRL, ErlCertCandidate, Issuer, Acc); + (_, Acc) -> + Acc + end, + case search_db(IsIssuerFun, Db, DbRef) of + {ok, OtpCert} -> + {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), + {ok, Root, lists:reverse(Chain)}; + {error, issuer_not_found} -> + {error, unknown_ca} + end; + Result -> + Result + end. -find_issuer(CRL, {certpath = Db, DbRef}) -> +search_certpath(CRL, CertPath, Db, DbRef) -> Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)), IsIssuerFun = fun({_Der,ErlCertCandidate}, Acc) -> @@ -63,15 +68,18 @@ find_issuer(CRL, {certpath = Db, DbRef}) -> (_, Acc) -> Acc end, - find_issuer(IsIssuerFun, Db, DbRef); -find_issuer(CRL, {Db, DbRef}) -> - Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)), - IsIssuerFun = - fun({_Key, {_Der,ErlCertCandidate}}, Acc) -> - verify_crl_issuer(CRL, ErlCertCandidate, Issuer, Acc); - (_, Acc) -> - Acc - end, + case find_issuer(IsIssuerFun, certpath, + [{Der, public_key:pkix_decode_cert(Der,otp)} || Der <- CertPath]) of + {ok, OtpCert} -> + {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), + {ok, Root, lists:reverse(Chain)}; + {error, issuer_not_found} -> + {error, unknown_ca} + end. + +search_db(IsIssuerFun, _, {extracted, ExtractedCerts})-> + find_issuer(IsIssuerFun, extracted, ExtractedCerts); +search_db(IsIssuerFun, Db, DbRef) -> find_issuer(IsIssuerFun, Db, DbRef). find_issuer(IsIssuerFun, certpath, Certs) -> diff --git a/lib/ssl/src/ssl_dist_connection_sup.erl b/lib/ssl/src/ssl_dist_connection_sup.erl index e5842c866e..441a7577be 100644 --- a/lib/ssl/src/ssl_dist_connection_sup.erl +++ b/lib/ssl/src/ssl_dist_connection_sup.erl @@ -42,38 +42,20 @@ start_link() -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= - init([]) -> - - TLSConnetionManager = tls_connection_manager_child_spec(), - %% Handles emulated options so that they inherited by the accept - %% socket, even when setopts is performed on the listen socket - ListenOptionsTracker = listen_options_tracker_child_spec(), - - {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager, - ListenOptionsTracker - ]}}. + TLSSup = tls_sup_child_spec(), + {ok, {{one_for_one, 10, 3600}, [TLSSup]}}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -tls_connection_manager_child_spec() -> - Name = dist_tls_connection, - StartFunc = {tls_connection_sup, start_link_dist, []}, - Restart = permanent, - Shutdown = 4000, - Modules = [tls_connection_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -listen_options_tracker_child_spec() -> - Name = dist_tls_socket, - StartFunc = {ssl_listen_tracker_sup, start_link_dist, []}, +tls_sup_child_spec() -> + Name = dist_tls_sup, + StartFunc = {tls_dist_sup, start_link, []}, Restart = permanent, Shutdown = 4000, - Modules = [tls_socket], + Modules = [tls_dist_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. - diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl index bea67935d8..ae0887c3d9 100644 --- a/lib/ssl/src/ssl_dist_sup.erl +++ b/lib/ssl/src/ssl_dist_sup.erl @@ -70,16 +70,16 @@ ssl_admin_child_spec() -> StartFunc = {ssl_dist_admin_sup, start_link , []}, Restart = permanent, Shutdown = 4000, - Modules = [ssl_admin_sup], + Modules = [ssl_dist_admin_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. ssl_connection_sup() -> - Name = ssl_dist_connection_sup, - StartFunc = {ssl_dist_connection_sup, start_link, []}, + Name = tls_dist_sup, + StartFunc = {tls_dist_sup, start_link, []}, Restart = permanent, Shutdown = 4000, - Modules = [ssl_connection_sup], + Modules = [tls_dist_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl new file mode 100644 index 0000000000..e6268b4876 --- /dev/null +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -0,0 +1,2055 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: Provid help function to handle generic parts of TLS +%% connection fsms +%%---------------------------------------------------------------------- + +-module(ssl_gen_statem). + +-include_lib("kernel/include/logger.hrl"). + +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_connection.hrl"). +-include("ssl_alert.hrl"). +-include("tls_handshake.hrl"). + +%% Initial Erlang process setup +-export([start_link/7, + start_link/8, + init/1]). + +%% TLS connection setup +-export([ssl_config/3, + connect/8, + handshake/7, + handshake/2, + handshake/3, + handshake_continue/3, + handshake_cancel/1, + handle_sni_extension/2, + socket_control/4, + socket_control/5, + prepare_connection/2]). + +%% User Events +-export([send/2, + recv/3, + close/2, + shutdown/2, + new_user/2, + get_opts/2, + set_opts/2, + peer_certificate/1, + negotiated_protocol/1, + connection_information/2 + ]). + +%% Erlang Distribution export +-export([dist_handshake_complete/2]). + +%% Generic fsm states +-export([initial_hello/3, + config_error/3, + connection/3]). + +-export([call/2, + handle_common_event/4, + handle_call/4, + handle_info/3 + ]). + +-export([hibernate_after/3]). + +%% Data handling +-export([read_application_data/2]). + +%% Alert and close handling +-export([send_alert/3, + handle_own_alert/4, + handle_alert/3, + handle_normal_shutdown/3, + handle_trusted_certs_db/1, + maybe_invalidate_session/6, + maybe_invalidate_session/5, + terminate/3]). + +%% Log handling +-export([format_status/2]). + +%%-------------------------------------------------------------------- +%%% Initial Erlang process setup +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +-spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> + {ok, pid()} | ignore | {error, reason()}. +%% +%% Description: Creates a process which calls Module:init/1 to +%% choose appropriat gen_statem and initialize. +%%-------------------------------------------------------------------- +start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. + +%%-------------------------------------------------------------------- +-spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> + {ok, pid()} | ignore | {error, reason()}. +%% +%% Description: Creates a gen_statem process which calls Module:init/1 to +%% initialize. +%%-------------------------------------------------------------------- +start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. + + +%%-------------------------------------------------------------------- +-spec init(list()) -> no_return(). +%% Description: Initialization +%%-------------------------------------------------------------------- +init([_Role, Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) -> + process_flag(trap_exit, true), + link(Sender), + case ErlDist of + true -> + process_flag(priority, max); + _ -> + ok + end, + ConnectionFsm = tls_connection_fsm(TLSOpts), + ConnectionFsm:init(InitArgs); +init([_Role, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs) -> + process_flag(trap_exit, true), + ConnectionFsm = dtls_connection_fsm(TLSOpts), + ConnectionFsm:init(InitArgs). + +%%==================================================================== +%% TLS connection setup +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}. +%%-------------------------------------------------------------------- +ssl_config(Opts, Role, #state{static_env = InitStatEnv0, + handshake_env = HsEnv, + connection_env = CEnv} = State0) -> + {ok, #{cert_db_ref := Ref, + cert_db_handle := CertDbHandle, + fileref_db_handle := FileRefHandle, + session_cache := CacheHandle, + crl_db_info := CRLDbHandle, + private_key := Key, + dh_params := DHParams, + own_certificates := OwnCerts}} = + ssl_config:init(Opts, Role), + TimeStamp = erlang:monotonic_time(), + Session = State0#state.session, + + State0#state{session = Session#session{own_certificates = OwnCerts, + time_stamp = TimeStamp}, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, + handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams}, + connection_env = CEnv#connection_env{private_key = Key}, + ssl_options = Opts}. + +%%-------------------------------------------------------------------- +-spec connect(tls_gen_connection | dtls_gen_connection, + ssl:host(), inet:port_number(), + port() | {tuple(), port()}, %% TLS | DTLS + {ssl_options(), #socket_options{}, + %% Tracker only needed on server side + undefined}, + pid(), tuple(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Connect to an ssl server. +%%-------------------------------------------------------------------- +connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> + try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo, + Timeout) + catch + exit:{noproc, _} -> + {error, ssl_not_started} + end. +%%-------------------------------------------------------------------- +-spec handshake(tls_gen_connection | dtls_gen_connection, + inet:port_number(), port(), + {ssl_options(), #socket_options{}, list()}, + pid(), tuple(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Performs accept on an ssl listen socket. e.i. performs +%% ssl handshake. +%%-------------------------------------------------------------------- +handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> + try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User, + CbInfo, Timeout) + catch + exit:{noproc, _} -> + {error, ssl_not_started} + end. + +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | + {ok, #sslsocket{}, map()}| {error, reason()}. +%% +%% Description: Starts ssl handshake. +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> + case call(Pid, {start, Timeout}) of + connected -> + {ok, Socket}; + {ok, Ext} -> + {ok, Socket, no_records(Ext)}; + Error -> + Error + end. + +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) -> + {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}. +%% +%% Description: Starts ssl handshake with some new options +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> + case call(Pid, {start, SslOptions, Timeout}) of + connected -> + {ok, Socket}; + Error -> + Error + end. + +%%-------------------------------------------------------------------- +-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()], + timeout()) -> {ok, #sslsocket{}}| {error, reason()}. +%% +%% Description: Continues handshake with new options +%%-------------------------------------------------------------------- +handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> + case call(Pid, {handshake_continue, SslOptions, Timeout}) of + connected -> + {ok, Socket}; + Error -> + Error + end. +%%-------------------------------------------------------------------- +-spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}. +%% +%% Description: Cancels connection +%%-------------------------------------------------------------------- +handshake_cancel(#sslsocket{pid = [Pid|_]}) -> + case call(Pid, cancel) of + closed -> + ok; + Error -> + Error + end. +%-------------------------------------------------------------------- +-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Set the ssl process to own the accept socket +%%-------------------------------------------------------------------- +socket_control(Connection, Socket, Pid, Transport) -> + socket_control(Connection, Socket, Pid, Transport, undefined). + +%-------------------------------------------------------------------- +-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom(), [pid()] | atom()) -> + {ok, #sslsocket{}} | {error, reason()}. +%%-------------------------------------------------------------------- +socket_control(dtls_gen_connection = Connection, Socket, Pids, Transport, udp_listener) -> + %% dtls listener process must have the socket control + {ok, Connection:socket(Pids, Transport, Socket, undefined)}; + +socket_control(tls_gen_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) -> + case Transport:controlling_process(Socket, Pid) of + ok -> + {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; + {error, Reason} -> + {error, Reason} + end; +socket_control(dtls_gen_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) -> + case Transport:controlling_process(Socket, Pid) of + ok -> + {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)}; + {error, Reason} -> + {error, Reason} + end. + +prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate}, + start_or_recv_from = RecvFrom} = State0, Connection) + when Renegotiate =/= {false, first}, + RecvFrom =/= undefined -> + State = Connection:reinit(State0), + {no_record, ack_connection(State)}; +prepare_connection(State0, Connection) -> + State = Connection:reinit(State0), + {no_record, ack_connection(State)}. + +%%==================================================================== +%% User events +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec send(pid(), iodata()) -> ok | {error, reason()}. +%% +%% Description: Sends data over the ssl connection +%%-------------------------------------------------------------------- +send(Pid, Data) -> + call(Pid, {application_data, + %% iolist_to_iovec should really + %% be called iodata_to_iovec() + erlang:iolist_to_iovec(Data)}). + +%%-------------------------------------------------------------------- +-spec recv(pid(), integer(), timeout()) -> + {ok, binary() | list()} | {error, reason()}. +%% +%% Description: Receives data when active = false +%%-------------------------------------------------------------------- +recv(Pid, Length, Timeout) -> + call(Pid, {recv, Length, Timeout}). + +%%-------------------------------------------------------------------- +-spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Get connection information +%%-------------------------------------------------------------------- +connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) -> + case call(Pid, {connection_information, IncludeSecrityInfo}) of + {ok, Info} when IncludeSecrityInfo == true -> + {ok, maybe_add_keylog(Info)}; + Other -> + Other + end. + +%%-------------------------------------------------------------------- +-spec close(pid(), {close, Timeout::integer() | + {NewController::pid(), Timeout::integer()}}) -> + ok | {ok, port()} | {error, reason()}. +%% +%% Description: Close an ssl connection +%%-------------------------------------------------------------------- +close(ConnectionPid, How) -> + case call(ConnectionPid, How) of + {error, closed} -> + ok; + Other -> + Other + end. +%%-------------------------------------------------------------------- +-spec shutdown(pid(), atom()) -> ok | {error, reason()}. +%% +%% Description: Same as gen_tcp:shutdown/2 +%%-------------------------------------------------------------------- +shutdown(ConnectionPid, How) -> + call(ConnectionPid, {shutdown, How}). + +%%-------------------------------------------------------------------- +-spec new_user(pid(), pid()) -> ok | {error, reason()}. +%% +%% Description: Changes process that receives the messages when active = true +%% or once. +%%-------------------------------------------------------------------- +new_user(ConnectionPid, User) -> + call(ConnectionPid, {new_user, User}). + +%%-------------------------------------------------------------------- +-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Same as inet:getopts/2 +%%-------------------------------------------------------------------- +get_opts(ConnectionPid, OptTags) -> + call(ConnectionPid, {get_opts, OptTags}). +%%-------------------------------------------------------------------- +-spec set_opts(pid(), list()) -> ok | {error, reason()}. +%% +%% Description: Same as inet:setopts/2 +%%-------------------------------------------------------------------- +set_opts(ConnectionPid, Options) -> + call(ConnectionPid, {set_opts, Options}). + +%%-------------------------------------------------------------------- +-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. +%% +%% Description: Returns the peer cert +%%-------------------------------------------------------------------- +peer_certificate(ConnectionPid) -> + call(ConnectionPid, peer_certificate). + +%%-------------------------------------------------------------------- +-spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. +%% +%% Description: Returns the negotiated protocol +%%-------------------------------------------------------------------- +negotiated_protocol(ConnectionPid) -> + call(ConnectionPid, negotiated_protocol). + +dist_handshake_complete(ConnectionPid, DHandle) -> + gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}). + +handle_sni_extension(undefined, State) -> + {ok, State}; +handle_sni_extension(#sni{hostname = Hostname}, State0) -> + case check_hostname(State0, Hostname) of + valid -> + State1 = handle_sni_hostname(Hostname, State0), + State = set_sni_guided_cert_selection(State1, true), + {ok, State}; + unrecognized_name -> + {ok, handle_sni_hostname(Hostname, State0)}; + #alert{} = Alert -> + {error, Alert} + end. + +%%==================================================================== +%% Generic states +%%==================================================================== +%%-------------------------------------------------------------------- +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | {start, {list(), list()}, timeout()}| term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +initial_hello({call, From}, {start, Timeout}, + #state{static_env = #static_env{role = client = Role, + host = Host, + port = Port, + 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, + ocsp_stapling := OcspStaplingOpt, + ocsp_nonce := OcspNonceOpt, + 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_handshake_1_3:maybe_automatic_session_resumption(State0), + TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), + OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt), + Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Session#session.session_id, + Renegotiation, + Session#session.own_certificates, + KeyShare, + TicketData, + OcspNonce), + + %% 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_handshake_1_3:maybe(), + try + %% Send Early Data + State4 = Maybe(tls_handshake_1_3:maybe_send_early_data(State3)), + + {#state{handshake_env = HsEnv1} = State5, _} = + Connection:send_handshake_flight(State4), + + 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}}, + 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, RequestedVersion, 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:handle_options(Opts, Role, OrigSSLOptions), + State = ssl_config(SslOpts, Role, State0), + initial_hello({call, From}, {start, Timeout}, + State#state{ssl_options = SslOpts, + socket_options = new_emulated(EmOpts, SockOpts)}) + catch throw:Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} + end; +initial_hello({call, From}, {new_user, _} = Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +initial_hello({call, From}, _Msg, _State) -> + {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]}; +initial_hello(_Type, _Event, _State) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +config_error({call, From}, {start, _Timeout}, + #state{protocol_specific = #{error := Error}} = State) -> + {stop_and_reply, {shutdown, normal}, + [{reply, From, {error, Error}}], State}; +config_error({call, From}, {close, _}, State) -> + {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State}; +config_error({call, From}, _Msg, State) -> + {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}; +config_error(_Type, _Event, _State) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection({call, RecvFrom}, {recv, N, Timeout}, + #state{static_env = #static_env{protocol_cb = Connection}, + socket_options = + #socket_options{active = false}} = State0) -> + passive_receive(State0#state{bytes_to_read = N, + start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, + [{{timeout, recv}, Timeout, timeout}]); +connection({call, From}, peer_certificate, + #state{session = #session{peer_certificate = Cert}} = State) -> + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]); +connection({call, From}, {connection_information, true}, State) -> + Info = connection_info(State) ++ security_info(State), + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); +connection({call, From}, {connection_information, false}, State) -> + Info = connection_info(State), + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = undefined}} = State) -> + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = SelectedProtocol}} = State) -> + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = SelectedProtocol, + negotiated_protocol = undefined}} = State) -> + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, + {close, {Pid, _Timeout}}, + #state{connection_env = #connection_env{terminated = closed} = CEnv, + protocol_specific = PS} = State) -> + {next_state, downgrade, State#state{connection_env = + CEnv#connection_env{terminated = true, + downgrade = {Pid, From}}, + protocol_specific = PS#{active_n_toggle => true, + active_n => 1} + }, + [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]}; +connection({call, From}, + {close,{Pid, Timeout}}, + #state{connection_states = ConnectionStates, + static_env = #static_env{protocol_cb = Connection}, + protocol_specific = #{sender := Sender} = PS, + connection_env = CEnv + } = State0) -> + case tls_sender:downgrade(Sender, Timeout) of + {ok, Write} -> + %% User downgrades connection + %% When downgrading an TLS connection to a transport connection + %% we must recive the close alert from the peer before releasing the + %% transport socket. + State = Connection:send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + State0#state{connection_states = + ConnectionStates#{current_write => Write}}), + {next_state, downgrade, State#state{connection_env = + CEnv#connection_env{downgrade = {Pid, From}, + terminated = true}, + protocol_specific = PS#{active_n_toggle => true, + active_n => 1} + }, + [{timeout, Timeout, downgrade}]}; + {error, timeout} -> + {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]} + end; +connection({call, From}, Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +connection(cast, {dist_handshake_complete, DHandle}, + #state{ssl_options = #{erl_dist := true}, + static_env = #static_env{protocol_cb = Connection}, + connection_env = CEnv, + socket_options = SockOpts} = State0) -> + process_flag(priority, normal), + State1 = + State0#state{ + socket_options = SockOpts#socket_options{active = true}, + connection_env = CEnv#connection_env{erl_dist_handle = DHandle}, + bytes_to_read = undefined}, + {Record, State} = read_application_data(<<>>, State1), + Connection:next_event(connection, Record, State); +connection(info, Msg, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:handle_info(Msg, ?FUNCTION_NAME, State); +connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom, + static_env = #static_env{protocol_cb = Connection}} = State) -> + passive_receive(State, ?FUNCTION_NAME, Connection, []); +connection(Type, Msg, State) -> + handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +%%==================================================================== +%% Event/Msg handling +%%==================================================================== +handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, + #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = _Version}} = State0) -> + Hist = ssl_handshake:update_handshake_history(Hist0, Raw), + {next_state, StateName, + State0#state{handshake_env = + HsEnv#handshake_env{tls_handshake_history = Hist}}, + [{next_event, internal, Handshake}]}; +handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, + #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); +handle_common_event(timeout, hibernate, _, _) -> + {keep_state_and_data, [hibernate]}; +handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, StateName, State); +handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) -> + {stop_and_reply, + {shutdown, user_timeout}, + {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}}; +handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State) -> + {next_state, StateName, State#state{start_or_recv_from = undefined, + bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]}; +handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}) when + StateName =/= connection -> + {keep_state_and_data, [postpone]}; +handle_common_event(Type, Msg, StateName, #state{connection_env = + #connection_env{negotiated_version = Version}} = State) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}), + handle_own_alert(Alert, Version, StateName, State). + +handle_call({application_data, _Data}, _, _, _) -> + %% In renegotiation priorities handshake, send data when handshake is finished + {keep_state_and_data, [postpone]}; +handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State) -> + %% Run terminate before returning so that the reuseaddr + %% inet-option works properly + Result = terminate(Close, StateName, State), + {stop_and_reply, + {shutdown, normal}, + {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}}; +handle_call({shutdown, read_write = How}, From, StateName, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}, + connection_env = CEnv} = State) -> + try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + StateName, State) of + _ -> + case Transport:shutdown(Socket, How) of + ok -> + {next_state, StateName, State#state{connection_env = + CEnv#connection_env{terminated = true}}, + [{reply, From, ok}]}; + Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, Error}, + State#state{connection_env = CEnv#connection_env{terminated = true}}} + end + catch + throw:Return -> + Return + end; +handle_call({shutdown, How0}, From, StateName, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State) -> + case Transport:shutdown(Socket, How0) of + ok -> + {next_state, StateName, State, [{reply, From, ok}]}; + Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State} + end; +handle_call({recv, _N, _Timeout}, From, _, + #state{socket_options = + #socket_options{active = Active}}) when Active =/= false -> + {keep_state_and_data, [{reply, From, {error, einval}}]}; +handle_call({recv, N, Timeout}, RecvFrom, StateName, State) -> + %% Doing renegotiate wait with handling request until renegotiate is + %% finished. + {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom}, + [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]}; +handle_call({new_user, User}, From, StateName, + State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}) -> + NewMon = erlang:monitor(process, User), + erlang:demonitor(OldMon, [flush]), + {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}}, + [{reply, From, ok}]}; +handle_call({get_opts, OptTags}, From, _, + #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}, + socket_options = SockOpts}) -> + OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), + {keep_state_and_data, [{reply, From, OptsReply}]}; +handle_call({set_opts, Opts0}, From, StateName, + #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport, + trackers = Trackers}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}, + socket_options = Opts1 + } = State0) -> + {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), + case {proplists:lookup(active, Opts0), Opts} of + {{_, N}, #socket_options{active=false}} when is_integer(N) -> + send_user( + Pid, + format_passive( + Connection:pids(State0), Transport, Socket, Trackers, Connection)); + _ -> + ok + end, + State = State0#state{socket_options = Opts}, + handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); + +handle_call(renegotiate, From, StateName, _) when StateName =/= connection -> + {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; + +handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, + #state{connection_states = ConnectionStates, + connection_env = #connection_env{negotiated_version = Version}}) -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates, read), + #security_parameters{master_secret = MasterSecret, + client_random = ClientRandom, + server_random = ServerRandom, + prf_algorithm = PRFAlgorithm} = SecParams, + Reply = try + SecretToUse = case Secret of + _ when is_binary(Secret) -> Secret; + master_secret -> MasterSecret + end, + SeedToUse = lists:reverse( + lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; + (client_random, Acc) -> [ClientRandom|Acc]; + (server_random, Acc) -> [ServerRandom|Acc] + end, [], Seed)), + ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) + catch + exit:_ -> {error, badarg}; + error:Reason -> {error, Reason} + end, + {keep_state_and_data, [{reply, From, Reply}]}; +handle_call(_,_,_,_) -> + {keep_state_and_data, [postpone]}. +handle_info({ErrorTag, Socket, econnaborted}, StateName, + #state{static_env = #static_env{role = Role, + host = Host, + port = Port, + socket = Socket, + transport_cb = Transport, + error_tag = ErrorTag, + trackers = Trackers, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = Type}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + start_or_recv_from = StartFrom + } = State) when StateName =/= connection -> + + maybe_invalidate_session(Version, Type, Role, Host, Port, Session), + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers,Socket, + StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection), + {stop, {shutdown, normal}, State}; + +handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{ + role = Role, + socket = Socket, + error_tag = ErrorTag}, + ssl_options = #{log_level := Level}} = State) -> + ssl_logger:log(info, Level, #{description => "Socket error", + reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION), + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}), + handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown,normal}, State}; + +handle_info({'DOWN', MonitorRef, _, _, Reason}, _, + #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}, + ssl_options = #{erl_dist := true}}) -> + {stop, {shutdown, Reason}}; +handle_info({'DOWN', MonitorRef, _, _, _}, _, + #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) -> + {stop, {shutdown, normal}}; +handle_info({'EXIT', Pid, _Reason}, StateName, + #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) -> + %% It seems the user application has linked to us + %% - ignore that and let the monitor handle this + {next_state, StateName, State}; +%%% So that terminate will be run when supervisor issues shutdown +handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> + {stop, shutdown, State}; +handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> + %% Handle as transport close" + {stop,{shutdown, transport_closed}, State}; +handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> + {stop,{shutdown, Reason}, State}; +handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> %% PRE TLS-1.3 + {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}}; +handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}, + ssl_options = #{log_level := Level}} = State) -> + ssl_logger:log(notice, Level, #{description => "Unexpected INFO message", + reason => [{message, Msg}, {socket, Socket}, + {error_tag, ErrorTag}]}, ?LOCATION), + {next_state, StateName, State}. + +%%==================================================================== +%% Application Data +%%==================================================================== +read_application_data(Data, + #state{user_data_buffer = + {Front0,BufferSize0,Rear0}, + connection_env = + #connection_env{erl_dist_handle = DHandle}} + = State) -> + Front = Front0, + BufferSize = BufferSize0 + byte_size(Data), + Rear = [Data|Rear0], + case DHandle of + undefined -> + read_application_data(State, Front, BufferSize, Rear); + _ -> + try read_application_dist_data(DHandle, Front, BufferSize, Rear) of + Buffer -> + {no_record, State#state{user_data_buffer = Buffer}} + catch error:_ -> + {stop,disconnect, + State#state{user_data_buffer = {Front,BufferSize,Rear}}} + end + end. +passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, + %% Assert! Erl distribution uses active sockets + connection_env = #connection_env{erl_dist_handle = undefined}} + = State0, StateName, Connection, StartTimerAction) -> + case BufferSize of + 0 -> + Connection:next_event(StateName, no_record, State0, StartTimerAction); + _ -> + case read_application_data(State0, Front, BufferSize, Rear) of + {stop, _, _} = ShutdownError -> + ShutdownError; + {Record, State} -> + case State#state.start_or_recv_from of + undefined -> + %% Cancel recv timeout as data has been delivered + Connection:next_event(StateName, Record, State, + [{{timeout, recv}, infinity, timeout}]); + _ -> + Connection:next_event(StateName, Record, State, StartTimerAction) + end + end + end. + +%%==================================================================== +%% Hibernation +%%==================================================================== + +hibernate_after(connection = StateName, + #state{ssl_options= #{hibernate_after := HibernateAfter}} = State, + Actions) -> + {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; +hibernate_after(StateName, State, Actions) -> + {next_state, StateName, State, Actions}. + +%%==================================================================== +%% Alert and close handling +%%==================================================================== +send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:send_alert_in_connection(Alert, State); +send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:send_alert(Alert, State). + +handle_own_alert(Alert0, _, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = #{log_level := LogLevel}} = State) -> + try %% Try to tell the other side + send_alert(Alert0, StateName, State) + catch _:_ -> %% Can crash if we are in a uninitialized state + ignore + end, + try %% Try to tell the local user + Alert = Alert0#alert{role = Role}, + log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), + handle_normal_shutdown(Alert,StateName, State) + catch _:_ -> + ok + end, + {stop, {shutdown, own_alert}, State}. + +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + trackers = Trackers}, + handshake_env = #handshake_env{renegotiation = {false, first}}, + start_or_recv_from = StartFrom} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection); + +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + trackers = Trackers}, + connection_env = #connection_env{user_application = {_Mon, Pid}}, + socket_options = Opts, + start_or_recv_from = RecvFrom} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection). + +handle_alert(#alert{level = ?FATAL} = Alert0, StateName, + #state{static_env = #static_env{role = Role, + socket = Socket, + host = Host, + port = Port, + trackers = Trackers, + transport_cb = Transport, + protocol_cb = Connection}, + connection_env = #connection_env{user_application = {_Mon, Pid}}, + ssl_options = #{log_level := LogLevel}, + start_or_recv_from = From, + session = Session, + socket_options = Opts} = State) -> + invalidate_session(Role, Host, Port, Session), + Alert = Alert0#alert{role = opposite_role(Role)}, + log_alert(LogLevel, Role, Connection:protocol_name(), + StateName, Alert), + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection), + {stop, {shutdown, normal}, State}; + +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + downgrade= StateName, State) -> + {next_state, StateName, State, [{next_event, internal, Alert}]}; +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0, + StateName, #state{static_env = #static_env{role = Role}} = State) -> + Alert = Alert0#alert{role = opposite_role(Role)}, + handle_normal_shutdown(Alert, StateName, State), + {stop,{shutdown, peer_close}, State}; +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, internal}}, + ssl_options = #{log_level := LogLevel}} = State) -> + Alert = Alert0#alert{role = opposite_role(Role)}, + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, Alert), + handle_normal_shutdown(Alert, StateName, State), + {stop,{shutdown, peer_close}, State}; + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = #{log_level := LogLevel} + } = State0) -> + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + gen_statem:reply(From, {error, renegotiation_rejected}), + State = Connection:reinit_handshake_data(State0), + Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}); + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = #{log_level := LogLevel} + } = State0) -> + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + gen_statem:reply(From, {error, renegotiation_rejected}), + %% Go back to connection! + State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}), + Connection:next_event(connection, no_record, State); + +%% Gracefully log and ignore all other warning alerts +handle_alert(#alert{level = ?WARNING} = Alert, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = #{log_level := LogLevel}} = State) -> + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, + Alert#alert{role = opposite_role(Role)}), + Connection:next_event(StateName, no_record, State). +handle_trusted_certs_db(#state{ssl_options = + #{cacertfile := <<>>, cacerts := []}}) -> + %% No trusted certs specified + ok; +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + cert_db = CertDb}, + ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined -> + %% Certs provided as DER directly can not be shared + %% with other connections and it is safe to delete them when the connection ends. + ssl_pkix_db:remove_trusted_certs(Ref, CertDb); +handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) -> + %% Something went wrong early (typically cacertfile does not + %% exist) so there is nothing to handle + ok; +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + file_ref_db = RefDb}, + ssl_options = #{cacertfile := File}}) -> + case ssl_pkix_db:ref_count(Ref, RefDb, -1) of + 0 -> + ssl_manager:clean_cert_db(Ref, File); + _ -> + ok + end. + +maybe_invalidate_session({3, 4},_, _, _, _, _) -> + ok; +maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 -> + maybe_invalidate_session(Type, Role, Host, Port, Session). + +maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) -> + invalidate_session(Role, Host, Port, Session); +maybe_invalidate_session(_, _, _, _, _) -> + ok. + +terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) -> + %% Happens when user closes the connection using ssl:close/1 + %% we want to guarantee that Transport:close has been called + %% when ssl:close/1 returns unless it is a downgrade where + %% we want to guarantee that close alert is received before + %% returning. In both cases terminate has been run manually + %% before run by gen_statem which will end up here + ok; +terminate({shutdown, transport_closed} = Reason, + _StateName, #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined); +terminate({shutdown, own_alert}, _StateName, #state{ + static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> + handle_trusted_certs_db(State), + case application:get_env(ssl, alert_timeout) of + {ok, Timeout} when is_integer(Timeout) -> + Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); + _ -> + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) + end; +terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection, + transport_cb = Transport, + socket = Socket} + } = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined); +terminate(Reason, connection, #state{static_env = #static_env{ + protocol_cb = Connection, + transport_cb = Transport, + socket = Socket}, + connection_states = ConnectionStates, + ssl_options = #{padding_check := Check} + } = State) -> + handle_trusted_certs_db(State), + Alert = terminate_alert(Reason), + %% Send the termination ALERT if possible + catch (ok = Connection:send_alert_in_connection(Alert, State)), + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, + protocol_cb = Connection, + socket = Socket} + } = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined). + +%%==================================================================== +%% Log handling +%%==================================================================== +format_status(normal, [_, StateName, State]) -> + [{data, [{"State", {StateName, State}}]}]; +format_status(terminate, [_, StateName, State]) -> + SslOptions = (State#state.ssl_options), + NewOptions = SslOptions#{password => ?SECRET_PRINTOUT, + cert => ?SECRET_PRINTOUT, + cacerts => ?SECRET_PRINTOUT, + key => ?SECRET_PRINTOUT, + dh => ?SECRET_PRINTOUT, + psk_identity => ?SECRET_PRINTOUT, + srp_identity => ?SECRET_PRINTOUT}, + [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, + protocol_buffers = ?SECRET_PRINTOUT, + user_data_buffer = ?SECRET_PRINTOUT, + handshake_env = ?SECRET_PRINTOUT, + connection_env = ?SECRET_PRINTOUT, + session = ?SECRET_PRINTOUT, + ssl_options = NewOptions, + flight_buffer = ?SECRET_PRINTOUT} + }}]}]. +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +tls_connection_fsm(#{versions := [{3,4}]}) -> + tls_connection_1_3; +tls_connection_fsm(_) -> + tls_connection. + +dtls_connection_fsm(_) -> + dtls_connection. + +next_statem_state([Version], client) -> + case ssl:tls_version(Version) of + {3,4} -> + wait_sh; + _ -> + hello + end; +next_statem_state([Version], server) -> + case ssl:tls_version(Version) of + {3,4} -> + start; + _ -> + hello + end; +next_statem_state(_, _) -> + hello. + +call(FsmPid, Event) -> + try gen_statem:call(FsmPid, Event) + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{{shutdown, _},_} -> + {error, closed} + end. + +check_hostname(#state{ssl_options = SslOptions}, Hostname) -> + case is_sni_value(Hostname) of + true -> + case is_hostname_recognized(SslOptions, Hostname) of + true -> + valid; + false -> + %% We should send an alert but for interoperability reasons we + %% allow the connection to be established. + %% ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME) + unrecognized_name + end; + false -> + ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, + {sni_included_trailing_dot, Hostname}) + end. + +is_sni_value(Hostname) -> + case hd(lists:reverse(Hostname)) of + $. -> + false; + _ -> + true + end. + +is_hostname_recognized(#{sni_fun := undefined, + sni_hosts := SNIHosts}, Hostname) -> + proplists:is_defined(Hostname, SNIHosts); +is_hostname_recognized(_, _) -> + true. + +handle_sni_hostname(Hostname, + #state{static_env = #static_env{role = Role} = InitStatEnv0, + handshake_env = HsEnv, + connection_env = CEnv} = State0) -> + NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), + case NewOptions of + undefined -> + State0; + _ -> + {ok, #{cert_db_ref := Ref, + cert_db_handle := CertDbHandle, + fileref_db_handle := FileRefHandle, + session_cache := CacheHandle, + crl_db_info := CRLDbHandle, + private_key := Key, + dh_params := DHParams, + own_certificates := OwnCerts}} = + ssl_config:init(NewOptions, Role), + State0#state{ + session = State0#state.session#session{own_certificates = OwnCerts}, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, + connection_env = CEnv#connection_env{private_key = Key}, + ssl_options = NewOptions, + handshake_env = HsEnv#handshake_env{sni_hostname = Hostname, + diffie_hellman_params = DHParams} + } + end. + +update_ssl_options_from_sni(#{sni_fun := SNIFun, + sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) -> + SSLOption = + case SNIFun of + undefined -> + proplists:get_value(SNIHostname, + SNIHosts); + SNIFun -> + SNIFun(SNIHostname) + end, + case SSLOption of + undefined -> + undefined; + _ -> + ssl:handle_options(SSLOption, server, OrigSSLOptions) + end. + +set_sni_guided_cert_selection(#state{handshake_env = HsEnv0} = State, Bool) -> + HsEnv = HsEnv0#handshake_env{sni_guided_cert_selection = Bool}, + State#state{handshake_env = HsEnv}. + +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer; + Initiater == internal -> + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) -> + gen_statem:reply(From, ok), + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv, + start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined -> + gen_statem:reply(StartFrom, connected), + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}, + start_or_recv_from = undefined}; +ack_connection(State) -> + State. + +no_records(Extensions) -> + maps:map(fun(_, Value) -> + ssl_handshake:extension_value(Value) + end, Extensions). + +handle_active_option(false, connection = StateName, To, Reply, State) -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + +handle_active_option(_, connection = StateName, To, Reply, #state{static_env = #static_env{role = Role}, + connection_env = #connection_env{terminated = true}, + user_data_buffer = {_,0,_}} = State) -> + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_delivered), + handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop_and_reply,{shutdown, peer_close}, [{reply, To, Reply}]}; +handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, + user_data_buffer = {_,0,_}} = State0) -> + case Connection:next_event(StateName0, no_record, State0) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, _, _} = Stop -> + Stop + end; +handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) -> + %% Active once already set + {next_state, StateName, State, [{reply, To, Reply}]}; + +%% user_data_buffer nonempty +handle_active_option(_, StateName0, To, Reply, + #state{static_env = #static_env{protocol_cb = Connection}} = State0) -> + case read_application_data(<<>>, State0) of + {stop, _, _} = Stop -> + Stop; + {Record, State1} -> + %% Note: Renogotiation may cause StateName0 =/= StateName + case Connection:next_event(StateName0, Record, State1) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, _, _} = Stop -> + Stop + end + end. + +read_application_data(#state{ + socket_options = SocketOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) -> + read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead). + +%% Pick binary from queue front, if empty wait for more data +read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> + read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin); +read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) -> + 0 = BufferSize, % Assert + {no_record, State#state{socket_options = SocketOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {Front,BufferSize,Rear}}}; +read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> + [Bin|Front] = lists:reverse(Rear), + read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin). + +read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) -> + %% Done with this binary - get next + read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead); +read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) -> + %% Decode one packet from a binary + case get_data(SocketOpts0, BytesToRead, Bin0) of + {ok, Data, Bin} -> % Send data + BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)), + read_application_data_deliver( + State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data); + {more, undefined} -> + %% We need more data, do not know how much + if + byte_size(Bin0) < BufferSize0 -> + %% We have more data in the buffer besides the first binary - concatenate all and retry + Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), + read_application_data_bin( + State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin); + true -> + %% All data is in the first binary, no use to retry - wait for more + {no_record, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}} + end; + {more, Size} when Size =< BufferSize0 -> + %% We have a packet in the buffer - collect it in a binary and decode + {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]), + Bin = iolist_to_binary(Data), + read_application_data_bin( + State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin); + {more, _Size} -> + %% We do not have a packet in the buffer - wait for more + {no_record, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; + passive -> + {no_record, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; + {error,_Reason} -> + %% Invalid packet in packet mode + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + trackers = Trackers}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}} = State, + Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), + deliver_packet_error( + Connection:pids(State), Transport, Socket, SocketOpts0, + Buffer, Pid, RecvFrom, Trackers, Connection), + {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Buffer],BufferSize0,[]}}} + end. + +read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) -> + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + trackers = Trackers}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}} = State, + SocketOpts = + deliver_app_data( + Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection), + if + SocketOpts#socket_options.active =:= false -> + %% Passive mode, wait for active once or recv + {no_record, + State#state{ + user_data_buffer = {Front,BufferSize,Rear}, + start_or_recv_from = undefined, + bytes_to_read = undefined, + socket_options = SocketOpts + }}; + true -> %% Try to deliver more data + %% Process early data if it is accepted. + case (State#state.handshake_env)#handshake_env.early_data_accepted of + false -> + read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined); + true -> + read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, undefined) + end + end. + + +read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) -> + read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin); +read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) -> + BufferSize = 0, + {Front,BufferSize,Rear}; +read_application_dist_data(DHandle, [], BufferSize, Rear) -> + [Bin|Front] = lists:reverse(Rear), + read_application_dist_data(DHandle, Front, BufferSize, [], Bin). +%% +read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> + case Bin0 of + %% + %% START Optimization + %% It is cheaper to match out several packets in one match operation than to loop for each + <<SizeA:32, DataA:SizeA/binary, + SizeB:32, DataB:SizeB/binary, + SizeC:32, DataC:SizeC/binary, + SizeD:32, DataD:SizeD/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD -> + %% We have 4 complete packets in the first binary + erlang:dist_ctrl_put_data(DHandle, DataA), + erlang:dist_ctrl_put_data(DHandle, DataB), + erlang:dist_ctrl_put_data(DHandle, DataC), + erlang:dist_ctrl_put_data(DHandle, DataD), + read_application_dist_data( + DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest); + <<SizeA:32, DataA:SizeA/binary, + SizeB:32, DataB:SizeB/binary, + SizeC:32, DataC:SizeC/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB, 0 < SizeC -> + %% We have 3 complete packets in the first binary + erlang:dist_ctrl_put_data(DHandle, DataA), + erlang:dist_ctrl_put_data(DHandle, DataB), + erlang:dist_ctrl_put_data(DHandle, DataC), + read_application_dist_data( + DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest); + <<SizeA:32, DataA:SizeA/binary, + SizeB:32, DataB:SizeB/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB -> + %% We have 2 complete packets in the first binary + erlang:dist_ctrl_put_data(DHandle, DataA), + erlang:dist_ctrl_put_data(DHandle, DataB), + read_application_dist_data( + DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest); + %% END Optimization + %% + %% Basic one packet code path + <<Size:32, Data:Size/binary, Rest/binary>> -> + %% We have a complete packet in the first binary + 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), + read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest); + <<Size:32, FirstData/binary>> when 4+Size =< BufferSize -> + %% We have a complete packet in the buffer + %% - fetch the missing content from the buffer front + {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]), + 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), + read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear); + <<Bin/binary>> -> + %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we + %% match out the whole binary which will trick the optimization into keeping the match context + %% for the first binary contains complete packet code above + case Bin of + <<_Size:32, _InsufficientData/binary>> -> + %% We have a length field in the first binary but there is not enough data + %% in the buffer to form a complete packet - await more data + {[Bin|Front0],BufferSize,Rear0}; + <<IncompleteLengthField/binary>> when 4 < BufferSize -> + %% We do not have a length field in the first binary but the buffer + %% contains enough data to maybe form a packet + %% - fetch a tiny binary from the buffer front to complete the length field + {LengthField,Front,Rear} = + case IncompleteLengthField of + <<>> -> + iovec_from_front(4, Front0, Rear0, []); + _ -> + iovec_from_front( + 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]) + end, + LengthBin = iolist_to_binary(LengthField), + read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin); + <<IncompleteLengthField/binary>> -> + %% We do not have enough data in the buffer to even form a length field - await more data + case IncompleteLengthField of + <<>> -> + {Front0,BufferSize,Rear0}; + _ -> + {[IncompleteLengthField|Front0],BufferSize,Rear0} + end + end + end. + +iovec_from_front(0, Front, Rear, Acc) -> + {lists:reverse(Acc),Front,Rear}; +iovec_from_front(Size, [], Rear, Acc) -> + case Rear of + %% Avoid lists:reverse/1 for simple cases. + %% Case clause for [] to avoid infinite loop. + [_] -> + iovec_from_front(Size, Rear, [], Acc); + [Bin2,Bin1] -> + iovec_from_front(Size, [Bin1,Bin2], [], Acc); + [Bin3,Bin2,Bin1] -> + iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc); + [_,_,_|_] = Rear -> + iovec_from_front(Size, lists:reverse(Rear), [], Acc) + end; +iovec_from_front(Size, [Bin|Front], Rear, []) -> + case Bin of + <<Last:Size/binary>> -> % Just enough + {[Last],Front,Rear}; + <<Last:Size/binary, Rest/binary>> -> % More than enough, split here + {[Last],[Rest|Front],Rear}; + <<>> -> % Not enough, skip empty binaries + iovec_from_front(Size, Front, Rear, []); + <<_/binary>> -> % Not enough + BinSize = byte_size(Bin), + iovec_from_front(Size - BinSize, Front, Rear, [Bin]) + end; +iovec_from_front(Size, [Bin|Front], Rear, Acc) -> + case Bin of + <<Last:Size/binary>> -> % Just enough + {lists:reverse(Acc, [Last]),Front,Rear}; + <<Last:Size/binary, Rest/binary>> -> % More than enough, split here + {lists:reverse(Acc, [Last]),[Rest|Front],Rear}; + <<>> -> % Not enough, skip empty binaries + iovec_from_front(Size, Front, Rear, Acc); + <<_/binary>> -> % Not enough + BinSize = byte_size(Bin), + iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc]) + end. +%% Picks ClientData +get_data(#socket_options{active=false}, undefined, _Bin) -> + %% Recv timed out save buffer data until next recv + passive; +get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin) + when Raw =:= raw; Raw =:= 0 -> %% Raw Mode + case Bin of + <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 -> + %% Active true or once, or passive mode recv(0) + {ok, Bin, <<>>}; + <<Data:BytesToRead/binary, Rest/binary>> -> + %% Passive Mode, recv(Bytes) + {ok, Data, Rest}; + <<_/binary>> -> + %% Passive Mode not enough data + {more, BytesToRead} + end; +get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) -> + PacketOpts = [{packet_size, Size}], + decode_packet(Type, Bin, PacketOpts). + +decode_packet({http, headers}, Buffer, PacketOpts) -> + decode_packet(httph, Buffer, PacketOpts); +decode_packet({http_bin, headers}, Buffer, PacketOpts) -> + decode_packet(httph_bin, Buffer, PacketOpts); +decode_packet(Type, Buffer, PacketOpts) -> + erlang:decode_packet(Type, Buffer, PacketOpts). + +%% Just like with gen_tcp sockets, an ssl socket that has been configured with +%% {packet, http} (or {packet, http_bin}) will automatically switch to expect +%% HTTP headers after it sees a HTTP Request or HTTP Response line. We +%% represent the current state as follows: +%% #socket_options.packet =:= http: Expect a HTTP Request/Response line +%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers +%% Note that if the user has explicitly configured the socket to expect +%% HTTP headers using the {packet, httph} option, we don't do any automatic +%% switching of states. +deliver_app_data(CPids, Transport, Socket, + #socket_options{active=Active, packet=Type} = SOpts, + Data, Pid, From, Trackers, Connection) -> + send_or_reply(Active, Pid, From, + format_reply(CPids, Transport, Socket, + SOpts, Data, Trackers, Connection)), + SO = + case Data of + {P, _, _, _} + when ((P =:= http_request) or (P =:= http_response)), + ((Type =:= http) or (Type =:= http_bin)) -> + SOpts#socket_options{packet={Type, headers}}; + http_eoh when tuple_size(Type) =:= 2 -> + %% End of headers - expect another Request/Response line + {Type1, headers} = Type, + SOpts#socket_options{packet=Type1}; + _ -> + SOpts + end, + case Active of + once -> + SO#socket_options{active=false}; + 1 -> + send_user(Pid, + format_passive(CPids, Transport, + Socket, Trackers, Connection)), + SO#socket_options{active=false}; + N when is_integer(N) -> + SO#socket_options{active=N - 1}; + _ -> + SO + end. + +format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet, + header = Header}, Data, _, _) -> + {ok, do_format_reply(Mode, Packet, Header, Data)}; +format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, + header = Header}, Data, Trackers, Connection) -> + {ssl, Connection:socket(CPids, Transport, Socket, Trackers), + do_format_reply(Mode, Packet, Header, Data)}. + +deliver_packet_error(CPids, Transport, Socket, + SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) -> + send_or_reply(Active, Pid, From, format_packet_error(CPids, + Transport, Socket, SO, Data, Trackers, Connection)). + +format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> + {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; +format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode}, + Data, Trackers, Connection) -> + {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers), + {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. + +do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode + header(N, Data); +do_format_reply(binary, _, _, Data) -> + Data; +do_format_reply(list, Packet, _, Data) + when Packet == http; Packet == {http, headers}; + Packet == http_bin; Packet == {http_bin, headers}; + Packet == httph; Packet == httph_bin -> + Data; +do_format_reply(list, _,_, Data) -> + binary_to_list(Data). + +format_passive(CPids, Transport, Socket, Trackers, Connection) -> + {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}. + +header(0, <<>>) -> + <<>>; +header(_, <<>>) -> + []; +header(0, Binary) -> + Binary; +header(N, Binary) -> + <<?BYTE(ByteN), NewBinary/binary>> = Binary, + [ByteN | header(N-1, NewBinary)]. + +send_or_reply(false, _Pid, From, Data) when From =/= undefined -> + gen_statem:reply(From, Data); +send_or_reply(false, Pid, undefined, _) when is_pid(Pid) -> + ok; +send_or_reply(_, no_pid, _, _) -> + ok; +send_or_reply(_, Pid, _, Data) -> + send_user(Pid, Data). + +send_user(Pid, Msg) -> + Pid ! Msg, + ok. + +alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection); +alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection). + +alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection). + +alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined -> + %% If there is an outstanding ssl_accept | recv + %% From will be defined and send_or_reply will + %% send the appropriate error message. + ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName), + send_or_reply(Active, Pid, From, {error, ReasonCode}); +alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) -> + case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of + closed -> + send_or_reply(Active, Pid, From, + {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)}); + ReasonCode -> + send_or_reply(Active, Pid, From, + {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode}) + end. + +log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> + ssl_logger:log(notice, Level, #{protocol => ProtocolName, + role => Role, + statename => StateName, + alert => Alert, + alerter => own}, Alert#alert.where); +log_alert(Level, Role, ProtocolName, StateName, Alert) -> + ssl_logger:log(notice, Level, #{protocol => ProtocolName, + role => Role, + statename => StateName, + alert => Alert, + alerter => peer}, Alert#alert.where). +terminate_alert(normal) -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert({Reason, _}) when Reason == close; + Reason == shutdown -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert(_) -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR). + +invalidate_session(client, Host, Port, Session) -> + ssl_manager:invalidate_session(Host, Port, Session); +invalidate_session(server, _, _, _) -> + ok. + +opposite_role(client) -> + server; +opposite_role(server) -> + client. + +connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname, + resumption = Resumption}, + session = #session{session_id = SessionId, + cipher_suite = CipherSuite, + srp_username = SrpUsername, + ecc = ECCCurve} = Session, + connection_states = #{current_write := CurrentWrite}, + connection_env = #connection_env{negotiated_version = {_,_} = Version}, + ssl_options = #{protocol := Protocol} = Opts}) -> + RecordCB = record_cb(Protocol), + CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + IsNamedCurveSuite = lists:member(KexAlg, + [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]), + CurveInfo = case ECCCurve of + {namedCurve, Curve} when IsNamedCurveSuite -> + [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}]; + _ -> + [] + end, + MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of + MaxFragmentLength when is_integer(MaxFragmentLength) -> + [{max_fragment_length, MaxFragmentLength}]; + _ -> + [] + end, + [{protocol, RecordCB:protocol_version(Version)}, + {session_id, SessionId}, + {session_data, term_to_binary(Session)}, + {session_resumption, Resumption}, + {selected_cipher_suite, CipherSuiteDef}, + {sni_hostname, SNIHostname}, + {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts). + +security_info(#state{connection_states = ConnectionStates, + static_env = #static_env{role = Role}, + ssl_options = #{keep_secrets := KeepSecrets}}) -> + ReadState = ssl_record:current_connection_state(ConnectionStates, read), + #{security_parameters := + #security_parameters{client_random = ClientRand, + server_random = ServerRand, + master_secret = MasterSecret, + application_traffic_secret = AppTrafSecretRead, + client_early_data_secret = ServerEarlyData + }} = ReadState, + BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}], + if KeepSecrets =/= true -> + BaseSecurityInfo; + true -> + #{security_parameters := + #security_parameters{application_traffic_secret = AppTrafSecretWrite, + client_early_data_secret = ClientEarlyData + }} = + ssl_record:current_connection_state(ConnectionStates, write), + if Role == server -> + if ServerEarlyData =/= undefined -> + [{server_traffic_secret_0, AppTrafSecretWrite}, + {client_traffic_secret_0, AppTrafSecretRead}, + {client_early_data_secret, ServerEarlyData}]; + true -> + [{server_traffic_secret_0, AppTrafSecretWrite}, + {client_traffic_secret_0, AppTrafSecretRead}] + end; + true -> + if ClientEarlyData =/= undefined -> + [{client_traffic_secret_0, AppTrafSecretWrite}, + {server_traffic_secret_0, AppTrafSecretRead}, + {client_early_data_secret, ClientEarlyData}]; + true -> + [{client_traffic_secret_0, AppTrafSecretWrite}, + {server_traffic_secret_0, AppTrafSecretRead}] + end + end ++ + case ReadState of + #{client_handshake_traffic_secret := ClientHSTrafficSecret, + server_handshake_traffic_secret := ServerHSTrafficSecret} -> + [{client_handshake_traffic_secret, ClientHSTrafficSecret}, + {server_handshake_traffic_secret, ServerHSTrafficSecret}]; + _ -> + [] + end ++ BaseSecurityInfo + end. + +record_cb(tls) -> + tls_record; +record_cb(dtls) -> + dtls_record. + +get_socket_opts(_, _,_,[], _, Acc) -> + {ok, Acc}; +get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, + [{mode, SockOpts#socket_options.mode} | Acc]); +get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) -> + case SockOpts#socket_options.packet of + {Type, headers} -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); + Type -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) + end; +get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, + [{header, SockOpts#socket_options.header} | Acc]); +get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, + [{active, SockOpts#socket_options.active} | Acc]); +get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) -> + case Connection:getopts(Transport, Socket, [Tag]) of + {ok, [Opt]} -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]); + {error, Reason} -> + {error, {options, {socket_options, Tag, Reason}}} + end; +get_socket_opts(_,_, _,Opts, _,_) -> + {error, {options, {socket_options, Opts, function_clause}}}. + +set_socket_opts(_,_,_, [], SockOpts, []) -> + {ok, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) -> + %% Set non emulated options + try ConnectionCb:setopts(Transport, Socket, Other) of + ok -> + {ok, SockOpts}; + {error, InetError} -> + {{error, {options, {socket_options, Other, InetError}}}, SockOpts} + catch + _:Error -> + %% So that inet behavior does not crash our process + {{error, {options, {socket_options, Other, Error}}}, SockOpts} + end; + +set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) + when Mode == list; Mode == binary -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{mode = Mode}, Other); +set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) + when Packet == raw; + Packet == 0; + Packet == 1; + Packet == 2; + Packet == 4; + Packet == asn1; + Packet == cdr; + Packet == sunrm; + Packet == fcgi; + Packet == tpkt; + Packet == line; + Packet == http; + Packet == httph; + Packet == http_bin; + Packet == httph_bin -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{packet = Packet}, Other); +set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other) + when is_integer(Header) -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{header = Header}, Other); +set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) -> + {{error,{options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other) + when Active == once; + Active == true; + Active == false -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{active = Active}, Other); +set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts], + SockOpts=#socket_options{active = Active0}, Other) + when Active1 >= -32768, Active1 =< 32767 -> + Active = if + is_integer(Active0), Active0 + Active1 < -32768 -> + error; + is_integer(Active0), Active0 + Active1 =< 0 -> + false; + is_integer(Active0), Active0 + Active1 > 32767 -> + error; + Active1 =< 0 -> + false; + is_integer(Active0) -> + Active0 + Active1; + true -> + Active1 + end, + case Active of + error -> + {{error, {options, {socket_options, Opt}} }, SockOpts}; + _ -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{active = Active}, Other) + end; +set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}} }, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]). +ssl_options_list(SslOptions) -> + L = maps:to_list(SslOptions), + ssl_options_list(L, []). + +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. + +ssl_options_list([], Acc) -> + lists:reverse(Acc); +%% Skip internal options, only return user options +ssl_options_list([{protocol, _}| T], Acc) -> + ssl_options_list(T, Acc); +ssl_options_list([{erl_dist, _}|T], Acc) -> + ssl_options_list(T, Acc); +ssl_options_list([{renegotiate_at, _}|T], Acc) -> + ssl_options_list(T, Acc); +ssl_options_list([{max_fragment_length, _}|T], Acc) -> + %% skip max_fragment_length from options since it is taken above from connection_states + ssl_options_list(T, Acc); +ssl_options_list([{ciphers = Key, Value}|T], Acc) -> + ssl_options_list(T, + [{Key, lists:map( + fun(Suite) -> + ssl_cipher_format:suite_bin_to_map(Suite) + end, Value)} + | Acc]); +ssl_options_list([{Key, Value}|T], Acc) -> + ssl_options_list(T, [{Key, Value} | Acc]). + +%% Maybe add NSS keylog info according to +%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format +maybe_add_keylog(Info) -> + maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info). + +maybe_add_keylog({_, 'tlsv1.2'}, Info) -> + try + {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), + {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info), + ClientRandom = binary:decode_unsigned(ClientRandomBin), + MasterSecret = binary:decode_unsigned(MasterSecretBin), + Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])], + Info ++ [{keylog,Keylog}] + catch + _Cxx:_Exx -> + Info + end; +maybe_add_keylog({_, 'tlsv1.3'}, Info) -> + try + {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), + {client_traffic_secret_0, ClientTrafficSecret0Bin} = lists:keyfind(client_traffic_secret_0, 1, Info), + {server_traffic_secret_0, ServerTrafficSecret0Bin} = lists:keyfind(server_traffic_secret_0, 1, Info), + {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info), + {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info), + {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info), + ClientRandom = binary:decode_unsigned(ClientRandomBin), + ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf), + ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf), + ClientHSecret = keylog_secret(ClientHSecretBin, Prf), + ServerHSecret = keylog_secret(ServerHSecretBin, Prf), + Keylog0 = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret, + io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret, + io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0, + io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0], + Keylog = case lists:keyfind(client_early_data_secret, 1, Info) of + {client_early_data_secret, EarlySecret} -> + ClientEarlySecret = keylog_secret(EarlySecret, Prf), + [io_lib:format("CLIENT_EARLY_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientEarlySecret + | Keylog0]; + _ -> + Keylog0 + end, + Info ++ [{keylog,Keylog}] + catch + _Cxx:_Exx -> + Info + end; +maybe_add_keylog(_, Info) -> + Info. + +keylog_secret(SecretBin, sha256) -> + io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]); +keylog_secret(SecretBin, sha384) -> + io_lib:format("~96.16.0B", [binary:decode_unsigned(SecretBin)]); +keylog_secret(SecretBin, sha512) -> + io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]). + +maybe_generate_client_shares(#{versions := [Version|_], + supported_groups := + #supported_groups{ + supported_groups = [Group|_]}}) + when Version =:= {3,4} -> + %% Generate only key_share entry for the most preferred group + ssl_cipher:generate_client_shares([Group]); +maybe_generate_client_shares(_) -> + undefined. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 7cc4a0a7c3..de5490d232 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -43,15 +43,15 @@ -type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} | #client_key_exchange{} | #finished{} | #certificate_verify{} | - #hello_request{} | #next_protocol{}. + #hello_request{} | #next_protocol{} | #end_of_early_data{}. %% Create handshake messages -export([hello_request/0, server_hello/4, server_hello_done/0, certificate/4, client_certificate_verify/6, certificate_request/5, key_exchange/3, - finished/5, next_protocol/1]). + finished/5, next_protocol/1, digitally_signed/5]). %% Handle handshake messages --export([certify/7, certificate_verify/6, verify_signature/5, +-export([certify/9, certificate_verify/6, verify_signature/5, master_secret/4, server_key_exchange_hash/2, verify_connection/6, init_handshake_history/0, update_handshake_history/2, verify_server_key/5, select_version/3, select_supported_version/2, extension_value/1 @@ -68,22 +68,23 @@ %% Cipher suites handling -export([available_suites/2, available_signature_algs/2, available_signature_algs/4, - cipher_suites/3, prf/6, select_session/11, supported_ecc/1, + cipher_suites/3, prf/6, select_session/9, supported_ecc/1, premaster_secret/2, premaster_secret/3, premaster_secret/4]). %% Extensions handling --export([client_hello_extensions/7, - handle_client_hello_extensions/9, %% Returns server hello extensions - handle_server_hello_extensions/9, select_curve/2, select_curve/3, +-export([client_hello_extensions/8, + handle_client_hello_extensions/10, %% Returns server hello extensions + handle_server_hello_extensions/10, select_curve/2, select_curve/3, select_hashsign/4, select_hashsign/5, select_hashsign_algs/3, empty_extensions/2, add_server_share/3, - add_alpn/2, add_selected_version/1, decode_alpn/1 + add_alpn/2, add_selected_version/1, decode_alpn/1, max_frag_enum/1 ]). -export([get_cert_params/1, + select_own_cert/1, server_name/3, - validation_fun_and_state/10, - handle_path_validation_error/7]). + validation_fun_and_state/4, + path_validation_alert/1]). %%==================================================================== %% Create handshake messages @@ -125,30 +126,33 @@ server_hello_done() -> #server_hello_done{}. %%-------------------------------------------------------------------- --spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. +-spec certificate([der_cert()] | undefined, db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. %% %% Description: Creates a certificate message. %%-------------------------------------------------------------------- -certificate(OwnCert, CertDbHandle, CertDbRef, client) -> +certificate(undefined, _, _, client) -> + %% If no suitable certificate is available, the client + %% SHOULD send a certificate message containing no + %% certificates. (chapter 7.4.6. RFC 4346) + #certificate{asn1_certificates = []}; +certificate([OwnCert], CertDbHandle, CertDbRef, client) -> Chain = case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, CertChain} -> CertChain; {error, _} -> - %% If no suitable certificate is available, the client - %% SHOULD send a certificate message containing no - %% certificates. (chapter 7.4.6. RFC 4346) - [] - end, + certificate(undefined, CertDbHandle, CertDbRef, client) + end, #certificate{asn1_certificates = Chain}; - -certificate(OwnCert, CertDbHandle, CertDbRef, server) -> +certificate([OwnCert], CertDbHandle, CertDbRef, server) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, Chain} -> #certificate{asn1_certificates = Chain}; {error, Error} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error}) - end. + end; +certificate([_, _ |_] = Chain, _, _, _) -> + #certificate{asn1_certificates = Chain}. %%-------------------------------------------------------------------- -spec client_certificate_verify(undefined | der_cert(), binary(), @@ -162,7 +166,7 @@ client_certificate_verify(undefined, _, _, _, _, _) -> ignore; client_certificate_verify(_, _, _, _, undefined, _) -> ignore; -client_certificate_verify(OwnCert, MasterSecret, Version, +client_certificate_verify([OwnCert|_], MasterSecret, Version, {HashAlgo, SignAlgo}, PrivateKey, {Handshake, _}) -> case public_key:pkix_is_fixed_dh_cert(OwnCert) of @@ -171,7 +175,7 @@ client_certificate_verify(OwnCert, MasterSecret, Version, false -> Hashes = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), - Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey), + Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo), #certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}} end. @@ -337,37 +341,28 @@ next_protocol(SelectedProtocol) -> %%==================================================================== %%-------------------------------------------------------------------- -spec certify(#certificate{}, db_handle(), certdb_ref(), ssl_options(), term(), - client | server, inet:hostname() | inet:ip_address()) -> {der_cert(), public_key_info()} | #alert{}. + client | server, inet:hostname() | inet:ip_address(), + ssl_record:ssl_version(), map()) -> {der_cert(), public_key_info()} | #alert{}. %% %% Description: Handles a certificate handshake message %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, #{server_name_indication := ServerNameIndication, - partial_chain := PartialChain, - verify_fun := VerifyFun, - customize_hostname_check := CustomizeHostnameCheck, - crl_check := CrlCheck, - log_level := Level, - depth := Depth} = Opts, CRLDbHandle, Role, Host) -> - + partial_chain := PartialChain} = SSlOptions, + CRLDbHandle, Role, Host, Version, CertExt) -> ServerName = server_name(ServerNameIndication, Host, Role), - [PeerCert | ChainCerts ] = ASN1Certs, + [PeerCert | _ChainCerts ] = ASN1Certs, try - {TrustedCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, - PartialChain), - ValidationFunAndState = validation_fun_and_state(VerifyFun, Role, - CertDbHandle, CertDbRef, ServerName, - CustomizeHostnameCheck, - CrlCheck, CRLDbHandle, CertPath, Level), - Options = [{max_path_length, Depth}, - {verify_fun, ValidationFunAndState}], - case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; + PathsAndAnchors = + ssl_certificate:trusted_cert_and_paths(ASN1Certs, CertDbHandle, CertDbRef, + PartialChain), + + case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SSlOptions, CertExt) of + {ok, {PublicKeyInfo, _}} -> + {PeerCert, PublicKeyInfo}; {error, Reason} -> - handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options, - CertDbHandle, CertDbRef) + path_validation_alert(Reason) end catch error:{_,{error, {asn1, Asn1Reason}}} -> @@ -400,27 +395,34 @@ certificate_verify(Signature, PublicKeyInfo, Version, %% %% Description: Checks that a public_key signature is valid. %%-------------------------------------------------------------------- -verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) -> - true; -verify_signature({3, Minor}, Hash, {HashAlgo, rsa_pss_rsae}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) +verify_signature({3, 4}, Hash, {HashAlgo, SignAlgo}, Signature, + {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae; + SignAlgo == rsa_pss_pss -> + Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), + public_key:verify(Hash, HashAlgo, Signature, PubKey, Options); +verify_signature({3, 3}, Hash, {HashAlgo, SignAlgo}, Signature, + {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae; + SignAlgo == rsa_pss_pss -> + Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), + public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options); +verify_signature({3, Minor}, Hash, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams}) when Minor >= 3 -> - public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]); -verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) - when Minor >= 3 -> - public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey); -verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) -> + Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), + public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options); +verify_signature({3, Minor}, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) when Minor =< 2 -> case public_key:decrypt_public(Signature, PubKey, [{rsa_pad, rsa_pkcs1_padding}]) of Hash -> true; - _ -> false + _ -> false end; -verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) -> - public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); +verify_signature({3, 4}, Hash, {HashAlgo, _SignAlgo}, Signature, {?'id-ecPublicKey', PubKey, PubKeyParams}) -> + public_key:verify(Hash, HashAlgo, Signature, {PubKey, PubKeyParams}); verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> + public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); +verify_signature({3, Minor}, _Hash, {_HashAlgo, anon}, _Signature, _) when Minor =< 3 -> + true; +verify_signature({3, Minor}, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) when Minor =< 3-> public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}). %%-------------------------------------------------------------------- @@ -683,6 +685,11 @@ encode_extensions([#signature_algorithms_cert{ Len = ListLen + 2, encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>); +encode_extensions([#sni{hostname = ""} | Rest], Acc) -> + HostnameBin = <<>>, + encode_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(0), + HostnameBin/binary, + Acc/binary>>); encode_extensions([#sni{hostname = Hostname} | Rest], Acc) -> HostLen = length(Hostname), HostnameBin = list_to_binary(Hostname), @@ -695,6 +702,10 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) -> ?BYTE(?SNI_NAMETYPE_HOST_NAME), ?UINT16(HostLen), HostnameBin/binary, Acc/binary>>); +encode_extensions([#max_frag_enum{enum = MaxFragEnum} | Rest], Acc) -> + ExtLength = 1, + encode_extensions(Rest, <<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(ExtLength), ?BYTE(MaxFragEnum), + Acc/binary>>); encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) -> Versions = encode_versions(Versions0), VerLen = byte_size(Versions), @@ -728,10 +739,19 @@ encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) -> ExtLen = KEModesLen + 1, encode_extensions(Rest, <<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), ?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>); +encode_extensions([ + #certificate_status_request{ + status_type = StatusRequest, + request = Request} | Rest], Acc) -> + CertStatusReq = encode_cert_status_req(StatusRequest, Request), + Len = byte_size(CertStatusReq), + encode_extensions( + Rest, <<?UINT16(?STATUS_REQUEST), ?UINT16(Len), + CertStatusReq/binary, Acc/binary>>); encode_extensions([#pre_shared_key_client_hello{ offered_psks = #offered_psks{ identities = Identities0, - binders = Binders0} = _PSKs} | Rest], Acc) -> + binders = Binders0}} | Rest], Acc) -> Identities = encode_psk_identities(Identities0), Binders = encode_psk_binders(Binders0), Len = byte_size(Identities) + byte_size(Binders), @@ -743,8 +763,51 @@ encode_extensions([#pre_shared_key_client_hello{ ?UINT16(Len), Identities/binary, Binders/binary>>); encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) -> encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT), - ?UINT16(2), ?UINT16(Identity), Acc/binary>>). + ?UINT16(2), ?UINT16(Identity), Acc/binary>>); +encode_extensions([#cookie{cookie = Cookie} | Rest], Acc) -> + CookieLen = byte_size(Cookie), + Len = CookieLen + 2, + encode_extensions(Rest, <<?UINT16(?COOKIE_EXT), ?UINT16(Len), ?UINT16(CookieLen), + Cookie/binary, Acc/binary>>); +encode_extensions([#early_data_indication{} | Rest], Acc) -> + encode_extensions(Rest, <<?UINT16(?EARLY_DATA_EXT), + ?UINT16(0), Acc/binary>>); +encode_extensions([#early_data_indication_nst{indication = MaxSize} | Rest], Acc) -> + encode_extensions(Rest, <<?UINT16(?EARLY_DATA_EXT), + ?UINT16(4), ?UINT32(MaxSize), Acc/binary>>). + +encode_cert_status_req( + StatusType, + #ocsp_status_request{ + responder_id_list = ResponderIDList, + request_extensions = ReqExtns}) -> + ResponderIDListBin = encode_responderID_list(ResponderIDList), + ReqExtnsBin = encode_request_extensions(ReqExtns), + <<?BYTE(StatusType), ResponderIDListBin/binary, ReqExtnsBin/binary>>. + +encode_responderID_list([]) -> + <<?UINT16(0)>>; +encode_responderID_list(List) -> + do_encode_responderID_list(List, <<>>). +%% ResponderID is DER-encoded ASN.1 type defined in RFC6960. +do_encode_responderID_list([], Acc) -> + Len = byte_size(Acc), + <<?UINT16(Len), Acc/binary>>; +do_encode_responderID_list([Responder | Rest], Acc) + when is_binary(Responder) -> + Len = byte_size(Responder), + do_encode_responderID_list( + Rest, <<Acc/binary, ?UINT16(Len), Responder/binary>>). + +%% Extensions are DER-encoded ASN.1 type defined in RFC6960 following +%% extension model employed in X.509 version 3 certificates(RFC5280). +encode_request_extensions([]) -> + <<?UINT16(0)>>; +encode_request_extensions(Extns) when is_list(Extns) -> + ExtnBin = public_key:der_encode('Extensions', Extns), + Len = byte_size(ExtnBin), + <<?UINT16(Len), ExtnBin/binary>>. encode_client_protocol_negotiation(undefined, _) -> undefined; @@ -757,7 +820,8 @@ encode_protocols_advertised_on_server(undefined) -> undefined; encode_protocols_advertised_on_server(Protocols) -> - #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. + #next_protocol_negotiation{ + extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. %%==================================================================== %% Decode handshake @@ -796,6 +860,14 @@ decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32 extensions = HelloExtensions}; decode_handshake(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) -> #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; +%% RFC 6066, servers return a certificate response along with their certificate +%% by sending a "CertificateStatus" message immediately after the "Certificate" +%% message and before any "ServerKeyExchange" or "CertificateRequest" messages. +decode_handshake(_Version, ?CERTIFICATE_STATUS, <<?BYTE(?CERTIFICATE_STATUS_TYPE_OCSP), + ?UINT24(Len), ASN1OcspResponse:Len/binary>>) -> + #certificate_status{ + status_type = ?CERTIFICATE_STATUS_TYPE_OCSP, + response = ASN1OcspResponse}; decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> #server_key_exchange{exchange_keys = Keys}; decode_handshake({Major, Minor}, ?CERTIFICATE_REQUEST, @@ -954,18 +1026,14 @@ cipher_suites(Suites, true) -> %% %% Description: use the TLS PRF to generate key material %%-------------------------------------------------------------------- -prf({3,0}, _, _, _, _, _) -> - {error, undefined}; prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> {ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}. -select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve0} = +select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, #session{ecc = ECCCurve0} = Session, Version, - #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = SslOpts, - Cache, CacheCb, Cert) -> - {SessionId, Resumed} = ssl_session:server_select_session(Version, Port, SuggestedSessionId, - SslOpts, Cert, - Cache, CacheCb), + #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = SslOpts,Cert) -> + {SessionId, Resumed} = ssl_session:server_select_session(Version, SessIdTracker, SuggestedSessionId, + SslOpts, Cert), case Resumed of undefined -> Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), @@ -1081,16 +1149,18 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) -> %% Extensions handling %%==================================================================== client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare, - TicketData) -> + TicketData, OcspNonce) -> HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation), HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts), - maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare, TicketData). + HelloExtensions2 = maybe_add_certificate_status_request(Version, SslOpts, OcspNonce, HelloExtensions1), + maybe_add_tls13_extensions(Version, HelloExtensions2, SslOpts, KeyShare, TicketData). add_tls12_extensions(_Version, #{alpn_advertised_protocols := AlpnAdvertisedProtocols, next_protocol_selector := NextProtocolSelector, - server_name_indication := ServerNameIndication} = SslOpts, + server_name_indication := ServerNameIndication, + max_fragment_length := MaxFragmentLength} = SslOpts, ConnectionStates, Renegotiation) -> SRP = srp_user(SslOpts), @@ -1101,7 +1171,8 @@ add_tls12_extensions(_Version, next_protocol_negotiation => encode_client_protocol_negotiation(NextProtocolSelector, Renegotiation), - sni => sni(ServerNameIndication) + sni => sni(ServerNameIndication), + max_frag_enum => max_frag_enum(MaxFragmentLength) }. @@ -1153,6 +1224,31 @@ maybe_add_tls13_extensions({3,4}, maybe_add_tls13_extensions(_, HelloExtensions, _, _, _) -> HelloExtensions. +maybe_add_certificate_status_request( + _Version, #{ocsp_stapling := false}, _OcspNonce, HelloExtensions) -> + HelloExtensions; +maybe_add_certificate_status_request( + _Version, #{ocsp_stapling := true, + ocsp_responder_certs := OcspResponderCerts}, + OcspNonce, HelloExtensions) -> + OcspResponderList = get_ocsp_responder_list(OcspResponderCerts), + OcspRequestExtns = public_key:ocsp_extensions(OcspNonce), + Req = #ocsp_status_request{responder_id_list = OcspResponderList, + request_extensions = OcspRequestExtns}, + CertStatusReqExtn = #certificate_status_request{ + status_type = ?CERTIFICATE_STATUS_TYPE_OCSP, + request = Req + }, + HelloExtensions#{status_request => CertStatusReqExtn}. + +get_ocsp_responder_list(ResponderCerts) -> + get_ocsp_responder_list(ResponderCerts, []). + +get_ocsp_responder_list([], Acc) -> + Acc; +get_ocsp_responder_list([ResponderCert | T], Acc) -> + get_ocsp_responder_list( + T, [public_key:ocsp_responder_id(ResponderCert) | Acc]). %% TODO: Add support for PSK key establishment @@ -1219,7 +1315,9 @@ get_identities_binders(TicketData) -> %% get_identities_binders([], {Identities, Binders}, _) -> {lists:reverse(Identities), lists:reverse(Binders)}; -get_identities_binders([{Key, _, Identity, _, _, HKDF}|T], {I0, B0}, N) -> +get_identities_binders([#ticket_data{key = Key, + identity = Identity, + cipher_suite = {_, HKDF}}|T], {I0, B0}, N) -> %% Use dummy binder for proper calculation of packet size when creating %% the real binder value. Binder = dummy_binder(HKDF), @@ -1286,18 +1384,27 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, alpn_preferred_protocols := ALPNPreferredProtocols} = Opts, #session{cipher_suite = NegotiatedCipherSuite, compression_method = Compression} = Session0, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, IsResumed) -> Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0), + MaxFragEnum = handle_mfl_extension(maps:get(max_frag_enum, Exts, undefined)), + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, - ConnectionStates0, Renegotiation, SecureRenegotation), + ConnectionStates1, Renegotiation, SecureRenegotation), Empty = empty_extensions(Version, server_hello), + %% RFC 6066 - server doesn't include max_fragment_length for resumed sessions + ServerMaxFragEnum = if IsResumed -> + undefined; + true -> + MaxFragEnum + end, ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server, ConnectionStates, Renegotiation), ec_point_formats => server_ecc_extension(Version, - maps:get(ec_point_formats, Exts, undefined)) + maps:get(ec_point_formats, Exts, undefined)), + max_frag_enum => ServerMaxFragEnum }, %% If we receive an ALPN extension and have ALPN configured for this connection, @@ -1319,34 +1426,55 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, Exts, Version, #{secure_renegotiate := SecureRenegotation, - next_protocol_selector := NextProtoSelector}, - ConnectionStates0, Renegotiation) -> + next_protocol_selector := NextProtoSelector, + ocsp_stapling := Stapling}, + ConnectionStates0, Renegotiation, IsNew) -> ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random, CipherSuite, undefined, Compression, ConnectionStates0, Renegotiation, SecureRenegotation), - %% If we receive an ALPN extension then this is the protocol selected, - %% otherwise handle the NPN extension. - ALPN = maps:get(alpn, Exts, undefined), - case decode_alpn(ALPN) of - %% ServerHello contains exactly one protocol: the one selected. - %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). - [Protocol] when not Renegotiation -> - {ConnectionStates, alpn, Protocol}; - [_] when Renegotiation -> - {ConnectionStates, alpn, undefined}; - undefined -> - NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), - Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation), - {ConnectionStates, npn, Protocol}; - {error, Reason} -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); - [] -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello); - [_|_] -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello) + %% RFC 6066: handle received/expected maximum fragment length + if IsNew -> + ServerMaxFragEnum = maps:get(max_frag_enum, Exts, undefined), + #{current_write := #{max_fragment_length := ConnMaxFragLen}} = ConnectionStates, + ClientMaxFragEnum = max_frag_enum(ConnMaxFragLen), + + if ServerMaxFragEnum == ClientMaxFragEnum -> + ok; + true -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; + true -> + ok + end, + + case handle_ocsp_extension(Stapling, Exts) of + #alert{} = Alert -> + Alert; + OcspState -> + %% If we receive an ALPN extension then this is the protocol selected, + %% otherwise handle the NPN extension. + ALPN = maps:get(alpn, Exts, undefined), + case decode_alpn(ALPN) of + %% ServerHello contains exactly one protocol: the one selected. + %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). + [Protocol] when not Renegotiation -> + {ConnectionStates, alpn, Protocol, OcspState}; + [_] when Renegotiation -> + {ConnectionStates, alpn, undefined, OcspState}; + undefined -> + NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), + Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation), + {ConnectionStates, npn, Protocol, OcspState}; + {error, Reason} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); + [] -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello); + [_|_] -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello) + end end. select_curve(Client, Server) -> @@ -1393,7 +1521,7 @@ select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns}, Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0), - {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert), + {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert), SignAlgo = sign_algo(SignAlgo0), PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), @@ -1447,7 +1575,7 @@ select_hashsign(#certificate_request{ Cert, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3-> - {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert), + {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert), SignAlgo = sign_algo(SignAlgo0), PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), @@ -1470,7 +1598,7 @@ select_hashsign(#certificate_request{ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) end; select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) -> - {_, _, PublicKeyAlgo0} = get_cert_params(Cert), + {_, _, PublicKeyAlgo0, _} = get_cert_params(Cert), PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), %% Check cert even for TLS 1.0/1.1 @@ -1486,14 +1614,28 @@ select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Versio %% - signature algorithm %% - parameters of the signature algorithm %% - public key algorithm (key type) +%% - RSA key size in bytes get_cert_params(Cert) -> #'OTPCertificate'{tbsCertificate = TBSCert, signatureAlgorithm = {_,SignAlgo, Param}} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}} = + #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}, + subjectPublicKey = PublicKey} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - {SignAlgo, Param, PublicKeyAlgo}. + RSAKeySize = + case PublicKey of + #'RSAPublicKey'{modulus = Modulus} -> + %% Get RSA key size in bytes + byte_size(binary:encode_unsigned(Modulus)); + _ -> + undefined + end, + {SignAlgo, Param, PublicKeyAlgo, RSAKeySize}. +select_own_cert([OwnCert| _]) -> + OwnCert; +select_own_cert(undefined) -> + undefined. get_signature_scheme(undefined) -> undefined; @@ -1555,6 +1697,8 @@ extension_value(#hash_sign_algos{hash_sign_algos = Algos}) -> Algos; extension_value(#alpn{extension_data = Data}) -> Data; +extension_value(#max_frag_enum{enum = Enum}) -> + Enum; extension_value(#next_protocol_negotiation{extension_data = Data}) -> Data; extension_value(#srp{username = Name}) -> @@ -1578,9 +1722,29 @@ extension_value(#pre_shared_key_client_hello{offered_psks = PSKs}) -> extension_value(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) -> SelectedIdentity; extension_value(#psk_key_exchange_modes{ke_modes = Modes}) -> - Modes. - - + Modes; +extension_value(#cookie{cookie = Cookie}) -> + Cookie. + +handle_ocsp_extension(true = Stapling, Extensions) -> + case maps:get(status_request, Extensions, false) of + undefined -> %% status_request in server hello is empty + #{ocsp_stapling => Stapling, + ocsp_expect => staple}; + false -> %% status_request is missing (not negotiated) + #{ocsp_stapling => Stapling, + ocsp_expect => no_staple}; + _Else -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, status_request_not_empty) + end; +handle_ocsp_extension(false = Stapling, Extensions) -> + case maps:get(status_request, Extensions, false) of + false -> %% status_request is missing (not negotiated) + #{ocsp_stapling => Stapling, + ocsp_expect => no_staple}; + _Else -> %% unsolicited status_request + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_status_request) + end. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -1638,9 +1802,7 @@ certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) -> [], CertDbData). %%-------------Handle handshake messages -------------------------------- -validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef, - ServerNameIndication, CustomizeHostCheck, CRLCheck, - CRLDbHandle, CertPath, LogLevel) -> +validation_fun_and_state({Fun, UserState0}, VerifyState, CertPath, LogLevel) -> {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> case ssl_certificate:validate(OtpCert, Extension, @@ -1649,30 +1811,25 @@ validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef, {valid, {NewSslState, UserState}}; {fail, Reason} -> apply_user_fun(Fun, OtpCert, Reason, UserState, - SslState, CertPath, LogLevel); + SslState, CertPath, LogLevel); {unknown, _} -> apply_user_fun(Fun, OtpCert, - Extension, UserState, SslState, CertPath, LogLevel) + Extension, UserState, SslState, CertPath, LogLevel) end; (OtpCert, VerifyResult, {SslState, UserState}) -> apply_user_fun(Fun, OtpCert, VerifyResult, UserState, SslState, CertPath, LogLevel) - end, {{Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}, UserState0}}; -validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, - ServerNameIndication, CustomizeHostCheck, CRLCheck, - CRLDbHandle, CertPath, LogLevel) -> + end, {VerifyState, UserState0}}; +validation_fun_and_state(undefined, VerifyState, CertPath, LogLevel) -> {fun(OtpCert, {extension, _} = Extension, SslState) -> ssl_certificate:validate(OtpCert, Extension, SslState); (OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or (VerifyResult == valid_peer) -> - case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, - CRLDbHandle, VerifyResult, CertPath, LogLevel) of - valid -> - ssl_certificate:validate(OtpCert, - VerifyResult, - SslState); + case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of + valid -> + ssl_certificate:validate(OtpCert, VerifyResult, SslState); Reason -> {fail, Reason} end; @@ -1680,15 +1837,14 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, ssl_certificate:validate(OtpCert, VerifyResult, SslState) - end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}. + end, VerifyState}. -apply_user_fun(Fun, OtpCert, VerifyResult, UserState0, - {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath, LogLevel) when - (VerifyResult == valid) or (VerifyResult == valid_peer) -> +apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogLevel) when + (VerifyResult0 == valid) or (VerifyResult0 == valid_peer) -> + VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState), case Fun(OtpCert, VerifyResult, UserState0) of {Valid, UserState} when (Valid == valid) or (Valid == valid_peer) -> - case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, - CRLDbHandle, VerifyResult, CertPath, LogLevel) of + case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of valid -> {Valid, {SslState, UserState}}; Result -> @@ -1707,58 +1863,16 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath, {unknown, {SslState, UserState}} end. -handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain, - Opts, Options, CertDbHandle, CertsDbRef) -> - handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason); -handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0, - Opts, Options, CertDbHandle, CertsDbRef) -> - handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason); -handle_path_validation_error(Reason, _, _, _, _,_, _) -> - path_validation_alert(Reason). - -handle_incomplete_chain(PeerCert, Chain0, - #{partial_chain := PartialChain} = Opts, Options, CertDbHandle, CertsDbRef, Reason) -> - case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of - {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found - case ssl_certificate:trusted_cert_and_path(Chain, - CertDbHandle, CertsDbRef, - PartialChain) of - {unknown_ca, []} -> - path_validation_alert(Reason); - {Trusted, Path} -> - case public_key:pkix_path_validation(Trusted, Path, Options) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; - {error, PathError} -> - handle_unordered_chain(PeerCert, Chain0, Opts, Options, - CertDbHandle, CertsDbRef, PathError) - end - end; - _ -> - handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason) - end. +maybe_check_hostname(OtpCert, valid_peer, SslState) -> + case ssl_certificate:validate(OtpCert, valid_peer, SslState) of + {valid, _} -> + valid_peer; + {fail, Reason} -> + Reason + end; +maybe_check_hostname(_, valid, _) -> + valid. -handle_unordered_chain(PeerCert, Chain0, - #{partial_chain := PartialChain}, Options, CertDbHandle, CertsDbRef, Reason) -> - {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, Chain0}), - case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, ExtractedCerts, Chain0) of - {ok, _, Chain} when Chain =/= Chain0 -> %% Chain appaears to be unordered - case ssl_certificate:trusted_cert_and_path(Chain, - CertDbHandle, CertsDbRef, - PartialChain) of - {unknown_ca, []} -> - path_validation_alert(Reason); - {Trusted, Path} -> - case public_key:pkix_path_validation(Trusted, Path, Options) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; - {error, PathError} -> - path_validation_alert(PathError) - end - end; - _ -> - path_validation_alert(Reason) - end. path_validation_alert({bad_cert, cert_expired}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); @@ -1782,46 +1896,97 @@ path_validation_alert({bad_cert, unknown_ca}) -> path_validation_alert(Reason) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason). -digitally_signed(Version, Hashes, HashAlgo, PrivateKey) -> - try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of +digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) -> + try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) of Signature -> Signature catch error:badkey-> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))) end. -do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine) - when Minor >= 3 -> - crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine)); -do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> - public_key:sign({digest, Hash}, HashAlgo, Key); -do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> - public_key:encrypt_private(Hash, Key, - [{rsa_pad, rsa_pkcs1_padding}]); -do_digitally_signed({3, _}, Hash, _, - #{algorithm := rsa} = Engine) -> + +do_digitally_signed({3, Minor}, Hash, _, + #{algorithm := rsa} = Engine, rsa) when Minor =< 2-> crypto:private_encrypt(rsa, Hash, maps:remove(algorithm, Engine), rsa_pkcs1_padding); -do_digitally_signed({3, _}, Hash, HashAlgo, #{algorithm := Alg} = Engine) -> - crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine)); -do_digitally_signed(_Version, Hash, HashAlgo, Key) -> +do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) + when Minor > 3 -> + Options = signature_options(SignAlgo, HashAlgo), + crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options); +do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) + when Minor > 3 -> + Options = signature_options(SignAlgo, HashAlgo), + crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options); +do_digitally_signed({3, 3}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) -> + Options = signature_options(SignAlgo, HashAlgo), + crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine), Options); +do_digitally_signed({3, 4}, Hash, HashAlgo, {#'RSAPrivateKey'{} = Key, + #'RSASSA-PSS-params'{}}, SignAlgo) -> + Options = signature_options(SignAlgo, HashAlgo), + public_key:sign(Hash, HashAlgo, Key, Options); +do_digitally_signed({3, 4}, Hash, HashAlgo, Key, SignAlgo) -> + Options = signature_options(SignAlgo, HashAlgo), + public_key:sign(Hash, HashAlgo, Key, Options); +do_digitally_signed({3, Minor}, Hash, HashAlgo, Key, SignAlgo) when Minor >= 3 -> + Options = signature_options(HashAlgo, SignAlgo), + public_key:sign({digest,Hash}, HashAlgo, Key, Options); +do_digitally_signed({3, Minor}, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when Minor =< 2 -> + public_key:encrypt_private(Hash, Key, + [{rsa_pad, rsa_pkcs1_padding}]); +do_digitally_signed(_Version, Hash, HashAlgo, Key, _SignAlgo) -> public_key:sign({digest, Hash}, HashAlgo, Key). + +signature_options(SignAlgo, HashAlgo) when SignAlgo =:= rsa_pss_rsae orelse + SignAlgo =:= rsa_pss_pss -> + pss_options(HashAlgo); +signature_options(_, _) -> + []. + +verify_options(SignAlgo, HashAlgo, _KeyParams) + when SignAlgo =:= rsa_pss_rsae orelse + SignAlgo =:= rsa_pss_pss -> + pss_options(HashAlgo); +verify_options(_, _, _) -> + []. + +pss_options(HashAlgo) -> + %% of the digest algorithm: rsa_pss_saltlen = -1 + [{rsa_padding, rsa_pkcs1_pss_padding}, + {rsa_pss_saltlen, -1}, + {rsa_mgf1_md, HashAlgo}]. bad_key(#'DSAPrivateKey'{}) -> unacceptable_dsa_key; bad_key(#'RSAPrivateKey'{}) -> unacceptable_rsa_key; bad_key(#'ECPrivateKey'{}) -> + unacceptable_ecdsa_key; +bad_key(#{algorithm := rsa}) -> + unacceptable_rsa_key; +bad_key(#{algorithm := ecdsa}) -> unacceptable_ecdsa_key. -crl_check(_, false, _,_,_, _, _, _) -> +cert_status_check(_, #{ocsp_state := #{ocsp_stapling := true, + ocsp_expect := stapled}}, _VerifyResult, _, _) -> + valid; %% OCSP staple will now be checked by ssl_certifcate:verify_cert_extensions/2 in ssl_certifcate:validate +cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := false}} = SslState, VerifyResult, CertPath, LogLevel) -> + maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel); +cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := best_effort, %%TODO should we support + ocsp_expect := undetermined}} = SslState, + VerifyResult, CertPath, LogLevel) -> + maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel). + +maybe_check_crl(_, #{crl_check := false}, _, _, _) -> valid; -crl_check(_, peer, _, _,_, valid, _, _) -> %% Do not check CAs with this option. +maybe_check_crl(_, #{crl_check := peer}, valid, _, _) -> %% Do not check CAs with this option. valid; -crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath, LogLevel) -> +maybe_check_crl(OtpCert, #{crl_check := Check, + certdb := CertDbHandle, + certdb_ref := CertDbRef, + crl_db := {Callback, CRLDbHandle}}, _, CertPath, LogLevel) -> Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) -> - ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath, - DBInfo}) + ssl_crl:trusted_cert_and_path(CRL, Issuer, CertPath, + DBInfo) end, {CertDbHandle, CertDbRef}}}, {update_crl, fun(DP, CRL) -> case Callback:fresh_crl(DP, CRL) of @@ -1925,13 +2090,8 @@ encrypted_premaster_secret(Secret, RSAPublicKey) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed)) end. -calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) -> - ssl_v3:certificate_verify(HashAlgo, MasterSecret, lists:reverse(Handshake)); calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) -> tls_v1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)). - -calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> - ssl_v3:finished(Role, MasterSecret, lists:reverse(Handshake)); calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). @@ -1961,20 +2121,10 @@ master_secret(Version, MasterSecret, {MasterSecret, ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState, ServerCipherState, Role)}. - -setup_keys({3,0}, _PrfAlgo, MasterSecret, - ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) -> - ssl_v3:setup_keys(MasterSecret, ServerRandom, - ClientRandom, HashSize, KML, EKML, IVS); - setup_keys({3,N}, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) -> tls_v1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KML, IVS). - -calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> - ssl_v3:master_secret(PremasterSecret, ClientRandom, ServerRandom); - calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom). @@ -2173,7 +2323,7 @@ enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, server_key_exchange_hash(HashAlgo, <<ClientRandom/binary, ServerRandom/binary, EncParams/binary>>), - Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), + Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey, SignAlgo), #server_key_params{params = Params, params_bin = EncParams, hashsign = {HashAlgo, SignAlgo}, @@ -2587,6 +2737,10 @@ decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), decode_extensions(Rest, Version, MessageType, Acc#{sni => dec_sni(NameList)}); +decode_extensions(<<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(1), ?BYTE(MaxFragEnum), Rest/binary>>, + Version, MessageType, Acc) -> + %% RFC 6066 Section 4 + decode_extensions(Rest, Version, MessageType, Acc#{max_frag_enum => #max_frag_enum{enum = MaxFragEnum}}); decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 -> <<?BYTE(_),Versions/binary>> = ExtData, @@ -2661,6 +2815,50 @@ decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len), #pre_shared_key_server_hello{ selected_identity = Identity}}); +decode_extensions(<<?UINT16(?COOKIE_EXT), ?UINT16(Len), ?UINT16(CookieLen), + Cookie:CookieLen/binary, Rest/binary>>, + Version, MessageType, Acc) + when Len == CookieLen + 2 -> + decode_extensions(Rest, Version, MessageType, + Acc#{cookie => #cookie{cookie = Cookie}}); + +%% RFC6066, if a server returns a "CertificateStatus" message, then +%% the server MUST have included an extension of type "status_request" +%% with empty "extension_data" in the extended server hello. +decode_extensions(<<?UINT16(?STATUS_REQUEST), ?UINT16(Len), + _ExtensionData:Len/binary, Rest/binary>>, Version, + MessageType = server_hello, Acc) + when Len =:= 0 -> + decode_extensions(Rest, Version, MessageType, + Acc#{status_request => undefined}); +%% RFC8446 4.4.2.1, In TLS1.3, the body of the "status_request" extension +%% from the server MUST be a CertificateStatus structure as defined in +%% RFC6066. +decode_extensions(<<?UINT16(?STATUS_REQUEST), ?UINT16(Len), + CertStatus:Len/binary, Rest/binary>>, Version, + MessageType, Acc) -> + case CertStatus of + <<?BYTE(?CERTIFICATE_STATUS_TYPE_OCSP), + ?UINT24(OCSPLen), + ASN1OCSPResponse:OCSPLen/binary>> -> + decode_extensions(Rest, Version, MessageType, + Acc#{status_request => #certificate_status{response = ASN1OCSPResponse}}); + _Other -> + decode_extensions(Rest, Version, MessageType, Acc) + end; + +decode_extensions(<<?UINT16(?EARLY_DATA_EXT), ?UINT16(0), Rest/binary>>, + Version, MessageType, Acc) -> + decode_extensions(Rest, Version, MessageType, + Acc#{early_data => #early_data_indication{}}); + +decode_extensions(<<?UINT16(?EARLY_DATA_EXT), ?UINT16(4), ?UINT32(MaxSize), + Rest/binary>>, + Version, MessageType, Acc) -> + decode_extensions(Rest, Version, MessageType, + Acc#{early_data => + #early_data_indication_nst{indication = MaxSize}}); + %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> @@ -2775,6 +2973,7 @@ certs_from_list(ACList) -> CertLen = byte_size(Cert), <<?UINT24(CertLen), Cert/binary>> end || Cert <- ACList]). + from_3bytes(Bin3) -> from_3bytes(Bin3, []). @@ -2948,6 +3147,13 @@ handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) -> false -> handle_alpn_extension(Tail, ClientProtocols) end. +handle_mfl_extension(#max_frag_enum{enum = Enum}=MaxFragEnum) when Enum >= 1, Enum =< 4 -> + MaxFragEnum; +handle_mfl_extension(#max_frag_enum{}) -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); +handle_mfl_extension(_) -> + undefined. + handle_next_protocol(undefined, _NextProtocolSelector, _Renegotiating) -> undefined; @@ -3166,6 +3372,18 @@ sni(disable) -> sni(Hostname) -> #sni{hostname = Hostname}. +%% convert max_fragment_length (in bytes) to the RFC 6066 ENUM +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_1) -> + #max_frag_enum{enum = 1}; +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_2) -> + #max_frag_enum{enum = 2}; +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_3) -> + #max_frag_enum{enum = 3}; +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_4) -> + #max_frag_enum{enum = 4}; +max_frag_enum(undefined) -> + undefined. + renegotiation_info(_, client, _, false) -> #renegotiation_info{renegotiated_connection = undefined}; renegotiation_info(_RecordCB, server, ConnectionStates, false) -> @@ -3239,9 +3457,6 @@ handle_renegotiation_info(_, _RecordCB, server, #renegotiation_info{renegotiated throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)) end end; -handle_renegotiation_info({3,0}, _RecordCB, client, undefined, ConnectionStates, true, _SecureRenegotation, _) -> - {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; - handle_renegotiation_info(_, RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); @@ -3286,7 +3501,7 @@ empty_extensions() -> empty_extensions({3,4}, client_hello) -> #{ sni => undefined, - %% max_fragment_length => undefined, + %% max_frag_enum => undefined, %% status_request => undefined, elliptic_curves => undefined, signature_algs => undefined, @@ -3301,7 +3516,7 @@ empty_extensions({3,4}, client_hello) -> pre_shared_key => undefined, psk_key_exchange_modes => undefined, %% early_data => undefined, - %% cookie => undefined, + cookie => undefined, client_hello_versions => undefined, %% cert_authorities => undefined, %% post_handshake_auth => undefined, @@ -3326,10 +3541,9 @@ empty_extensions({3,4}, server_hello) -> empty_extensions({3,4}, hello_retry_request) -> #{server_hello_selected_version => undefined, key_share => undefined, - pre_shared_key => undefined + pre_shared_key => undefined, %% TODO remove! + cookie => undefined }; -empty_extensions({3,0}, _) -> - empty_extensions(); empty_extensions(_, server_hello) -> #{renegotiation_info => undefined, alpn => undefined, @@ -3338,3 +3552,51 @@ empty_extensions(_, server_hello) -> handle_log(Level, {LogLevel, ReportMap, Meta}) -> ssl_logger:log(Level, LogLevel, ReportMap, Meta). + + +path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, + CRLDbHandle, Version, SslOptions, CertExt); +path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + case path_validation(TrustedCert, Path, ServerName, + Role, CertDbHandle, CRLDbHandle, CertDbRef, + Version, SslOptions, CertExt) of + {ok, _} = Result -> + Result; + {error, _} -> + path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) + end. + +path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, + #{verify_fun := VerifyFun, + customize_hostname_check := CustomizeHostnameCheck, + crl_check := CrlCheck, + log_level := Level, + signature_algs := SignAlgos, + depth := Depth}, + #{cert_ext := CertExt, + ocsp_responder_certs := OcspResponderCerts, + ocsp_state := OcspState}) -> + ValidationFunAndState = + validation_fun_and_state(VerifyFun, #{role => Role, + certdb => CertDbHandle, + certdb_ref => CertDbRef, + server_name => ServerName, + customize_hostname_check => + CustomizeHostnameCheck, + signature_algs => SignAlgos, + signature_algs_cert => undefined, + version => Version, + crl_check => CrlCheck, + crl_db => CRLDbHandle, + cert_ext => CertExt, + issuer => TrustedCert, + ocsp_responder_certs => OcspResponderCerts, + ocsp_state => OcspState}, + Path, Level), + Options = [{max_path_length, Depth}, + {verify_fun, ValidationFunAndState}], + public_key:pkix_path_validation(TrustedCert, Path, Options). diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index a772567846..3f06eaa095 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -38,19 +38,20 @@ -define(ECDSA, 3). -record(session, { - session_id, - peer_certificate, - own_certificate, - compression_method, - cipher_suite, - master_secret, - srp_username, - is_resumable, - time_stamp, - ecc, %% TLS 1.3 Group - sign_alg, %% TLS 1.3 Signature Algorithm - dh_public_value %% TLS 1.3 DH Public Value from peer - }). + session_id, + internal_id, + peer_certificate, + own_certificates, + compression_method, + cipher_suite, + master_secret, + srp_username, + is_resumable, + time_stamp, + ecc, %% TLS 1.3 Group + sign_alg, %% TLS 1.3 Signature Algorithm + dh_public_value %% TLS 1.3 DH Public Value from peer + }). -define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3 -define(NUM_OF_PREMASTERSECRET_BYTES, 48). @@ -376,7 +377,7 @@ -define(NAMED_CURVE, 3). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% RFC 6066 Server name indication +%% RFC 6066 TLS Extensions: Extension Definitions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% section 3 @@ -389,12 +390,42 @@ hostname = undefined }). +%% enum{ 2^9(1), 2^10(2), 2^11(3), 2^12(4), (255) } MaxFragmentLength; +-define(MAX_FRAGMENT_LENGTH_EXT, 1). +-define(MAX_FRAGMENT_LENGTH_BYTES_1, 512). +-define(MAX_FRAGMENT_LENGTH_BYTES_2, 1024). +-define(MAX_FRAGMENT_LENGTH_BYTES_3, 2048). +-define(MAX_FRAGMENT_LENGTH_BYTES_4, 4096). + +-record(max_frag_enum, { + enum = undefined %% contains the enum value 1..4 + }). + +%% Section 8, Certificate Status Request +-define(STATUS_REQUEST, 5). +-define(CERTIFICATE_STATUS_TYPE_OCSP, 1). +-define(CERTIFICATE_STATUS, 22). + +%% status request record defined in RFC 6066, section 8 +-record(certificate_status_request, { + status_type, + request +}). + +-record(ocsp_status_request, { + responder_id_list = [], + request_extensions = [] +}). + +-record(certificate_status, { + status_type, + response +}). + %% Other possible values from RFC 6066, not supported --define(MAX_FRAGMENT_LENGTH, 1). -define(CLIENT_CERTIFICATE_URL, 2). -define(TRUSTED_CA_KEYS, 3). -define(TRUNCATED_HMAC, 4). --define(STATUS_REQUEST, 5). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% RFC 7250 Using Raw Public Keys in Transport Layer Security (TLS) diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index b9411e6ebf..b080016458 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -25,7 +25,6 @@ -include_lib("public_key/include/public_key.hrl"). --define(VSN, "8.2.6"). -define(SECRET_PRINTOUT, "***"). -type reason() :: any(). @@ -75,10 +74,10 @@ %% Keep as interop with legacy software but do not support as default %% tlsv1.0 and tlsv1.1 is now also considered legacy %% tlsv1.3 is under development (experimental). --define(ALL_AVAILABLE_VERSIONS, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). +-define(ALL_AVAILABLE_VERSIONS, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', tlsv1]). -define(ALL_AVAILABLE_DATAGRAM_VERSIONS, ['dtlsv1.2', dtlsv1]). %% Defines the default versions when not specified by an ssl option. --define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2']). +-define(ALL_SUPPORTED_VERSIONS, ['tlsv1.3', 'tlsv1.2']). -define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1']). %% Versions allowed in TLSCiphertext.version (TLS 1.2 and prior) and @@ -86,7 +85,7 @@ %% TLS 1.3 sets TLSCiphertext.legacy_record_version to 0x0303 for all records %% generated other than an than an initial ClientHello, where it MAY also be 0x0301. %% Thus, the allowed range is limited to 0x0300 - 0x0303. --define(ALL_TLS_RECORD_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). +-define(ALL_TLS_RECORD_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1]). -define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2']). -define(MIN_DATAGRAM_SUPPORTED_VERSIONS, [dtlsv1]). @@ -116,6 +115,8 @@ %% 2^24.5 * 2^14 = 2^38.5 -define(KEY_USAGE_LIMIT_AES_GCM, 388736063997). +-define(DEFAULT_MAX_EARLY_DATA_SIZE, 16384). + %% This map stores all supported options with default values and %% list of dependencies: %% #{<option> => {<default_value>, [<option>]}, @@ -124,6 +125,7 @@ #{ alpn_advertised_protocols => {undefined, [versions]}, alpn_preferred_protocols => {undefined, [versions]}, + anti_replay => {undefined, [versions, session_tickets]}, beast_mitigation => {one_n_minus_one, [versions]}, cacertfile => {undefined, [versions, verify_fun, @@ -133,12 +135,16 @@ certfile => {<<>>, [versions]}, ciphers => {[], [versions]}, client_renegotiation => {undefined, [versions]}, + cookie => {true, [versions]}, crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]}, crl_check => {false, [versions]}, customize_hostname_check => {[], [versions]}, - depth => {1, [versions]}, + depth => {10, [versions]}, dh => {undefined, [versions]}, dhfile => {undefined, [versions]}, + early_data => {undefined, [versions, + session_tickets, + use_ticket]}, eccs => {undefined, [versions]}, erl_dist => {false, [versions]}, fail_if_no_peer_cert => {false, [versions]}, @@ -153,8 +159,18 @@ key_update_at => {?KEY_USAGE_LIMIT_AES_GCM, [versions]}, log_level => {notice, [versions]}, max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]}, + middlebox_comp_mode => {true, [versions]}, + max_fragment_length => {undefined, [versions]}, next_protocol_selector => {undefined, [versions]}, next_protocols_advertised => {undefined, [versions]}, + %% If enable OCSP stapling + ocsp_stapling => {false, [versions]}, + %% Optional arg, if give suggestion of OCSP responders + ocsp_responder_certs => {[], [versions, + ocsp_stapling]}, + %% Optional arg, if add nonce extension in request + ocsp_nonce => {true, [versions, + ocsp_stapling]}, padding_check => {true, [versions]}, partial_chain => {fun(_) -> unknown_ca end, [versions]}, password => {"", [versions]}, @@ -163,8 +179,8 @@ renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]}, reuse_session => {undefined, [versions]}, reuse_sessions => {true, [versions]}, - anti_replay => {undefined, [versions, session_tickets]}, secure_renegotiate => {true, [versions]}, + keep_secrets => {false, [versions]}, server_name_indication => {undefined, [versions]}, session_tickets => {disabled, [versions]}, signature_algs => {undefined, [versions]}, @@ -176,23 +192,20 @@ supported_groups => {undefined, [versions]}, use_ticket => {undefined, [versions]}, user_lookup_fun => {undefined, [versions]}, - validate_extensions_fun => {undefined, [versions]}, verify => {verify_none, [versions, fail_if_no_peer_cert, - partial_chain, - verify_client_once]}, - verify_client_once => {false, [versions]}, + partial_chain]}, verify_fun => { - {fun(_,{bad_cert, _}, UserState) -> + {fun(_, {bad_cert, _}, UserState) -> {valid, UserState}; - (_,{extension, #'Extension'{critical = true}}, UserState) -> + (_, {extension, #'Extension'{critical = true}}, UserState) -> %% This extension is marked as critical, so %% certificate verification should fail if we don't %% understand the extension. However, this is %% `verify_none', so let's accept it anyway. {valid, UserState}; - (_,{extension, _}, UserState) -> + (_, {extension, _}, UserState) -> {unknown, UserState}; (_, valid, UserState) -> {valid, UserState}; @@ -228,6 +241,17 @@ {stop, any(), any()}. -type ssl_options() :: map(). +%% Internal ticket data record holding pre-processed ticket data. +-record(ticket_data, + {key, %% key in client ticket store + pos, %% ticket position in binders list + identity, %% opaque ticket binary + psk, %% pre-shared key + nonce, %% ticket nonce + cipher_suite, %% cipher suite - hash, bulk cipher algorithm + max_size %% max early data size allowed by this ticket + }). + -endif. % -ifdef(ssl_internal). diff --git a/lib/ssl/src/ssl_listen_tracker_sup.erl b/lib/ssl/src/ssl_listen_tracker_sup.erl index f7e97bcb76..6afd1c0009 100644 --- a/lib/ssl/src/ssl_listen_tracker_sup.erl +++ b/lib/ssl/src/ssl_listen_tracker_sup.erl @@ -69,4 +69,4 @@ init(_O) -> tracker_name(normal) -> ?MODULE; tracker_name(dist) -> - list_to_atom(atom_to_list(?MODULE) ++ "dist"). + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl index dd1f4cb07a..027dbe8732 100644 --- a/lib/ssl/src/ssl_logger.erl +++ b/lib/ssl/src/ssl_logger.erl @@ -180,6 +180,11 @@ parse_handshake(Direction, #certificate{} = Certificate) -> [header_prefix(Direction)]), Message = io_lib:format("~p", [?rec_info(certificate, Certificate)]), {Header, Message}; +parse_handshake(Direction, #certificate_status{} = CertificateStatus) -> + Header = io_lib:format("~s Handshake, CertificateStatus", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(certificate_status, CertificateStatus)]), + {Header, Message}; parse_handshake(Direction, #server_key_exchange{} = ServerKeyExchange) -> Header = io_lib:format("~s Handshake, ServerKeyExchange", [header_prefix(Direction)]), @@ -249,9 +254,13 @@ parse_handshake(Direction, #key_update{} = KeyUpdate) -> Header = io_lib:format("~s Post-Handshake, KeyUpdate", [header_prefix(Direction)]), Message = io_lib:format("~p", [?rec_info(key_update, KeyUpdate)]), + {Header, Message}; +parse_handshake(Direction, #end_of_early_data{} = EndOfEarlyData) -> + Header = io_lib:format("~s Handshake, EndOfEarlyData", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(end_of_early_data, EndOfEarlyData)]), {Header, Message}. - parse_cipher_suites([_|_] = Ciphers) -> [format_cipher(C) || C <- Ciphers]. diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 664d967cb2..9d0dbc2d9b 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -29,7 +29,7 @@ -export([start_link/1, start_link_dist/1, connection_init/3, cache_pem_file/2, lookup_trusted_cert/4, - new_session_id/1, clean_cert_db/2, + clean_cert_db/2, register_session/2, register_session/4, invalidate_session/2, insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2, invalidate_session/3, name/1]). @@ -48,15 +48,14 @@ -record(state, { session_cache_client :: db_handle(), - session_cache_server :: db_handle(), - session_cache_cb :: atom(), + session_cache_client_cb :: atom(), session_lifetime :: integer(), certificate_db :: db_handle(), session_validation_timer :: reference(), session_cache_client_max :: integer(), - session_cache_server_max :: integer(), - session_server_invalidator :: undefined | pid(), - session_client_invalidator :: undefined | pid() + session_client_invalidator :: undefined | pid(), + options :: list(), + client_session_order :: gb_trees:tree() }). -define(GEN_UNIQUE_ID_MAX_TRIES, 10). @@ -149,14 +148,6 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> ssl_pkix_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer). %%-------------------------------------------------------------------- --spec new_session_id(integer()) -> ssl:session_id(). -%% -%% Description: Creates a session id for the server. -%%-------------------------------------------------------------------- -new_session_id(Port) -> - call({new_session_id, Port}). - -%%-------------------------------------------------------------------- -spec clean_cert_db(reference(), binary()) -> ok. %% %% Description: Send clean request of cert db to ssl_manager process should @@ -229,30 +220,31 @@ init([ManagerName, PemCacheName, Opts]) -> put(ssl_manager, ManagerName), put(ssl_pem_cache, PemCacheName), process_flag(trap_exit, true), - CacheCb = proplists:get_value(session_cb, Opts, ssl_session_cache), + + #{session_cb := DefaultCacheCb, + session_cb_init_args := DefaultCacheCbInitArgs, + lifetime := DefaultSessLifeTime, + max := ClientSessMax + } = ssl_config:pre_1_3_session_opts(client), + CacheCb = proplists:get_value(session_cb, Opts, DefaultCacheCb), SessionLifeTime = - proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'), - CertDb = ssl_pkix_db:create(PemCacheName), + proplists:get_value(session_lifetime, Opts, DefaultSessLifeTime), ClientSessionCache = CacheCb:init([{role, client} | - proplists:get_value(session_cb_init_args, Opts, [])]), - ServerSessionCache = - CacheCb:init([{role, server} | - proplists:get_value(session_cb_init_args, Opts, [])]), + proplists:get_value(session_cb_init_args, Opts, DefaultCacheCbInitArgs)]), + + CertDb = ssl_pkix_db:create(PemCacheName), Timer = erlang:send_after(SessionLifeTime * 1000 + 5000, self(), validate_sessions), {ok, #state{certificate_db = CertDb, session_cache_client = ClientSessionCache, - session_cache_server = ServerSessionCache, - session_cache_cb = CacheCb, + session_cache_client_cb = CacheCb, session_lifetime = SessionLifeTime, session_validation_timer = Timer, - session_cache_client_max = - max_session_cache_size(session_cache_client_max), - session_cache_server_max = - max_session_cache_size(session_cache_server_max), + session_cache_client_max = ClientSessMax, session_client_invalidator = undefined, - session_server_invalidator = undefined + options = Opts, + client_session_order = gb_trees:empty() }}. %%-------------------------------------------------------------------- @@ -299,12 +291,6 @@ handle_call({{delete_crls, CRLsOrPath}, _}, _From, #state{certificate_db = Db} = State) -> ssl_pkix_db:remove_crls(Db, CRLsOrPath), {reply, ok, State}; - -handle_call({{new_session_id, Port}, _}, - _, #state{session_cache_cb = CacheCb, - session_cache_server = Cache} = State) -> - Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), - {reply, Id, State}; handle_call({{register_session, Host, Port, Session},_}, _, State0) -> State = client_register_session(Host, Port, Session, State0), {reply, ok, State}. @@ -324,23 +310,11 @@ handle_cast({register_session, Host, Port, Session, unique}, State0) -> handle_cast({register_session, Host, Port, Session, true}, State0) -> State = client_register_session(Host, Port, Session, State0), {noreply, State}; - -handle_cast({register_session, Port, Session}, State0) -> - State = server_register_session(Port, Session, State0), - {noreply, State}; - handle_cast({invalidate_session, Host, Port, #session{session_id = ID} = Session}, #state{session_cache_client = Cache, - session_cache_cb = CacheCb} = State) -> + session_cache_client_cb = CacheCb} = State) -> invalidate_session(Cache, CacheCb, {{Host, Port}, ID}, Session, State); - -handle_cast({invalidate_session, Port, #session{session_id = ID} = Session}, - #state{session_cache_server = Cache, - session_cache_cb = CacheCb} = State) -> - invalidate_session(Cache, CacheCb, {Port, ID}, Session, State); - - handle_cast({insert_crls, Path, CRLs}, #state{certificate_db = Db} = State) -> ssl_pkix_db:add_crls(Db, Path, CRLs), @@ -359,20 +333,16 @@ handle_cast({delete_crls, CRLsOrPath}, %% %% Description: Handling all non call/cast messages %%------------------------------------------------------------------- -handle_info(validate_sessions, #state{session_cache_cb = CacheCb, +handle_info(validate_sessions, #state{session_cache_client_cb = CacheCb, session_cache_client = ClientCache, - session_cache_server = ServerCache, session_lifetime = LifeTime, - session_client_invalidator = Client, - session_server_invalidator = Server + session_client_invalidator = Client } = State) -> Timer = erlang:send_after(?SESSION_VALIDATION_INTERVAL, self(), validate_sessions), CPid = start_session_validator(ClientCache, CacheCb, LifeTime, Client), - SPid = start_session_validator(ServerCache, CacheCb, LifeTime, Server), {noreply, State#state{session_validation_timer = Timer, - session_client_invalidator = CPid, - session_server_invalidator = SPid}}; + session_client_invalidator = CPid}}; handle_info({clean_cert_db, Ref, File}, #state{certificate_db = [CertDb, {RefDb, FileMapDb} | _]} = State) -> @@ -384,12 +354,8 @@ handle_info({clean_cert_db, Ref, File}, clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) end, {noreply, State}; - handle_info({'EXIT', Pid, _}, #state{session_client_invalidator = Pid} = State) -> {noreply, State#state{session_client_invalidator = undefined}}; -handle_info({'EXIT', Pid, _}, #state{session_server_invalidator = Pid} = State) -> - {noreply, State#state{session_server_invalidator = undefined}}; - handle_info(_Info, State) -> {noreply, State}. @@ -403,13 +369,11 @@ handle_info(_Info, State) -> %%-------------------------------------------------------------------- terminate(_Reason, #state{certificate_db = Db, session_cache_client = ClientSessionCache, - session_cache_server = ServerSessionCache, - session_cache_cb = CacheCb, + session_cache_client_cb = CacheCb, session_validation_timer = Timer}) -> erlang:cancel_timer(Timer), ssl_pkix_db:remove(Db), catch CacheCb:terminate(ClientSessionCache), - catch CacheCb:terminate(ServerSessionCache), ok. %%-------------------------------------------------------------------- @@ -463,46 +427,15 @@ session_validation({{Port, _}, Session}, LifeTime) -> validate_session(Port, Session, LifeTime), LifeTime. -max_session_cache_size(CacheType) -> - case application:get_env(ssl, CacheType) of - {ok, Size} when is_integer(Size) -> - Size; - _ -> - ?DEFAULT_MAX_SESSION_CACHE - end. - -invalidate_session(Cache, CacheCb, Key, _Session, State) -> +invalidate_session(Cache, CacheCb, Key, _Session, + #state{client_session_order = Order} = State) -> case CacheCb:lookup(Cache, Key) of undefined -> %% Session is already invalidated {noreply, State}; - #session{} -> + #session{internal_id = InternalId} -> CacheCb:delete(Cache, Key), - {noreply, State} - end. - -%% If we cannot generate a not allready in use session ID in -%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The -%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which -%% states : "If we cannot find a session id in -%% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone -%% is trying to open roughly very close to 2^128 (or 2^256) SSL -%% sessions to our server" -new_id(_, 0, _, _) -> - <<>>; -new_id(Port, Tries, Cache, CacheCb) -> - Id = ssl_cipher:random_bytes(?NUM_OF_SESSION_ID_BYTES), - case CacheCb:lookup(Cache, {Port, Id}) of - undefined -> - Now = erlang:monotonic_time(), - %% New sessions cannot be set to resumable - %% until handshake is compleate and the - %% other session values are set. - CacheCb:update(Cache, {Port, Id}, #session{session_id = Id, - is_resumable = new, - time_stamp = Now}), - Id; - _ -> - new_id(Port, Tries - 1, Cache, CacheCb) + {noreply, State#state{session_cache_client = Cache, + client_session_order = gb_trees:delete(InternalId, Order)}} end. clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) -> @@ -515,55 +448,62 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) -> ok end. -client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache, - session_cache_cb = CacheCb, +client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache0, + session_cache_client_cb = CacheCb, session_cache_client_max = Max, - session_client_invalidator = Pid0} = State) -> + options = Options, + client_session_order = Order0} = State) -> TimeStamp = erlang:monotonic_time(), NewSession = Session#session{time_stamp = TimeStamp}, - case CacheCb:select_session(Cache, {Host, Port}) of + case CacheCb:select_session(Cache0, {Host, Port}) of no_session -> - Pid = do_register_session({{Host, Port}, - NewSession#session.session_id}, - NewSession, Max, Pid0, Cache, CacheCb), - State#state{session_client_invalidator = Pid}; + {Cache, Order} = do_register_session({{Host, Port}, + NewSession#session.session_id}, + NewSession, Max, Cache0, CacheCb, Options, Order0), + State#state{session_cache_client = Cache, client_session_order = Order}; Sessions -> register_unique_session(Sessions, NewSession, {Host, Port}, State) end. -client_register_session(Host, Port, Session, #state{session_cache_client = Cache, - session_cache_cb = CacheCb, +client_register_session(Host, Port, Session, #state{session_cache_client = Cache0, + session_cache_client_cb = CacheCb, session_cache_client_max = Max, - session_client_invalidator = Pid0} = State) -> + options = Options, + client_session_order = Order0} = State) -> TimeStamp = erlang:monotonic_time(), NewSession = Session#session{time_stamp = TimeStamp}, - Pid = do_register_session({{Host, Port}, - NewSession#session.session_id}, - NewSession, Max, Pid0, Cache, CacheCb), - State#state{session_client_invalidator = Pid}. - -server_register_session(Port, Session, #state{session_cache_server_max = Max, - session_cache_server = Cache, - session_cache_cb = CacheCb, - session_server_invalidator = Pid0} = State) -> - TimeStamp = erlang:monotonic_time(), - NewSession = Session#session{time_stamp = TimeStamp}, - Pid = do_register_session({Port, NewSession#session.session_id}, - NewSession, Max, Pid0, Cache, CacheCb), - State#state{session_server_invalidator = Pid}. - -do_register_session(Key, Session, Max, Pid, Cache, CacheCb) -> - try CacheCb:size(Cache) of - Size when Size >= Max -> - invalidate_session_cache(Pid, CacheCb, Cache); - _ -> - CacheCb:update(Cache, Key, Session), - Pid + SessionId = NewSession#session.session_id, + {Cache, Order} = do_register_session({{Host, Port}, SessionId}, + NewSession, Max, Cache0, CacheCb, Options, Order0), + State#state{session_cache_client = Cache, + client_session_order = Order}. + +do_register_session(Key, #session{time_stamp = TimeStamp} = Session0, + Max, Cache, CacheCb, Options, Order0) -> + try + case CacheCb:size(Cache) of + Max -> + InternalId = {TimeStamp, erlang:unique_integer([monotonic])}, + Session = Session0#session{internal_id = InternalId}, + {_, OldKey, Order1} = gb_trees:take_smallest(Order0), + Order = gb_trees:insert(InternalId, Key, Order1), + CacheCb:delete(Cache, OldKey), + CacheCb:update(Cache, Key, Session), + {Cache, Order}; + _ -> + InternalId = {TimeStamp, erlang:unique_integer([monotonic])}, + Session = Session0#session{internal_id = InternalId}, + Order = gb_trees:insert(InternalId, Key, Order0), + CacheCb:update(Cache, Key, Session), + {Cache, Order} + end catch - error:undef -> - CacheCb:update(Cache, Key, Session), - Pid + _:_ -> + %% Backwards compatibility if size functions is not implemented by callback + Args = proplists:get_value(session_cb_init_args, Options, []), + CacheCb:terminate(Cache), + {CacheCb:init(Args), gb_trees:empty()} end. @@ -571,31 +511,33 @@ do_register_session(Key, Session, Max, Pid, Cache, CacheCb) -> %% for itself creating big delays at connection time. register_unique_session(Sessions, Session, PartialKey, #state{session_cache_client_max = Max, - session_cache_client = Cache, - session_cache_cb = CacheCb, - session_client_invalidator = Pid0} = State) -> + session_cache_client = Cache0, + session_cache_client_cb = CacheCb, + options = Options, + client_session_order = Order0} = State) -> case exists_equivalent(Session , Sessions) of true -> State; false -> - Pid = do_register_session({PartialKey, - Session#session.session_id}, - Session, Max, Pid0, Cache, CacheCb), - State#state{session_client_invalidator = Pid} + {Cache, Order} = do_register_session({PartialKey, + Session#session.session_id}, + Session, Max, Cache0, CacheCb, Options, Order0), + State#state{session_cache_client = Cache, + client_session_order = Order} end. exists_equivalent(_, []) -> false; exists_equivalent(#session{ peer_certificate = PeerCert, - own_certificate = OwnCert, + own_certificates = [OwnCert | _], compression_method = Compress, cipher_suite = CipherSuite, srp_username = SRP, ecc = ECC} , [#session{ peer_certificate = PeerCert, - own_certificate = OwnCert, + own_certificates = [OwnCert | _], compression_method = Compress, cipher_suite = CipherSuite, srp_username = SRP, @@ -614,21 +556,14 @@ add_trusted_certs(Pid, Trustedcerts, Db) -> session_cache(client, #state{session_cache_client = Cache}) -> Cache; -session_cache(server, #state{session_cache_server = Cache}) -> - Cache. +session_cache(server, _) -> + no_longer_defined. crl_db_info([_,_,_,Local], {internal, Info}) -> {Local, Info}; crl_db_info(_, UserCRLDb) -> UserCRLDb. -%% Only start a session invalidator if there is not -%% one already active -invalidate_session_cache(undefined, CacheCb, Cache) -> - start_session_validator(Cache, CacheCb, {invalidate_before, erlang:monotonic_time()}, undefined); -invalidate_session_cache(Pid, _CacheCb, _Cache) -> - Pid. - load_mitigation() -> MSec = rand:uniform(?LOAD_MITIGATION), receive diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 867d2cfc5a..040c4f1ebc 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -40,8 +40,13 @@ set_renegotiation_flag/2, set_client_verify_data/3, set_server_verify_data/3, - empty_connection_state/2, initial_connection_state/2, record_protocol_role/1, - step_encryption_state/1]). + set_max_fragment_length/2, + empty_connection_state/2, + empty_connection_state/3, + record_protocol_role/1, + step_encryption_state/1, + step_encryption_state_read/1, + step_encryption_state_write/1]). %% Compression -export([compress/3, uncompress/3, compressions/0]). @@ -87,7 +92,8 @@ pending_connection_state(ConnectionStates, write) -> maps:get(pending_write, ConnectionStates). %%-------------------------------------------------------------------- --spec activate_pending_connection_state(connection_states(), read | write, tls_connection | dtls_connection) -> +-spec activate_pending_connection_state(connection_states(), read | write, + tls_gen_connection | dtls_gen_connection) -> connection_states(). %% %% Description: Creates a new instance of the connection_states record @@ -136,6 +142,17 @@ step_encryption_state(#state{connection_states = ConnStates#{current_read => NewRead, current_write => NewWrite}}. +step_encryption_state_read(#state{connection_states = + #{pending_read := PendingRead} = ConnStates} = State) -> + NewRead = PendingRead#{sequence_number => 0}, + State#state{connection_states = + ConnStates#{current_read => NewRead}}. + +step_encryption_state_write(#state{connection_states = + #{pending_write := PendingWrite} = ConnStates} = State) -> + NewWrite = PendingWrite#{sequence_number => 0}, + State#state{connection_states = + ConnStates#{current_write => NewWrite}}. %%-------------------------------------------------------------------- -spec set_security_params(#security_parameters{}, #security_parameters{}, @@ -203,6 +220,33 @@ set_renegotiation_flag(Flag, #{current_read := CurrentRead0, pending_write => PendingWrite}. %%-------------------------------------------------------------------- +-spec set_max_fragment_length(term(), connection_states()) -> connection_states(). +%% +%% Description: Set maximum fragment length in all connection states +%%-------------------------------------------------------------------- +set_max_fragment_length(#max_frag_enum{enum = MaxFragEnum}, + #{current_read := CurrentRead0, + current_write := CurrentWrite0, + pending_read := PendingRead0, + pending_write := PendingWrite0} + = ConnectionStates) -> + MaxFragmentLength = if MaxFragEnum == 1 -> ?MAX_FRAGMENT_LENGTH_BYTES_1; + MaxFragEnum == 2 -> ?MAX_FRAGMENT_LENGTH_BYTES_2; + MaxFragEnum == 3 -> ?MAX_FRAGMENT_LENGTH_BYTES_3; + MaxFragEnum == 4 -> ?MAX_FRAGMENT_LENGTH_BYTES_4 + end, + CurrentRead = CurrentRead0#{max_fragment_length => MaxFragmentLength}, + CurrentWrite = CurrentWrite0#{max_fragment_length => MaxFragmentLength}, + PendingRead = PendingRead0#{max_fragment_length => MaxFragmentLength}, + PendingWrite = PendingWrite0#{max_fragment_length => MaxFragmentLength}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite, + pending_read => PendingRead, + pending_write => PendingWrite}; +set_max_fragment_length(_,ConnectionStates) -> + ConnectionStates. + +%%-------------------------------------------------------------------- -spec set_client_verify_data(current_read | current_write | current_both, binary(), connection_states())-> connection_states(). @@ -416,6 +460,10 @@ nonce_seed(_,_, CipherState) -> %%-------------------------------------------------------------------- empty_connection_state(ConnectionEnd, BeastMitigation) -> + MaxEarlyDataSize = ssl_config:get_max_early_data_size(), + empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize). +%% +empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) -> SecParams = empty_security_params(ConnectionEnd), #{security_parameters => SecParams, beast_mitigation => BeastMitigation, @@ -424,7 +472,11 @@ empty_connection_state(ConnectionEnd, BeastMitigation) -> mac_secret => undefined, secure_renegotiation => undefined, client_verify_data => undefined, - server_verify_data => undefined + server_verify_data => undefined, + max_early_data_size => MaxEarlyDataSize, + max_fragment_length => undefined, + trial_decryption => false, + early_data_limit => false }. empty_security_params(ConnectionEnd = ?CLIENT) -> @@ -451,19 +503,6 @@ record_protocol_role(client) -> record_protocol_role(server) -> ?SERVER. -initial_connection_state(ConnectionEnd, BeastMitigation) -> - #{security_parameters => - initial_security_params(ConnectionEnd), - sequence_number => 0, - beast_mitigation => BeastMitigation, - compression_state => undefined, - cipher_state => undefined, - mac_secret => undefined, - secure_renegotiation => undefined, - client_verify_data => undefined, - server_verify_data => undefined - }. - initial_security_params(ConnectionEnd) -> SecParams = #security_parameters{connection_end = ConnectionEnd, compression_algorithm = ?NULL}, diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index c0555046c3..d142ecf6da 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -68,6 +68,7 @@ master_secret, % opaque 48 resumption_master_secret, application_traffic_secret, + client_early_data_secret, client_random, % opaque 32 server_random, % opaque 32 exportable % boolean @@ -154,6 +155,8 @@ -define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)). -define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)). -define(TLS13_MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+256)). +-define(MAX_PADDING_LENGTH,256). +-define(MAX_MAC_LENGTH,32). %% -record(protocol_version, { %% major, % unit 8 diff --git a/lib/ssl/src/ssl_server_session_cache.erl b/lib/ssl/src/ssl_server_session_cache.erl new file mode 100644 index 0000000000..b444cf8865 --- /dev/null +++ b/lib/ssl/src/ssl_server_session_cache.erl @@ -0,0 +1,259 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%%---------------------------------------------------------------------- +%% Purpose: Handle server side pre TLS-1.3 reuse session storage. +%% This implements the RFC session reuse and not the session ticket extension +%% that inspired the RFC session tickets of TLS-1.3. +%%---------------------------------------------------------------------- + +-module(ssl_server_session_cache). +-behaviour(gen_server). + +-include_lib("kernel/include/logger.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_internal.hrl"). + +%% API +-export([start_link/2, + new_session_id/1, + register_session/2, + reuse_session/2 + ]). + +%% gen_server callbacks +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2 + %%code_change/3, + %%format_status/2 + ]). + +-record(state, {store_cb, + lifetime, + db, + max, + session_order, + id_generator, + listner + }). + +%%%=================================================================== +%%% API +%%%=================================================================== + +-spec start_link(pid(), map()) -> {ok, Pid :: pid()} | + {error, Error :: {already_started, pid()}} | + {error, Error :: term()} | + ignore. +start_link(ssl_unknown_listener = Listner, Map) -> + gen_server:start_link({local, Listner}, ?MODULE, [Listner, Map], []); +start_link(Listner, Map) -> + gen_server:start_link(?MODULE, [Listner, Map], []). + +%%-------------------------------------------------------------------- +-spec new_session_id(Pid::pid()) -> ssl:session_id(). +%% +%% Description: Creates a session id for the server. +%%-------------------------------------------------------------------- +new_session_id(Pid) -> + case call(Pid, new_session_id) of + {no_server, _} -> + crypto:strong_rand_bytes(32); + Result -> + Result + end. + +%%-------------------------------------------------------------------- +-spec reuse_session(pid(), ssl:session_id()) -> #session{} | not_reusable. +%% +%% Description: Returns session to reuse +%%-------------------------------------------------------------------- +reuse_session(Pid, SessionId) -> + case call(Pid, {reuse_session, SessionId}) of + {no_server, _} -> + not_reusable; + Result -> + Result + end. +%%-------------------------------------------------------------------- +-spec register_session(pid(), term()) -> ok. +%% +%% Description: Makes a session available for reuse +%%-------------------------------------------------------------------- +register_session(Pid, Session) -> + gen_server:cast(Pid, {register_session, Session}). + + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +-spec init(Args :: term()) -> {ok, State :: term()}. +init([Listner, #{lifetime := Lifetime, + session_cb := Cb, + session_cb_init_args := InitArgs, + max := Max + }]) -> + process_flag(trap_exit, true), + Monitor = monitor_listener(Listner), + DbRef = init(Cb, [{role, server} | InitArgs]), + State = #state{store_cb = Cb, + lifetime = Lifetime, + db = DbRef, + max = Max, + session_order = gb_trees:empty(), + id_generator = crypto:strong_rand_bytes(16), + listner = Monitor + }, + {ok, State}. + +-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) -> + {reply, Reply :: term(), NewState :: term()} . +handle_call(new_session_id, _From, #state{id_generator = IdGen} = State) -> + SessionId = session_id(IdGen), + {reply, SessionId, State}; +handle_call({reuse_session, SessionId}, _From, #state{store_cb = Cb, + db = Store0, + lifetime = Lifetime, + session_order = Order0} = State0) -> + case lookup(Cb, Store0, SessionId) of + undefined -> + {reply, not_reusable, State0}; + #session{internal_id = InId} = Session -> + case ssl_session:valid_session(Session, Lifetime) of + true -> + {reply, Session, State0}; + false -> + Order = invalidate_session(Cb, Store0, Order0, SessionId, InId), + {reply, not_reusable, State0#state{session_order = Order}} + end + end. + +-spec handle_cast(Request :: term(), State :: term()) -> + {noreply, NewState :: term()}. +handle_cast({register_session, #session{session_id = SessionId, time_stamp = TimeStamp} = Session0}, + #state{store_cb = Cb, + db = Store0, + max = Max, + lifetime = Lifetime, + session_order = Order0} + = State0) -> + InternalId = {TimeStamp, erlang:unique_integer([monotonic])}, + Session = Session0#session{internal_id = InternalId}, + State = case size(Cb, Store0) of + Max -> + %% Throw away oldest session table may not grow larger than max + {_, OldSessId, Order1} = gb_trees:take_smallest(Order0), + Store1 = delete(Cb, Store0, OldSessId), + %% Insert new session + Order = gb_trees:insert(InternalId, SessionId, Order1), + Store = update(Cb, Store1, SessionId, Session), + Store#state{db = Store, session_order = Order}; + Size when Size > 0 -> + {_, OldSessId, Order1} = gb_trees:take_smallest(Order0), + OldestSession = lookup(Cb, Store0, OldSessId), + case ssl_session:valid_session(OldestSession, Lifetime) of + true -> + Store = update(Cb, Store0, SessionId, Session#session{time_stamp = TimeStamp}), + State0#state{db = Store, + session_order = gb_trees:insert(InternalId, SessionId, Order0)}; + false -> + %% Throw away oldest session as it is not valid anymore + Store1 = delete(Cb, Store0, OldSessId), + Store = update(Cb, Store1, SessionId, Session#session{time_stamp = TimeStamp}), + State0#state{db = Store, + session_order = gb_trees:insert(InternalId, SessionId, Order1)} + end; + 0 -> + Store = update(Cb, Store0, SessionId, Session#session{time_stamp = TimeStamp}), + State0#state{db = Store, + session_order = gb_trees:insert(InternalId, SessionId, Order0)} + end, + {noreply, State}. + +-spec handle_info(Info :: timeout() | term(), State :: term()) -> + {noreply, NewState :: term()}. +handle_info({'DOWN', Monitor, _, _, _}, #state{listner = Monitor} = State) -> + {stop, normal, State}; +handle_info(_, State) -> + {noreply, State}. + +terminate(_, _) -> + ok. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +call(Pid, Msg) -> + try gen_server:call(Pid, Msg, infinity) + catch + exit:Reason -> + {no_server, Reason} + end. + +session_id(Key) -> + Unique1 = erlang:unique_integer(), + Unique2 = erlang:unique_integer(), + %% Obfuscate to avoid DoS attack possiblities + %% This id should be unpredictable an 32 bytes + %% and unique but have no other cryptographic requirements. + Bin1 = crypto:crypto_one_time(aes_128_ecb, Key, <<Unique1:128>>, true), + Bin2 = crypto:crypto_one_time(aes_128_ecb, Key, <<Unique2:128>>, true), + <<Bin1/binary, Bin2/binary>>. + +invalidate_session(Cb, Store, Order, SessionId, InternalId) -> + Cb:delete(Store, SessionId), + gb_trees:delete(InternalId, Order). + +init(Cb, Options) -> + Cb:init(Options). + +lookup(Cb, Cache, Key) -> + Cb:lookup(Cache, Key). + +update(ssl_server_session_cache_db = Cb, Cache, Key, Session) -> + Cb:update(Cache, Key, Session); +update(Cb, Cache, Key, Session) -> + Cb:update(Cache, Key, Session), + Cache. + +delete(ssl_server_session_cache_db = Cb, Cache, Key) -> + Cb:delete(Cache, Key); +delete(Cb, Cache, Key) -> + Cb:delete(Cache, Key), + Cache. + +size(Cb,Cache) -> + try Cb:size(Cache) of + Size -> + Size + catch + error:undef -> + Cb:foldl(fun(_, Acc) -> Acc + 1 end, 0, Cache) + end. + +monitor_listener(ssl_unknown_listener) -> + %% Backwards compatible Erlang node + %% global process. + undefined; +monitor_listener(Listen) when is_port(Listen) -> + erlang:monitor(port, Listen). diff --git a/lib/ssl/src/ssl_server_session_cache_db.erl b/lib/ssl/src/ssl_server_session_cache_db.erl new file mode 100644 index 0000000000..b55d2e306f --- /dev/null +++ b/lib/ssl/src/ssl_server_session_cache_db.erl @@ -0,0 +1,80 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%%---------------------------------------------------------------------- +%% Purpose: Handle server side TLS-1.2 session storage +%%---------------------------------------------------------------------- + +-module(ssl_server_session_cache_db). + +-behaviour(ssl_session_cache_api). + +%% API +-export([init/1, + terminate/1, + lookup/2, + update/3, + delete/2, + size/1]). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% Description: Return table reference. +%%-------------------------------------------------------------------- +init(_Options) -> + gb_trees:empty(). + +%%-------------------------------------------------------------------- +%% Description: Looks up a cach entry. Should be callable from any +%% process. +%%-------------------------------------------------------------------- +lookup(Cache, Key) -> + case gb_trees:lookup(Key, Cache) of + {value, Session} -> + Session; + none -> + undefined + end. + +%%-------------------------------------------------------------------- +%% Description: Caches a new session or updates a already cached one. +%% Will only be called from the ssl_server_cache process. +%%-------------------------------------------------------------------- +update(Cache, Key, Session) -> + gb_trees:insert(Key, Session, Cache). + +%%-------------------------------------------------------------------- +%% Description: Delets a cache entry. +%% Will only be called from the ssl_server_cache process. +%%-------------------------------------------------------------------- +delete(Cache, Key) -> + gb_trees:delete(Key, Cache). + +%%-------------------------------------------------------------- +%% Description: Returns the cache size +%%-------------------------------------------------------------------- +size(Cache) -> + gb_trees:size(Cache). + +terminate(_) -> + ok. diff --git a/lib/ssl/src/ssl_server_session_cache_sup.erl b/lib/ssl/src/ssl_server_session_cache_sup.erl new file mode 100644 index 0000000000..88f068a319 --- /dev/null +++ b/lib/ssl/src/ssl_server_session_cache_sup.erl @@ -0,0 +1,65 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Supervisor for a listen options tracker +%%---------------------------------------------------------------------- +-module(ssl_server_session_cache_sup). + +-behaviour(supervisor). + +-include("ssl_internal.hrl"). + +%% API +-export([start_link/0]). +-export([start_child/1]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +start_child(Listner) -> + supervisor:start_child(?MODULE, [Listner | [ssl_config:pre_1_3_session_opts(server)]]). + + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 3, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {ssl_server_session_cache, start_link, []}, + Restart = transient, % Should be restarted only on abnormal termination + Shutdown = 4000, + Modules = [ssl_server_session_cache], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. + diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index 3628a35aa2..ccc5c9ded7 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -30,11 +30,22 @@ -include("ssl_api.hrl"). %% Internal application API --export([is_new/2, client_select_session/4, server_select_session/7, valid_session/2]). +-export([is_new/2, client_select_session/4, server_select_session/5, valid_session/2, legacy_session_id/0]). -type seconds() :: integer(). %%-------------------------------------------------------------------- +-spec legacy_session_id() -> ssl:session_id(). +%% +%% Description: TLS-1.3 deprecates the session id but has a dummy +%% value for it for protocol backwards-compatibility reasons. +%% If now lower versions are configured this function can be called +%% for a dummy value. +%%-------------------------------------------------------------------- +legacy_session_id() -> + crypto:strong_rand_bytes(32). + +%%-------------------------------------------------------------------- -spec is_new(ssl:session_id(), ssl:session_id()) -> boolean(). %% %% Description: Checks if the session id decided by the server is a @@ -63,34 +74,28 @@ client_select_session({_, _, #{versions := Versions, case Version of {3, N} when N >= 4 -> - NewSession#session{session_id = crypto:strong_rand_bytes(32)}; + NewSession#session{session_id = legacy_session_id()}; _ -> do_client_select_session(ClientInfo, Cache, CacheCb, NewSession) end. %%-------------------------------------------------------------------- --spec server_select_session(ssl_record:ssl_version(), inet:port_number(), binary(), map(), - binary(),db_handle(), atom()) -> {binary(), #session{} | undefined}. +-spec server_select_session(ssl_record:ssl_version(), pid(), binary(), map(), + binary()) -> {binary(), #session{} | undefined}. %% %% Description: Should be called by the server side to get an id %% for the client hello message. %%-------------------------------------------------------------------- -server_select_session({_, Minor}, Port, <<>>, _SslOpts, _Cert, _, _) when Minor >= 4 -> - {ssl_manager:new_session_id(Port), undefined}; -server_select_session(_, Port, <<>>, _SslOpts, _Cert, _, _) -> - {ssl_manager:new_session_id(Port), undefined}; -server_select_session(_, Port, SuggestedId, Options, Cert, Cache, CacheCb) -> - LifeTime = case application:get_env(ssl, session_lifetime) of - {ok, Time} when is_integer(Time) -> Time; - _ -> ?'24H_in_sec' - end, - case is_resumable(SuggestedId, Port, Options, - Cache, CacheCb, LifeTime, Cert) +server_select_session(_, SessIdTracker, <<>>, _SslOpts, _Cert) -> + {ssl_server_session_cache:new_session_id(SessIdTracker), undefined}; +server_select_session(_, SessIdTracker, SuggestedId, Options, Cert) -> + case is_resumable(SuggestedId, SessIdTracker, Options, Cert) of {true, Resumed} -> {SuggestedId, Resumed}; {false, undefined} -> - {ssl_manager:new_session_id(Port), undefined} + Id = ssl_server_session_cache:new_session_id(SessIdTracker), + {Id, undefined} end. -spec valid_session(#session{}, seconds() | {invalidate_before, integer()}) -> boolean(). @@ -106,7 +111,15 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- - +do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession) when is_binary(SessionId) andalso + is_binary(SessionData) -> + try binary_to_term(SessionData, [safe]) of + Session -> + Session + catch + _:_ -> + NewSession#session{session_id = <<>>} + end; do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession) when is_binary(SessionId)-> case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of undefined -> @@ -115,8 +128,8 @@ do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, Cac Session end; do_client_select_session(ClientInfo, - Cache, CacheCb, #session{own_certificate = OwnCert} = NewSession) -> - case select_session(ClientInfo, Cache, CacheCb, OwnCert) of + Cache, CacheCb, #session{own_certificates = OwnCerts} = NewSession) -> + case select_session(ClientInfo, Cache, CacheCb, OwnCerts) of no_session -> NewSession#session{session_id = <<>>}; Session -> @@ -126,37 +139,35 @@ do_client_select_session(ClientInfo, select_session({_, _, #{reuse_sessions := Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true -> %% If reuse_sessions == false | save a new session should be created no_session; -select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) -> +select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCerts) -> Sessions = CacheCb:select_session(Cache, {HostIP, Port}), - select_session(Sessions, SslOpts, OwnCert). + select_session(Sessions, SslOpts, OwnCerts). select_session([], _, _) -> no_session; -select_session(Sessions, #{ciphers := Ciphers}, OwnCert) -> +select_session(Sessions, #{ciphers := Ciphers}, OwnCerts) -> IsNotResumable = fun(Session) -> not (resumable(Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) - andalso (OwnCert == Session#session.own_certificate)) + andalso (OwnCerts == Session#session.own_certificates)) end, case lists:dropwhile(IsNotResumable, Sessions) of [] -> no_session; [Session | _] -> Session end. -is_resumable(_, _, #{reuse_sessions := false}, _, _, _, _) -> +is_resumable(_, _, #{reuse_sessions := false}, _) -> {false, undefined}; -is_resumable(SuggestedSessionId, Port, #{reuse_session := ReuseFun} = Options, Cache, - CacheCb, SecondLifeTime, OwnCert) -> - case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of +is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCert) -> + case ssl_server_session_cache:reuse_session(SessIdTracker, SuggestedSessionId) of #session{cipher_suite = CipherSuite, - own_certificate = SessionOwnCert, + own_certificates = [SessionOwnCert | _], compression_method = Compression, is_resumable = IsResumable, peer_certificate = PeerCert} = Session -> case resumable(IsResumable) andalso (OwnCert == SessionOwnCert) - andalso valid_session(Session, SecondLifeTime) andalso reusable_options(Options, Session) andalso ReuseFun(SuggestedSessionId, PeerCert, Compression, CipherSuite) @@ -164,7 +175,7 @@ is_resumable(SuggestedSessionId, Port, #{reuse_session := ReuseFun} = Options, C true -> {true, Session}; false -> {false, undefined} end; - undefined -> + not_reusable -> {false, undefined} end. diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl index d438a9dafd..59fbd9b3c3 100644 --- a/lib/ssl/src/ssl_session_cache_api.erl +++ b/lib/ssl/src/ssl_session_cache_api.erl @@ -40,3 +40,5 @@ -callback foldl(fun(), term(), session_cache_ref()) -> term(). -callback select_session(session_cache_ref(), {ssl:host(), inet:port_number()} | inet:port_number()) -> [#session{}]. -callback size(session_cache_ref()) -> integer(). + +-optional_callbacks([select_session/2, foldl/3]). diff --git a/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl new file mode 100644 index 0000000000..69169cca0d --- /dev/null +++ b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl @@ -0,0 +1,90 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Supervisor for a listen options tracker +%%---------------------------------------------------------------------- +-module(ssl_upgrade_server_session_cache_sup). + +-behaviour(supervisor). + +-include("ssl_internal.hrl"). + +%% API +-export([start_link/0, + start_link_dist/0]). +-export([start_child/1]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, sup_name(normal)}, ?MODULE, []). + +start_link_dist() -> + supervisor:start_link({local, sup_name(dist)}, ?MODULE, []). + +start_child(Type) -> + SupName = sup_name(Type), + Children = supervisor:count_children(SupName), + Workers = proplists:get_value(workers, Children), + case Workers of + 0 -> + %% In case two upgrade servers are started very close to each other + %% only one will be able to grab the local name and we will use + %% that process for handling pre TLS-1.3 sessions for + %% servers with to us unknown listeners. + case supervisor:start_child(SupName, [ssl_unknown_listener, ssl_config:pre_1_3_session_opts(server)]) of + {error, {already_started, Child}} -> + {ok, Child}; + {ok, _} = Return -> + Return + end; + 1 -> + [{_,Child,_, _}] = supervisor:which_children(SupName), + {ok, Child} + end. + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 3, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {ssl_server_session_cache, start_link, []}, + Restart = transient, % Should be restarted only on abnormal termination + Shutdown = 4000, + Modules = [ssl_server_session_cache], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. + +sup_name(normal) -> + ?MODULE; +sup_name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). diff --git a/lib/ssl/src/ssl_v3.erl b/lib/ssl/src/ssl_v3.erl deleted file mode 100644 index 4eab60b440..0000000000 --- a/lib/ssl/src/ssl_v3.erl +++ /dev/null @@ -1,200 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2018. 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: Handles sslv3 encryption. -%%---------------------------------------------------------------------- - --module(ssl_v3). - --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_record.hrl"). % MD5 and SHA - --export([master_secret/3, finished/3, certificate_verify/3, - mac_hash/6, setup_keys/7, - suites/0]). --compile(inline). - -%%==================================================================== -%% Internal application API -%%==================================================================== - --spec master_secret(binary(), binary(), binary()) -> binary(). - -master_secret(PremasterSecret, ClientRandom, ServerRandom) -> - %% draft-ietf-tls-ssl-version3-00 - 6.2.2 - %% key_block = - %% MD5(master_secret + SHA(`A' + master_secret + - %% ServerHello.random + - %% ClientHello.random)) + - %% MD5(master_secret + SHA(`BB' + master_secret + - %% ServerHello.random + - %% ClientHello.random)) + - %% MD5(master_secret + SHA(`CCC' + master_secret + - %% ServerHello.random + - %% ClientHello.random)) + [...]; - Block = generate_keyblock(PremasterSecret, ClientRandom, ServerRandom, 48), - Block. - --spec finished(client | server, binary(), [binary()]) -> binary(). - -finished(Role, MasterSecret, Handshake) -> - %% draft-ietf-tls-ssl-version3-00 - 5.6.9 Finished - %% struct { - %% opaque md5_hash[16]; - %% opaque sha_hash[20]; - %% } Finished; - %% - %% md5_hash MD5(master_secret + pad2 + - %% MD5(handshake_messages + Sender + - %% master_secret + pad1)); - %% sha_hash SHA(master_secret + pad2 + - %% SHA(handshake_messages + Sender + - %% master_secret + pad1)); - Sender = get_sender(Role), - MD5 = handshake_hash(?MD5, MasterSecret, Sender, Handshake), - SHA = handshake_hash(?SHA, MasterSecret, Sender, Handshake), - <<MD5/binary, SHA/binary>>. - --spec certificate_verify(md5sha | sha, binary(), [binary()]) -> binary(). - -certificate_verify(md5sha, MasterSecret, Handshake) -> - %% md5_hash - %% MD5(master_secret + pad_2 + - %% MD5(handshake_messages + master_secret + pad_1)); - %% sha_hash - %% SHA(master_secret + pad_2 + - %% SHA(handshake_messages + master_secret + pad_1)); - - MD5 = handshake_hash(?MD5, MasterSecret, undefined, Handshake), - SHA = handshake_hash(?SHA, MasterSecret, undefined, Handshake), - <<MD5/binary, SHA/binary>>; - -certificate_verify(sha, MasterSecret, Handshake) -> - %% sha_hash - %% SHA(master_secret + pad_2 + - %% SHA(handshake_messages + master_secret + pad_1)); - - handshake_hash(?SHA, MasterSecret, undefined, Handshake). - --spec mac_hash(integer(), binary(), integer(), integer(), integer(), binary()) -> binary(). - -mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) -> - %% draft-ietf-tls-ssl-version3-00 - 5.2.3.1 - %% hash(MAC_write_secret + pad_2 + - %% hash(MAC_write_secret + pad_1 + seq_num + - %% SSLCompressed.type + SSLCompressed.length + - %% SSLCompressed.fragment)); - Mac = mac_hash(Method, Mac_write_secret, - [<<?UINT64(Seq_num), ?BYTE(Type), - ?UINT16(Length)>>, Fragment]), - Mac. - --spec setup_keys(binary(), binary(), binary(), - integer(), integer(), term(), integer()) -> - {binary(), binary(), binary(), - binary(), binary(), binary()}. - -setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) -> - KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom, - 2*(HS+KML+IVS)), - %% draft-ietf-tls-ssl-version3-00 - 6.2.2 - %% The key_block is partitioned as follows. - %% client_write_MAC_secret[CipherSpec.hash_size] - %% server_write_MAC_secret[CipherSpec.hash_size] - %% client_write_key[CipherSpec.key_material] - %% server_write_key[CipherSpec.key_material] - %% client_write_IV[CipherSpec.IV_size] /* non-export ciphers */ - %% server_write_IV[CipherSpec.IV_size] /* non-export ciphers */ - <<ClientWriteMacSecret:HS/binary, ServerWriteMacSecret:HS/binary, - ClientWriteKey:KML/binary, ServerWriteKey:KML/binary, - ClientIV:IVS/binary, ServerIV:IVS/binary>> = KeyBlock, - {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, - ServerWriteKey, ClientIV, ServerIV}. - --spec suites() -> [ssl_cipher_format:cipher_suite()]. - -suites() -> - [ - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA - ]. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- - -hash(?MD5, Data) -> - crypto:hash(md5, Data); -hash(?SHA, Data) -> - crypto:hash(sha, Data). - -%%pad_1(?NULL) -> -%% ""; -pad_1(?MD5) -> - <<"666666666666666666666666666666666666666666666666">>; -pad_1(?SHA) -> - <<"6666666666666666666666666666666666666666">>. -%%pad_2(?NULL) -> -%% ""; -pad_2(?MD5) -> - <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" - "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>; -pad_2(?SHA) -> - <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" - "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>. - -mac_hash(?NULL, _Secret, _Data) -> - <<>>; -mac_hash(Method, Secret, Data) -> - InnerHash = hash(Method, [Secret, pad_1(Method), Data]), - hash(Method, [Secret, pad_2(Method), InnerHash]). - -handshake_hash(Method, MasterSecret, undefined, Handshake) -> - InnerHash = hash(Method, [Handshake, MasterSecret, pad_1(Method)]), - hash(Method, [MasterSecret, pad_2(Method), InnerHash]); -handshake_hash(Method, MasterSecret, Sender, Handshake) -> - InnerHash = hash(Method, [Handshake, Sender, MasterSecret, pad_1(Method)]), - hash(Method, [MasterSecret, pad_2(Method), InnerHash]). - -get_sender(client) -> "CLNT"; -get_sender(server) -> "SRVR". - -generate_keyblock(MasterSecret, ServerRandom, ClientRandom, WantedLength) -> - gen(MasterSecret, [MasterSecret, ServerRandom, ClientRandom], - WantedLength, 0, $A, 1, []). - -gen(_Secret, _All, Wanted, Len, _C, _N, Acc) when Wanted =< Len -> - <<Block:Wanted/binary, _/binary>> = list_to_binary(lists:reverse(Acc)), - Block; -gen(Secret, All, Wanted, Len, C, N, Acc) -> - Prefix = lists:duplicate(N, C), - SHA = crypto:hash(sha, [Prefix, All]), - MD5 = crypto:hash(md5, [Secret, SHA]), - gen(Secret, All, Wanted, Len + 16, C+1, N+1, [MD5 | Acc]). diff --git a/lib/ssl/src/tls_client_ticket_store.erl b/lib/ssl/src/tls_client_ticket_store.erl index 70725ffc56..eb10adc9f1 100644 --- a/lib/ssl/src/tls_client_ticket_store.erl +++ b/lib/ssl/src/tls_client_ticket_store.erl @@ -25,10 +25,11 @@ -module(tls_client_ticket_store). -behaviour(gen_server). +-include("ssl_internal.hrl"). -include("tls_handshake_1_3.hrl"). %% API --export([find_ticket/2, +-export([find_ticket/5, get_tickets/2, lock_tickets/2, remove_tickets/1, @@ -51,7 +52,7 @@ -record(data, { pos = undefined, - hkdf, + cipher_suite, sni, psk, timestamp, @@ -69,9 +70,8 @@ start_link(Max, Lifetime) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [Max, Lifetime], []). -find_ticket(Pid, HashAlgos) -> - %% TODO use also SNI when selecting tickets - gen_server:call(?MODULE, {find_ticket, Pid, HashAlgos}, infinity). +find_ticket(Pid, Ciphers, HashAlgos, SNI, EarlyDataSize) -> + gen_server:call(?MODULE, {find_ticket, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize}, infinity). get_tickets(Pid, Keys) -> gen_server:call(?MODULE, {get_tickets, Pid, Keys}, infinity). @@ -86,8 +86,8 @@ remove_tickets([]) -> remove_tickets(Keys) -> gen_server:cast(?MODULE, {remove_tickets, Keys}). -store_ticket(Ticket, HKDF, SNI, PSK) -> - gen_server:call(?MODULE, {store_ticket, Ticket, HKDF, SNI, PSK}, infinity). +store_ticket(Ticket, CipherSuite, SNI, PSK) -> + gen_server:call(?MODULE, {store_ticket, Ticket, CipherSuite, SNI, PSK}, infinity). unlock_tickets(Pid, Keys) -> gen_server:call(?MODULE, {unlock, Pid, Keys}, infinity). @@ -108,8 +108,8 @@ init(Args) -> -spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) -> {reply, Reply :: term(), NewState :: term()} . -handle_call({find_ticket, Pid, HashAlgos}, _From, State) -> - Key = find_ticket(State, Pid, HashAlgos), +handle_call({find_ticket, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize}, _From, State) -> + Key = do_find_ticket(State, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize), {reply, Key, State}; handle_call({get_tickets, Pid, Keys}, _From, State) -> Data = get_tickets(State, Pid, Keys), @@ -117,8 +117,8 @@ handle_call({get_tickets, Pid, Keys}, _From, State) -> handle_call({lock, Pid, Keys}, _From, State0) -> State = lock_tickets(State0, Pid, Keys), {reply, ok, State}; -handle_call({store_ticket, Ticket, HKDF, SNI, PSK}, _From, State0) -> - State = store_ticket(State0, Ticket, HKDF, SNI, PSK), +handle_call({store_ticket, Ticket, CipherSuite, SNI, PSK}, _From, State0) -> + State = store_ticket(State0, Ticket, CipherSuite, SNI, PSK), {reply, ok, State}; handle_call({unlock, Pid, Keys}, _From, State0) -> State = unlock_tickets(State0, Pid, Keys), @@ -171,37 +171,84 @@ inital_state([Max, Lifetime]) -> max = Max }. - -find_ticket(_, _, []) -> - undefined; -find_ticket(#state{db = Db, - lifetime = Lifetime} = State, Pid, [Hash|T]) -> - case iterate_tickets(gb_trees:iterator(Db), Pid, Hash, Lifetime) of - none -> - find_ticket(State, Pid, T); +do_find_ticket(Iter, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize) -> + do_find_ticket(Iter, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize, []). +%% +do_find_ticket(_, _, _, [], _, _, []) -> + {undefined, undefined}; +do_find_ticket(_, _, _, [], _, _, Acc) -> + {undefined, last_elem(Acc)}; +do_find_ticket(#state{db = Db, + lifetime = Lifetime} = State, Pid, Ciphers, [Hash|T], SNI, EarlyDataSize, Acc) -> + case iterate_tickets(gb_trees:iterator(Db), Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize) of + {undefined, undefined} -> + do_find_ticket(State, Pid, Ciphers, T, SNI, EarlyDataSize, Acc); + {undefined, Key} -> + do_find_ticket(State, Pid, Ciphers, T, SNI, EarlyDataSize, [Key|Acc]); Key -> Key end. - -iterate_tickets(Iter0, Pid, Hash, Lifetime) -> +iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize) -> + iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, []). +%% +iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc) -> case gb_trees:next(Iter0) of - {Key, #data{hkdf = Hash, + {Key, #data{cipher_suite = {Cipher, Hash}, + sni = TicketSNI, + ticket = #new_session_ticket{ + extensions = Extensions}, timestamp = Timestamp, lock = Lock}, Iter} when Lock =:= undefined orelse Lock =:= Pid -> + MaxEarlyData = tls_handshake_1_3:get_max_early_data(Extensions), Age = erlang:system_time(seconds) - Timestamp, if Age < Lifetime -> - Key; + case verify_ticket_sni(SNI, TicketSNI) of + match -> + case lists:member(Cipher, Ciphers) of + true -> + Front = last_elem(Acc), + %% 'Key' can be used with early_data as both + %% block cipher and hash algorithm matches. + %% 'Front' can only be used for session + %% resumption. + case EarlyDataSize =:= undefined orelse + EarlyDataSize =< MaxEarlyData of + true -> + {Key, Front}; + false -> + %% 'Key' cannot be used for early_data as the data + %% to be sent exceeds the max limit for this ticket. + iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, + Lifetime, EarlyDataSize,[Key|Acc]) + end; + false -> + iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, [Key|Acc]) + end; + nomatch -> + iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc) + end; true -> - iterate_tickets(Iter, Pid, Hash, Lifetime) + iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc) end; {_, _, Iter} -> - iterate_tickets(Iter, Pid, Hash, Lifetime); + iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc); none -> - none + {undefined, last_elem(Acc)} end. +last_elem([_|_] = L) -> + lists:last(L); +last_elem([]) -> + undefined. + +verify_ticket_sni(undefined, _) -> + match; +verify_ticket_sni(SNI, SNI) -> + match; +verify_ticket_sni(_, _) -> + nomatch. %% Get tickets that are not locked by another process get_tickets(State, Pid, Keys) -> @@ -214,7 +261,7 @@ get_tickets(_, _, [], Acc) -> get_tickets(#state{db = Db} = State, Pid, [Key|T], Acc) -> try gb_trees:get(Key, Db) of #data{pos = Pos, - hkdf = HKDF, + cipher_suite = CipherSuite, psk = PSK, timestamp = Timestamp, ticket = NewSessionTicket, @@ -225,14 +272,23 @@ get_tickets(#state{db = Db} = State, Pid, [Key|T], Acc) -> ticket_age_add = AgeAdd, ticket_nonce = Nonce, ticket = Ticket, - extensions = _Extensions + extensions = Extensions } = NewSessionTicket, TicketAge = erlang:system_time(seconds) - Timestamp, ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd), Identity = #psk_identity{ identity = Ticket, obfuscated_ticket_age = ObfuscatedTicketAge}, - get_tickets(State, Pid, T, [{Key, Pos, Identity, PSK, Nonce, HKDF}|Acc]) + MaxEarlyData = tls_handshake_1_3:get_max_early_data(Extensions), + TicketData = #ticket_data{ + key = Key, + pos = Pos, + identity = Identity, + psk = PSK, + nonce = Nonce, + cipher_suite = CipherSuite, + max_size = MaxEarlyData}, + get_tickets(State, Pid, T, [TicketData|Acc]) catch _:_ -> get_tickets(State, Pid, T, Acc) @@ -286,7 +342,7 @@ collect_invalid_tickets(Iter0, Lifetime, Acc) -> end. -store_ticket(#state{db = Db0, max = Max} = State, Ticket, HKDF, SNI, PSK) -> +store_ticket(#state{db = Db0, max = Max} = State, Ticket, CipherSuite, SNI, PSK) -> Timestamp = erlang:system_time(seconds), Size = gb_trees:size(Db0), Db1 = if Size =:= Max -> @@ -296,7 +352,7 @@ store_ticket(#state{db = Db0, max = Max} = State, Ticket, HKDF, SNI, PSK) -> end, Key = {erlang:monotonic_time(), erlang:unique_integer([monotonic])}, Db = gb_trees:insert(Key, - #data{hkdf = HKDF, + #data{cipher_suite = CipherSuite, sni = SNI, psk = PSK, timestamp = Timestamp, diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 4eb03a26b1..8f25e5a3cd 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -19,376 +19,144 @@ %% %% %%---------------------------------------------------------------------- -%% Purpose: Handles an ssl connection, e.i. both the setup -%% e.i. SSL-Handshake, SSL-Alert and SSL-Cipher protocols and delivering -%% data to the application. All data on the connectinon is received and -%% sent according to the SSL-record protocol. +%% 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] / +%% 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] +%% 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_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED +%% +%% <- Possibly Receive -- | | +%% OCSP Stapel ------> | 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_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("tls_handshake_1_3.hrl"). -include("ssl_alert.hrl"). -include("tls_record.hrl"). -include("ssl_cipher.hrl"). -include("ssl_api.hrl"). -include("ssl_internal.hrl"). --include("ssl_srp.hrl"). --include_lib("public_key/include/public_key.hrl"). --include_lib("kernel/include/logger.hrl"). %% Internal application API %% Setup --export([start_fsm/8, start_link/8, init/1, pids/1]). - -%% State transition handling --export([next_event/3, next_event/4, - handle_protocol_record/3]). +-export([init/1]). -%% Handshake handling --export([renegotiation/2, renegotiate/2, send_handshake/2, - send_handshake_flight/1, - queue_handshake/2, queue_change_cipher/2, - reinit/1, reinit_handshake_data/1, select_sni_extension/1, - empty_connection_state/2]). - -%% Alert and close handling --export([send_alert/2, send_alert_in_connection/2, - send_sync_alert/2, - close/5, protocol_name/0]). - -%% Data handling --export([socket/4, setopts/3, getopts/3]). +-export([renegotiate/2]). %% gen_statem state functions --export([init/3, error/3, downgrade/3, %% Initiation and take down states - hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states +-export([initial_hello/3, + config_error/3, + downgrade/3, + hello/3, + user_hello/3, + wait_ocsp_stapling/3, + certify/3, + cipher/3, + abbreviated/3, connection/3]). -%% TLS 1.3 state functions (server) --export([start/3, %% common state with client - negotiated/3, - recvd_ch/3, - wait_cert/3, %% common state with client - wait_cv/3, %% common state with client - wait_eoed/3, - wait_finished/3, %% common state with client - wait_flight2/3, - connected/3 %% common state with client - ]). -%% TLS 1.3 state functions (client) --export([wait_cert_cr/3, - wait_ee/3, - wait_sh/3 - ]). + %% gen_statem callbacks --export([callback_mode/0, terminate/3, code_change/4, format_status/2]). +-export([callback_mode/0, + terminate/3, + code_change/4, + format_status/2]). --export([encode_handshake/4, send_key_update/2, update_cipher_key/2]). - --define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). - %%==================================================================== %% Internal application API %%==================================================================== -%%==================================================================== -%% Setup -%%==================================================================== -start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts, - User, {CbModule, _,_, _, _} = CbInfo, - Timeout) -> - try - {ok, Sender} = tls_sender:start(), - {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), - ssl_connection:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error - end; - -start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts, - User, {CbModule, _,_, _, _} = CbInfo, - Timeout) -> - try - {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]), - {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), - ssl_connection:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error - end. - -%%-------------------------------------------------------------------- --spec start_link(atom(), pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> - {ok, pid()} | ignore | {error, reason()}. -%% -%% Description: Creates a gen_statem process which calls Module:init/1 to -%% initialize. -%%-------------------------------------------------------------------- -start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. - -init([Role, Sender, Host, Port, Socket, {#{erl_dist := ErlDist}, _, _} = Options, User, CbInfo]) -> - process_flag(trap_exit, true), - link(Sender), - case ErlDist of - true -> - process_flag(priority, max); - _ -> - ok - end, +init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, Host, Port, Socket, Options, User, CbInfo), try - State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - initialize_tls_sender(State), - gen_statem:enter_loop(?MODULE, [], init, State) + State1 = #state{static_env = #static_env{session_cache = Cache, + session_cache_cb = CacheCb + }, + ssl_options = SslOptions, + session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0), + State = case Role of + client -> + Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0), + State1#state{session = Session}; + server -> + State1 + end, + tls_gen_connection:initialize_tls_sender(State), + gen_statem:enter_loop(?MODULE, [], initial_hello, State) catch throw:Error -> EState = State0#state{protocol_specific = Map#{error => Error}}, - gen_statem:enter_loop(?MODULE, [], error, EState) - end. - -pids(#state{protocol_specific = #{sender := Sender}}) -> - [self(), Sender]. - -%%==================================================================== -%% State transition handling -%%==================================================================== -next_record(_, #state{handshake_env = - #handshake_env{unprocessed_handshake_events = N} = HsEnv} - = State) when N > 0 -> - {no_record, State#state{handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; -next_record(_, #state{protocol_buffers = - #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts}, - connection_states = ConnectionStates, - ssl_options = #{padding_check := Check}} = State) -> - next_record(State, CipherTexts, ConnectionStates, Check); -next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, - protocol_specific = #{active_n_toggle := true} - } = State) -> - %% If ssl application user is not reading data wait to activate socket - flow_ctrl(State); - -next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, - protocol_specific = #{active_n_toggle := true} - } = State) -> - activate_socket(State); -next_record(_, State) -> - {no_record, State}. - - -flow_ctrl(#state{user_data_buffer = {_,Size,_}, - socket_options = #socket_options{active = false}, - bytes_to_read = undefined} = State) when Size =/= 0 -> - {no_record, State}; -flow_ctrl(#state{user_data_buffer = {_,Size,_}, - socket_options = #socket_options{active = false}, - bytes_to_read = 0} = State) when Size =/= 0 -> - {no_record, State}; -flow_ctrl(#state{user_data_buffer = {_,Size,_}, - socket_options = #socket_options{active = false}, - bytes_to_read = BytesToRead} = State) when (Size >= BytesToRead) andalso - (BytesToRead > 0) -> - {no_record, State}; -flow_ctrl(State) -> - activate_socket(State). - - -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) -> - case tls_socket:setopts(Transport, Socket, [{active, N}]) of - ok -> - {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; - _ -> - self() ! {CloseTag, Socket}, - {no_record, State} - end. - -%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one -%% -next_record(State, CipherTexts, ConnectionStates, Check) -> - next_record(State, CipherTexts, ConnectionStates, Check, []). -%% -next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State, - [CT|CipherTexts], ConnectionStates0, Check, Acc) -> - case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {#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 - next_record_done(State, [], ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); - [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) - end; - {Record, ConnectionStates} when Acc =:= [] -> - %% Singelton non-?APPLICATION_DATA record - deliver - next_record_done(State, CipherTexts, ConnectionStates, Record); - {_Record, _ConnectionStates_to_forget} -> - %% Not ?APPLICATION_DATA but we have accumulated fragments - %% -> build an ?APPLICATION_DATA record with concatenated fragments - %% and forget about decrypting this record - we'll decrypt it again next time - %% Will not work for stream ciphers - next_record_done(State, [CT|CipherTexts], ConnectionStates0, - #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))}); - #alert{} = Alert -> - Alert - end; -next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, - [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) -> - case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {#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 - next_record_done(State, [], ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); - [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) - end; - #alert{} = Alert -> - Alert - end; -next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) -> - next_record_done(State, CipherTexts, ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc))}); -next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, - [CT|CipherTexts], ConnectionStates0, Check, []) -> - case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {Record, ConnectionStates} -> - %% Singelton non-?APPLICATION_DATA record - deliver - next_record_done(State, CipherTexts, ConnectionStates, Record); - #alert{} = Alert -> - Alert + gen_statem:enter_loop(?MODULE, [], config_error, EState) end. -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}}. - -next_event(StateName, Record, State) -> - next_event(StateName, Record, State, []). -%% -next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) -> - case next_record(StateName, State0) of - {no_record, State} -> - ssl_connection:hibernate_after(StateName, State, Actions); - {Record, State} -> - next_event(StateName, Record, State, Actions); - #alert{} = Alert -> - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), - {stop, {shutdown, own_alert}, State0} - end; -next_event(StateName, #ssl_tls{} = Record, State, Actions) -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; -next_event(StateName, #alert{} = Alert, State, Actions) -> - {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}. - -%%% TLS record protocol level application data messages -handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, - #state{start_or_recv_from = From, - socket_options = #socket_options{active = false}} = State0) when From =/= undefined -> - case ssl_connection:read_application_data(Data, State0) of - {stop, _, _} = Stop-> - Stop; - {Record, #state{start_or_recv_from = Caller} = State} -> - TimerAction = case Caller of - undefined -> %% Passive recv complete cancel timer - [{{timeout, recv}, infinity, timeout}]; - _ -> - [] - end, - next_event(StateName, Record, State, TimerAction) - end; -handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) -> - case ssl_connection:read_application_data(Data, State0) of - {stop, _, _} = Stop-> - Stop; - {Record, State} -> - next_event(StateName, Record, State) - end; -%%% TLS record protocol level handshake messages -handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, - StateName, #state{protocol_buffers = - #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, - connection_env = #connection_env{negotiated_version = Version}, - static_env = #static_env{role = Role}, - ssl_options = Options} = State0) -> - try - %% Calculate the effective version that should be used when decoding an incoming handshake - %% message. - EffectiveVersion = effective_version(Version, Options, Role), - {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options), - State = - State0#state{protocol_buffers = - Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, - case Packets of - [] -> - assert_buffer_sanity(Buf, Options), - next_event(StateName, no_record, State); - _ -> - Events = tls_handshake_events(Packets), - case StateName of - connection -> - ssl_connection:hibernate_after(StateName, State, Events); - _ -> - HsEnv = State#state.handshake_env, - {next_state, StateName, - State#state{handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events - = unprocessed_events(Events)}}, Events} - end - end - catch throw:#alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State0) - end; -%%% TLS record protocol level change cipher messages -handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> - {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; -%%% TLS record protocol level Alert messages -handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try decode_alerts(EncAlerts) of - Alerts = [_|_] -> - handle_alerts(Alerts, {next_state, StateName, State}); - [] -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert), - Version, StateName, State); - #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State) - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), - Version, StateName, State) - - end; -%% Ignore unknown TLS record level protocol messages -handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> - {next_state, StateName, State, []}. -%%==================================================================== -%% Handshake handling -%%==================================================================== -renegotiation(Pid, WriteState) -> - gen_statem:call(Pid, {user_renegotiate, WriteState}). - renegotiate(#state{static_env = #static_env{role = client}, handshake_env = HsEnv} = State, Actions) -> %% Handle same way as if server requested @@ -411,236 +179,26 @@ renegotiate(#state{static_env = #static_env{role = server, 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, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = Flight0, - ssl_options = #{log_level := LogLevel}, - connection_states = ConnectionStates0} = State0) -> - {BinHandshake, ConnectionStates, Hist} = - encode_handshake(Handshake, Version, ConnectionStates0, Hist0), - ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake), - ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake), - - State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}, - flight_buffer = Flight0 ++ [BinHandshake]}. - - -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - flight_buffer = Flight} = State0) -> - tls_socket:send(Transport, Socket, Flight), - {State0#state{flight_buffer = []}, []}. - - -queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = Flight0, - ssl_options = #{log_level := LogLevel}, - connection_states = ConnectionStates0} = State0) -> - {BinChangeCipher, ConnectionStates} = - encode_change_cipher(Msg, Version, ConnectionStates0), - ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher), - State0#state{connection_states = ConnectionStates, - flight_buffer = Flight0 ++ [BinChangeCipher]}. - -reinit(#state{protocol_specific = #{sender := Sender}, - connection_env = #connection_env{negotiated_version = Version}, - connection_states = #{current_write := Write}} = State) -> - tls_sender:update_connection_state(Sender, Write, Version), - reinit_handshake_data(State). - -reinit_handshake_data(#state{handshake_env = HsEnv} =State) -> - %% premaster_secret, public_key_info and tls_handshake_info - %% are only needed during the handshake phase. - %% To reduce memory foot print of a connection reinitialize them. - State#state{ - handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), - public_key_info = undefined, - premaster_secret = undefined} - }. - -select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> - SNI; -select_sni_extension(_) -> - undefined. - -empty_connection_state(ConnectionEnd, BeastMitigation) -> - ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation). - -%%==================================================================== -%% Alert and close handling -%%==================================================================== - -%%-------------------------------------------------------------------- --spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> - {iolist(), ssl_record:connection_states()}. -%% -%% Description: Encodes an alert -%%-------------------------------------------------------------------- -encode_alert(#alert{} = Alert, Version, ConnectionStates) -> - tls_record:encode_alert_record(Alert, Version, ConnectionStates). - -send_alert(Alert, #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{log_level := LogLevel}, - connection_states = ConnectionStates0} = StateData0) -> - {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), - tls_socket:send(Transport, Socket, BinMsg), - ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - StateData0#state{connection_states = ConnectionStates}. - -%% If an ALERT sent in the connection state, should cause the TLS -%% connection to end, we need to synchronize with the tls_sender -%% process so that the ALERT if possible (that is the tls_sender process is -%% not blocked) is sent before the connection process terminates and -%% thereby closes the transport socket. -send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) -> - send_sync_alert(Alert, State); -send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) -> - send_sync_alert(Alert, State); -send_alert_in_connection(Alert, - #state{protocol_specific = #{sender := Sender}}) -> - tls_sender:send_alert(Sender, Alert). -send_sync_alert( - Alert, #state{protocol_specific = #{sender := Sender}} = State) -> - try tls_sender:send_and_ack_alert(Sender, Alert) - catch - _:_ -> - throw({stop, {shutdown, own_alert}, State}) - end. - -%% User closes or recursive call! -close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> - tls_socket:setopts(Transport, Socket, [{active, false}]), - Transport:shutdown(Socket, write), - _ = Transport:recv(Socket, 0, Timeout), - ok; -%% Peer closed socket -close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> - close({close, 0}, Socket, Transport, ConnectionStates, Check); -%% We generate fatal alert -close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> - %% Standard trick to try to make sure all - %% data sent to the tcp port is really delivered to the - %% peer application before tcp port is closed so that the peer will - %% get the correct TLS alert message and not only a transport close. - %% Will return when other side has closed or after timout millisec - %% e.g. we do not want to hang if something goes wrong - %% with the network but we want to maximise the odds that - %% peer application gets all data sent on the tcp connection. - close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); -close(downgrade, _,_,_,_) -> - ok; -%% Other -close(_, Socket, Transport, _,_) -> - tls_socket:close(Transport, Socket). -protocol_name() -> - "TLS". - -%%==================================================================== -%% Data handling -%%==================================================================== - -socket(Pids, Transport, Socket, Trackers) -> - tls_socket:socket(Pids, Transport, Socket, ?MODULE, Trackers). - -setopts(Transport, Socket, Other) -> - tls_socket:setopts(Transport, Socket, Other). - -getopts(Transport, Socket, Tag) -> - tls_socket:getopts(Transport, Socket, Tag). + tls_gen_connection:next_event(hello, no_record, State, Actions). %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec init(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- - -init({call, From}, {start, Timeout}, - #state{static_env = #static_env{role = client, - host = Host, - port = Port, - transport_cb = Transport, - socket = Socket, - session_cache = Cache, - session_cache_cb = CacheCb}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, - connection_env = CEnv, - ssl_options = #{log_level := LogLevel, - %% Use highest version in initial ClientHello. - %% Versions is a descending list of supported versions. - versions := [HelloVersion|_] = Versions, - session_tickets := SessionTickets} = SslOpts, - session = NewSession, - connection_states = ConnectionStates0 - } = State0) -> - KeyShare = maybe_generate_client_shares(SslOpts), - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession), - %% Update UseTicket in case of automatic session resumption - {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0), - TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), - Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, - Renegotiation, - Session#session.own_certificate, - KeyShare, - TicketData), - - Handshake0 = ssl_handshake:init_handshake_history(), - - %% Update pre_shared_key extension with binders (TLS 1.3) - Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion), - - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello1, HelloVersion, ConnectionStates0, Handshake0), - - tls_socket:send(Transport, Socket, BinMsg), - ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1), - ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - - %% RequestedVersion is used as the legacy record protocol version and shall be - %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the - %% lowest supported protocol version. - %% - %% negotiated_version is also used by the TLS 1.3 state machine and is set after - %% ServerHello is processed. - RequestedVersion = tls_record:hello_version(Versions), - State = State1#state{connection_states = ConnectionStates, - connection_env = CEnv#connection_env{ - negotiated_version = RequestedVersion}, - session = Session, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake}, - start_or_recv_from = From, - key_share = KeyShare}, - next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]); - -init(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). +initial_hello(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- --spec error(gen_statem:event_type(), +-spec config_error(gen_statem:event_type(), {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -error({call, From}, {start, _Timeout}, - #state{protocol_specific = #{error := Error}} = State) -> - {stop_and_reply, {shutdown, normal}, - [{reply, From, {error, Error}}], State}; - -error({call, _} = Call, Msg, State) -> - gen_handshake(?FUNCTION_NAME, Call, Msg, State); -error(_, _, _) -> - {keep_state_and_data, [postpone]}. +config_error(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- -spec hello(gen_statem:event_type(), @@ -656,90 +214,67 @@ hello(internal, #client_hello{extensions = Extensions} = Hello, handshake_env = HsEnv#handshake_env{hello = Hello}}, [{reply, From, {ok, Extensions}}]}; hello(internal, #server_hello{extensions = Extensions} = Hello, - #state{ssl_options = #{handshake := hello}, + #state{ssl_options = #{ + handshake := hello}, handshake_env = HsEnv, - start_or_recv_from = From} = State) -> - {next_state, user_hello, State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{hello = Hello}}, - [{reply, From, {ok, Extensions}}]}; - -hello(internal, #client_hello{client_version = ClientVersion} = Hello, - #state{connection_states = ConnectionStates0, - static_env = #static_env{ - port = Port, - session_cache = Cache, - session_cache_cb = CacheCb}, - handshake_env = #handshake_env{kex_algorithm = KeyExAlg, - renegotiation = {Renegotiation, _}, - negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = CEnv, - session = #session{own_certificate = Cert} = Session0, - ssl_options = SslOpts} = State) -> - - case choose_tls_version(SslOpts, Hello) of - 'tls_v1.3' -> + start_or_recv_from = From} = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{ + hello = Hello}}, [{reply, From, {ok, Extensions}}]}; +hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{ssl_options = SslOpts0, + connection_env = CEnv} = State0) -> + case choose_tls_fsm(SslOpts0, Hello) of + tls_1_3_fsm -> %% Continue in TLS 1.3 'start' state - {next_state, start, State, [{next_event, internal, Hello}]}; - 'tls_v1.2' -> - case tls_handshake:hello(Hello, - SslOpts, - {Port, Session0, Cache, CacheCb, - ConnectionStates0, Cert, KeyExAlg}, - Renegotiation) of - #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, ClientVersion, hello, - State#state{connection_env = CEnv#connection_env{negotiated_version - = ClientVersion}}); - {Version, {Type, Session}, - ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> - Protocol = case Protocol0 of - undefined -> CurrentProtocol; - _ -> Protocol0 - end, - gen_handshake(?FUNCTION_NAME, - internal, - {common_client_hello, Type, ServerHelloExt}, - State#state{connection_states = ConnectionStates, - connection_env = CEnv#connection_env{negotiated_version = Version}, - handshake_env = HsEnv#handshake_env{ - hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, - negotiated_protocol = Protocol}, - session = Session - }) + {next_state, start, State0, [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}; + tls_1_0_to_1_2_fsm -> + case handle_client_hello(Hello, State0) of + {ServerHelloExt, Type, State} -> + {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}; + Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, + State0#state{connection_env = CEnv#connection_env{negotiated_version + = ClientVersion}}) end - end; -hello(internal, #server_hello{} = Hello, +hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv, static_env = #static_env{role = client}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - ssl_options = SslOptions} = State) -> - case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> %%TODO - ssl_connection:handle_own_alert(Alert, ReqVersion, hello, + handshake_env = #handshake_env{ + ocsp_stapling_state = OcspState0, + renegotiation = {Renegotiation, _}} = HsEnv, + session = #session{session_id = OldId}, + ssl_options = SslOptions} = State) -> + case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ReqVersion, hello, State#state{connection_env = - CEnv#connection_env{negotiated_version = ReqVersion}}); + CEnv#connection_env{negotiated_version = ReqVersion} + }); %% Legacy TLS 1.2 and older - {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> - ssl_connection:handle_session(Hello, - Version, NewId, ConnectionStates, ProtoExt, Protocol, State); + {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} -> + 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)}}); %% TLS 1.3 - {next_state, wait_sh, SelectedVersion} -> + {next_state, wait_sh, SelectedVersion, OcspState} -> %% Continue in TLS 1.3 'wait_sh' state {next_state, wait_sh, - State#state{ - connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}}, - [{next_event, internal, Hello}]} + State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state = maps:merge(OcspState0,OcspState)}, + connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}}, + [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]} end; hello(info, Event, State) -> - handle_info(Event, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Event, ?FUNCTION_NAME, State); hello(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). user_hello(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec abbreviated(gen_statem:event_type(), term(), #state{}) -> @@ -748,7 +283,16 @@ user_hello(Type, Event, State) -> abbreviated(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); abbreviated(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection: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(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +wait_ocsp_stapling(Type, Event, State) -> + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec certify(gen_statem:event_type(), term(), #state{}) -> @@ -757,7 +301,7 @@ abbreviated(Type, Event, State) -> certify(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); certify(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), term(), #state{}) -> @@ -766,7 +310,7 @@ certify(Type, Event, State) -> cipher(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); cipher(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), @@ -779,50 +323,16 @@ connection({call, From}, {user_renegotiate, WriteState}, #state{connection_states = ConnectionStates} = State) -> {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}}, [{next_event,{call, From}, renegotiate}]}; -connection({call, From}, - {close, {Pid, _Timeout}}, - #state{connection_env = #connection_env{terminated = closed} = CEnv, - protocol_specific = PS} = State) -> - {next_state, downgrade, State#state{connection_env = - CEnv#connection_env{terminated = true, - downgrade = {Pid, From}}, - protocol_specific = PS#{active_n_toggle => true, - active_n => 1} - }, - [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]}; -connection({call, From}, - {close,{Pid, Timeout}}, - #state{connection_states = ConnectionStates, - protocol_specific = #{sender := Sender} = PS, - connection_env = CEnv - } = State0) -> - case tls_sender:downgrade(Sender, Timeout) of - {ok, Write} -> - %% User downgrades connection - %% When downgrading an TLS connection to a transport connection - %% we must recive the close alert from the peer before releasing the - %% transport socket. - State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - State0#state{connection_states = - ConnectionStates#{current_write => Write}}), - {next_state, downgrade, State#state{connection_env = - CEnv#connection_env{downgrade = {Pid, From}, - terminated = true}, - protocol_specific = PS#{active_n_toggle => true, - active_n => 1} - }, - [{timeout, Timeout, downgrade}]}; - {error, timeout} -> - {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]} - end; connection(internal, #hello_request{}, #state{static_env = #static_env{role = client, host = Host, port = Port, session_cache = Cache, session_cache_cb = CacheCb}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}}, - session = #session{own_certificate = Cert} = Session0, + handshake_env = #handshake_env{ + renegotiation = {Renegotiation, peer}, + ocsp_stapling_state = OcspState}, + session = #session{own_certificates = OwnCerts} = Session0, ssl_options = SslOpts, protocol_specific = #{sender := Pid}, connection_states = ConnectionStates} = State0) -> @@ -831,11 +341,13 @@ connection(internal, #hello_request{}, Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Session#session.session_id, - Renegotiation, Cert, undefined, - undefined), - {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}, - session = Session}), - next_event(hello, no_record, State, Actions) + Renegotiation, OwnCerts, undefined, + undefined, maps:get(ocsp_nonce, OcspState, undefined)), + {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 _:_ -> {stop, {shutdown, sender_blocked}, State0} @@ -844,16 +356,18 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{role = client, host = Host, port = Port}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - session = #session{own_certificate = Cert}, + handshake_env = #handshake_env{ + renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState}, + session = #session{own_certificates = OwnCerts}, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, - <<>>, Renegotiation, Cert, undefined, - undefined), + <<>>, Renegotiation, OwnCerts, undefined, + undefined, maps:get(ocsp_nonce, OcspState, undefined)), - {State, Actions} = send_handshake(Hello, State0), - next_event(hello, no_record, State, Actions); + {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, @@ -867,36 +381,21 @@ connection(internal, #client_hello{} = Hello, %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), {ok, Write} = tls_sender:renegotiate(Sender), - 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}]); + 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), - send_alert_in_connection(Alert, State0), - State = reinit_handshake_data(State0), - next_event(?FUNCTION_NAME, no_record, State); - -connection(internal, #new_session_ticket{} = NewSessionTicket, State) -> - %% TLS 1.3 - handle_new_session_ticket(NewSessionTicket, State), - next_event(?FUNCTION_NAME, no_record, State); - -connection(internal, #key_update{} = KeyUpdate, State0) -> - %% TLS 1.3 - case handle_key_update(KeyUpdate, State0) of - {ok, State} -> - next_event(?FUNCTION_NAME, no_record, State); - {error, State, Alert} -> - ssl_connection:handle_own_alert(Alert, {3,4}, connection, State), - next_event(?FUNCTION_NAME, no_record, State) - end; - + 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) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), #state{}) -> @@ -918,120 +417,9 @@ downgrade(info, {CloseTag, Socket}, State) -> {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State}; downgrade(info, Info, State) -> - handle_info(Info, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Info, ?FUNCTION_NAME, State); downgrade(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). - -%%-------------------------------------------------------------------- -%% TLS 1.3 state functions -%%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- --spec start(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -start(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -start(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec negotiated(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -negotiated(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -negotiated(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec recvd_ch(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -recvd_ch(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -recvd_ch(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_cert(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cert(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_cert(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_cv(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cv(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_cv(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_eoed(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_eoed(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_eoed(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_finished(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_finished(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_finished(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_flight2(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_flight2(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_flight2(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec connected(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -connected(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -connected(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_cert_cr(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cert_cr(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_cert_cr(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_ee(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_ee(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_ee(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_sh(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_sh(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_sh(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %-------------------------------------------------------------------- %% gen_statem callbacks @@ -1043,14 +431,14 @@ terminate({shutdown, {sender_died, Reason}}, _StateName, #state{static_env = #static_env{socket = Socket, transport_cb = Transport}} = State) -> - ssl_connection:handle_trusted_certs_db(State), - close(Reason, Socket, Transport, undefined, undefined); + ssl_gen_statem:handle_trusted_certs_db(State), + tls_gen_connection:close(Reason, Socket, Transport, undefined, undefined); terminate(Reason, StateName, State) -> - catch ssl_connection:terminate(Reason, StateName, State), + catch ssl_gen_statem:terminate(Reason, StateName, State), ensure_sender_terminate(Reason, State). format_status(Type, Data) -> - ssl_connection:format_status(Type, Data). + ssl_gen_statem:format_status(Type, Data). code_change(_OldVsn, StateName, State, _) -> {ok, StateName, State}. @@ -1064,23 +452,18 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac erl_dist := IsErlDist, client_renegotiation := ClientRenegotiation} = SSLOptions, ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), - SessionCacheCb = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - Cb; - _ -> - ssl_session_cache - end, - InternalActiveN = case application:get_env(ssl, internal_active_n) of - {ok, N} when is_integer(N) andalso (not IsErlDist) -> - N; - _ -> - ?INTERNAL_ACTIVE_N + #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role), + InternalActiveN = case IsErlDist of + true -> + ?INTERNAL_ACTIVE_N; + false -> + ssl_config:get_internal_active_n() end, UserMonitor = erlang:monitor(process, User), InitStatEnv = #static_env{ role = Role, transport_cb = CbModule, - protocol_cb = ?MODULE, + protocol_cb = tls_gen_connection, data_tag = DataTag, close_tag = CloseTag, error_tag = ErrorTag, @@ -1101,7 +484,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac connection_env = #connection_env{user_application = {UserMonitor, User}}, socket_options = SocketOptions, ssl_options = SSLOptions, - session = #session{is_resumable = new}, + session = #session{is_resumable = false}, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, user_data_buffer = {[],0,[]}, @@ -1113,287 +496,75 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac } }. -initialize_tls_sender(#state{static_env = #static_env{ - role = Role, - transport_cb = Transport, - socket = Socket, - trackers = Trackers - }, - connection_env = #connection_env{negotiated_version = Version}, - socket_options = SockOpts, - ssl_options = #{renegotiate_at := RenegotiateAt, - key_update_at := KeyUpdateAt, - log_level := LogLevel}, - connection_states = #{current_write := ConnectionWriteState}, - protocol_specific = #{sender := Sender}}) -> - Init = #{current_write => ConnectionWriteState, - role => Role, - socket => Socket, - socket_options => SockOpts, - trackers => Trackers, - transport_cb => Transport, - negotiated_version => Version, - renegotiate_at => RenegotiateAt, - key_update_at => KeyUpdateAt, - log_level => LogLevel}, - tls_sender:initialize(Sender, Init). - -next_tls_record(Data, StateName, - #state{protocol_buffers = - #protocol_buffers{tls_record_buffer = Buf0, - tls_cipher_texts = CT0} = Buffers, - ssl_options = SslOpts} = State0) -> - Versions = - %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all - %% record version are accepted when receiving initial ClientHello and - %% ServerHello. This can happen in state 'hello' in case of all TLS - %% versions and also in state 'start' when TLS 1.3 is negotiated. - %% After the version is negotiated all subsequent TLS records shall have - %% the proper legacy_record_version (= negotiated_version). - %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this - %% point it is the same as the negotiated protocol version. - %% TODO: Refactor state machine and introduce a record_protocol_version beside - %% the negotiated_version. - case StateName of - State when State =:= hello orelse - State =:= start -> - [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]; - _ -> - State0#state.connection_env#connection_env.negotiated_version - end, - case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of - {Records, Buf1} -> - CT1 = CT0 ++ Records, - next_record(StateName, State0#state{protocol_buffers = - Buffers#protocol_buffers{tls_record_buffer = Buf1, - tls_cipher_texts = CT1}}); - #alert{} = Alert -> - handle_record_alert(Alert, State0) - end. - - -handle_record_alert(Alert, _) -> - Alert. - -tls_handshake_events(Packets) -> - lists:map(fun(Packet) -> - {next_event, internal, {handshake, Packet}} - end, Packets). - -%% raw data from socket, upack records -handle_info({Protocol, _, Data}, StateName, - #state{static_env = #static_env{data_tag = Protocol}, - connection_env = #connection_env{negotiated_version = Version}} = State0) -> - case next_tls_record(Data, StateName, State0) of - {Record, State} -> - next_event(StateName, Record, State); - #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State0) - end; -handle_info({PassiveTag, Socket}, StateName, - #state{static_env = #static_env{socket = Socket, - passive_tag = PassiveTag}, - start_or_recv_from = From, - protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, - protocol_specific = PS - } = State0) -> - case (From =/= undefined) andalso (CTs == []) of - 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, - State0#state{protocol_specific = PS#{active_n_toggle => true}}) - end; -handle_info({CloseTag, Socket}, StateName, - #state{static_env = #static_env{ - role = Role, - host = Host, - port = Port, - socket = Socket, - close_tag = CloseTag}, - handshake_env = #handshake_env{renegotiation = Type}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session} = State) when StateName =/= connection -> - ssl_connection:maybe_invalidate_session(Version, Type, Role, Host, Port, Session), - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown, transport_closed}, State}; -handle_info({CloseTag, Socket}, StateName, - #state{static_env = #static_env{ - role = Role, - socket = Socket, - close_tag = CloseTag}, - socket_options = #socket_options{active = Active}, - protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, - user_data_buffer = {_,BufferSize,_}, - protocol_specific = PS} = State) -> - - %% Note that as of TLS 1.1, - %% failure to properly close a connection no longer requires that a - %% session not be resumed. This is a change from TLS 1.0 to conform - %% with widespread implementation practice. - - case (Active == false) andalso ((CTs =/= []) or (BufferSize =/= 0)) of - false -> - %% As invalidate_sessions here causes performance issues, - %% we will conform to the widespread implementation - %% practice and go aginst the spec - %% case Version of - %% {3, N} when N >= 1 -> - %% ok; - %% _ -> - %% invalidate_session(Role, Host, Port, Session) - %% ok - %% end, - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown, transport_closed}, State}; - true -> - %% Fixes non-delivery of final TLS record in {active, once}. - %% Basically allows the application the opportunity to set {active, once} again - %% and then receive the final message. Set internal active_n to zero - %% to ensure socket close message is sent if there is not enough data to deliver. - next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}}) - end; -handle_info({'EXIT', Sender, Reason}, _, - #state{protocol_specific = #{sender := Sender}} = State) -> - {stop, {shutdown, {sender_died, Reason}}, State}; -handle_info(Msg, StateName, State) -> - ssl_connection:StateName(info, Msg, State, ?MODULE). - -handle_alerts([], Result) -> - 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}, - user_data_buffer = {_,BufferSize,_}, - protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} = - State}) when (BufferSize =/= 0) orelse - (CTs =/= []) -> - {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}}; -handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); -handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). - -encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> - Frag = tls_handshake:encode_handshake(Handshake, Version), - Hist = ssl_handshake:update_handshake_history(Hist0, Frag), - {Encoded, ConnectionStates} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), - {Encoded, ConnectionStates, Hist}. - -encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> - tls_record:encode_change_cipher_spec(Version, ConnectionStates). - -decode_alerts(Bin) -> - ssl_alert:decode(Bin). - -gen_handshake(StateName, Type, Event, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try ssl_connection:StateName(Type, Event, State, ?MODULE) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - Version, StateName, State) - end. - - -gen_handshake_1_3(StateName, Type, Event, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - Version, StateName, State) +handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) -> + case tls_dtls_connection:handle_sni_extension(State0, Hello) of + #state{connection_states = ConnectionStates0, + static_env = #static_env{trackers = Trackers}, + handshake_env = #handshake_env{ + kex_algorithm = KeyExAlg, + renegotiation = {Renegotiation, _}, + negotiated_protocol = CurrentProtocol, + sni_guided_cert_selection = SNICertSelection} = HsEnv, + connection_env = CEnv, + session = #session{own_certificates = OwnCerts} = Session0, + ssl_options = SslOpts} = State -> + SessionTracker = proplists:get_value(session_id_tracker, Trackers), + case tls_handshake:hello(Hello, + SslOpts, + {SessionTracker, Session0, + ConnectionStates0, OwnCerts, KeyExAlg}, + Renegotiation) of + #alert{} = Alert -> + Alert; + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt0, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + ServerHelloExt = + case SNICertSelection of + true -> + ServerHelloExt0#{sni => #sni{hostname = ""}}; + false -> + ServerHelloExt0 + end, + {ServerHelloExt, Type, State#state{connection_states = ConnectionStates, + connection_env = CEnv#connection_env{negotiated_version = Version}, + handshake_env = HsEnv#handshake_env{ + hashsign_algorithm = HashSign, + client_hello_version = ClientVersion, + negotiated_protocol = Protocol}, + session = Session + }} + end; + #alert{} = Alert -> + Alert end. gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try tls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), Version, StateName, State) end; gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try tls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), Version, StateName, State) end. - -gen_info_1_3(Event, connected = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, - malformed_data), - Version, StateName, State) - end; - -gen_info_1_3(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - Version, StateName, State) - end. - -unprocessed_events(Events) -> - %% The first handshake event will be processed immediately - %% as it is entered first in the event queue and - %% when it is processed there will be length(Events)-1 - %% handshake events left to process before we should - %% process more TLS-records received on the socket. - erlang:length(Events)-1. - - -assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, - #{max_handshake_size := Max}) when - Length =< Max -> - case size(Rest) of - N when N < Length -> - true; - N when N > Length -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - too_big_handshake_data)); - _ -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data)) - end; -assert_buffer_sanity(Bin, _) -> - case size(Bin) of - N when N < 3 -> - true; - _ -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data)) - end. - ensure_sender_terminate(downgrade, _) -> ok; %% Do not terminate sender during downgrade phase ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> @@ -1407,125 +578,17 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> end, spawn(Kill). -maybe_generate_client_shares(#{versions := [Version|_], - supported_groups := - #supported_groups{ - supported_groups = [Group|_]}}) - when Version =:= {3,4} -> - %% Generate only key_share entry for the most preferred group - ssl_cipher:generate_client_shares([Group]); -maybe_generate_client_shares(_) -> - undefined. - -choose_tls_version(#{versions := Versions}, - #client_hello{ - extensions = #{client_hello_versions := - #client_hello_versions{versions = ClientVersions} - } - }) -> +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 {3,4} -> - 'tls_v1.3'; + tls_1_3_fsm; _Else -> - 'tls_v1.2' + tls_1_0_to_1_2_fsm end; -choose_tls_version(_, _) -> - 'tls_v1.2'. - - -%% Special version handling for TLS 1.3 clients: -%% In the shared state 'init' negotiated_version is set to requested version and -%% that is expected by the legacy part of the state machine. However, in order to -%% be able to process new TLS 1.3 extensions, the effective version shall be set -%% {3,4}. -%% When highest supported version is {3,4} the negotiated version is set to {3,3}. -effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} -> - Version; -%% Use highest supported version during startup (TLS server, all versions). -effective_version(undefined, #{versions := [Version|_]}, _) -> - Version; -%% Use negotiated version in all other cases. -effective_version(Version, _, _) -> - Version. - - -handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) -> - ok; -handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket, - #state{connection_states = ConnectionStates, - ssl_options = #{session_tickets := SessionTickets, - server_name_indication := SNI}, - connection_env = #connection_env{user_application = {_, User}}}) - when SessionTickets =:= manual -> - #{security_parameters := SecParams} = - ssl_record:current_connection_state(ConnectionStates, read), - HKDF = SecParams#security_parameters.prf_algorithm, - RMS = SecParams#security_parameters.resumption_master_secret, - PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), - send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK); -handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket, - #state{connection_states = ConnectionStates, - ssl_options = #{session_tickets := SessionTickets, - server_name_indication := SNI}}) - when SessionTickets =:= auto -> - #{security_parameters := SecParams} = - ssl_record:current_connection_state(ConnectionStates, read), - HKDF = SecParams#security_parameters.prf_algorithm, - RMS = SecParams#security_parameters.resumption_master_secret, - PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), - tls_client_ticket_store:store_ticket(NewSessionTicket, HKDF, SNI, PSK). - - -handle_key_update(#key_update{request_update = update_not_requested}, State0) -> - %% Update read key in connection - {ok, update_cipher_key(current_read, State0)}; -handle_key_update(#key_update{request_update = update_requested}, - #state{protocol_specific = #{sender := Sender}} = State0) -> - %% Update read key in connection - State1 = update_cipher_key(current_read, State0), - %% Send key_update and update sender's write key - case send_key_update(Sender, update_not_requested) of - ok -> - {ok, State1}; - {error, Reason} -> - {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)} - end. - - -update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) -> - CS = update_cipher_key(ConnStateName, CS0), - State0#state{connection_states = CS}; -update_cipher_key(ConnStateName, CS0) -> - #{security_parameters := SecParams0, - cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0), - HKDF = SecParams0#security_parameters.prf_algorithm, - CipherSuite = SecParams0#security_parameters.cipher_suite, - ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret, - ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0), - - %% Calculate traffic keys - #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, Cipher, ApplicationTrafficSecret), - - SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret}, - CipherState = CipherState0#cipher_state{key = Key, iv = IV}, - ConnState = ConnState0#{security_parameters => SecParams, - cipher_state => CipherState, - sequence_number => 0}, - CS0#{ConnStateName => ConnState}. - - -send_key_update(Sender, Type) -> - KeyUpdate = tls_handshake_1_3:key_update(Type), - tls_sender:send_post_handshake(Sender, KeyUpdate). - - -%% Send ticket data to user as opaque binary -send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK) -> - Timestamp = erlang:system_time(seconds), - TicketData = #{hkdf => HKDF, - sni => SNI, - psk => PSK, - timestamp => Timestamp, - ticket => NewSessionTicket}, - User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}. +choose_tls_fsm(_, _) -> + tls_1_0_to_1_2_fsm. diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index b440d65706..6c81933c23 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -20,9 +20,16 @@ %% %%---------------------------------------------------------------------- -%% Purpose: TODO +%% Purpose: TLS-1.3 FSM %%---------------------------------------------------------------------- - +%% INITIAL_HELLO +%% Client send +%% first ClientHello +%% | ---> CONFIG_ERROR +%% | Send error to user +%% | and shutdown +%% | +%% V %% RFC 8446 %% A.1. Client %% @@ -104,27 +111,163 @@ -include("ssl_alert.hrl"). -include("ssl_connection.hrl"). +-include("tls_connection.hrl"). -include("tls_handshake.hrl"). -include("tls_handshake_1_3.hrl"). -%% gen_statem helper functions --export([start/4, - negotiated/4, - wait_cert/4, - wait_cv/4, - wait_finished/4, - wait_sh/4, - wait_ee/4, - wait_cert_cr/4 +-behaviour(gen_statem). + +%% gen_statem callbacks +-export([init/1, callback_mode/0, terminate/3, code_change/4, format_status/2]). + +%% gen_statem state functions +-export([initial_hello/3, + config_error/3, + user_hello/3, + start/3, + negotiated/3, + wait_cert/3, + wait_cv/3, + wait_finished/3, + wait_sh/3, + wait_ee/3, + wait_cert_cr/3, + wait_eoed/3, + connection/3, + downgrade/3 ]). +%% Internal API +-export([setopts/3, + getopts/3, + send_key_update/2, + update_cipher_key/2]). + +%%==================================================================== +%% Internal API +%%==================================================================== + +setopts(Transport, Socket, Other) -> + tls_socket:setopts(Transport, Socket, Other). + +getopts(Transport, Socket, Tag) -> + tls_socket:getopts(Transport, Socket, Tag). + +send_key_update(Sender, Type) -> + KeyUpdate = tls_handshake_1_3:key_update(Type), + tls_sender:send_post_handshake(Sender, KeyUpdate). + +update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) -> + CS = update_cipher_key(ConnStateName, CS0), + State0#state{connection_states = CS}; +update_cipher_key(ConnStateName, CS0) -> + #{security_parameters := SecParams0, + cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0), + HKDF = SecParams0#security_parameters.prf_algorithm, + CipherSuite = SecParams0#security_parameters.cipher_suite, + ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret, + ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0), + + %% Calculate traffic keys + KeyLength = tls_v1:key_length(CipherSuite), + {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, KeyLength, ApplicationTrafficSecret), + + SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret}, + CipherState = CipherState0#cipher_state{key = Key, iv = IV}, + ConnState = ConnState0#{security_parameters => SecParams, + cipher_state => CipherState, + sequence_number => 0}, + CS0#{ConnStateName => ConnState}. + +%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- +callback_mode() -> + state_functions. -start(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -start(internal, #client_hello{} = Hello, State0, _Module) -> +init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> + State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, + Host, Port, Socket, Options, User, CbInfo), + try + State = ssl_gen_statem: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 -> + 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, undefined); +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}. + +%-------------------------------------------------------------------- +%% state callbacks +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +-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). + +%%-------------------------------------------------------------------- +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +config_error(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + + +user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} + = State) -> + gen_statem:reply(From, ok), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), + Version, ?FUNCTION_NAME, State); +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{hello = Hello}, + ssl_options = Options0} = State0) -> + Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), + State = ssl_gen_statem:ssl_config(Options, Role, State0), + Next = case Role of + client -> + wait_sh; + server -> + start + end, + {next_state, Next, State#state{start_or_recv_from = From}, + [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; +user_hello(_, _, _) -> + {keep_state_and_data, [postpone]}. + +start(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +start(internal, #client_hello{extensions = Extensions} = Hello, + #state{ssl_options = #{handshake := hello}, + start_or_recv_from = From, + handshake_env = HsEnv} = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{ + hello = Hello}}, [{reply, From, {ok, Extensions}}]}; +start(internal, #client_hello{} = Hello, State0) -> case tls_handshake_1_3:do_start(Hello, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0); {State, start} -> {next_state, start, State, []}; {State, negotiated} -> @@ -132,114 +275,299 @@ start(internal, #client_hello{} = Hello, State0, _Module) -> {State, negotiated, PSK} -> %% Session Resumption with PSK {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]} end; -start(internal, #server_hello{} = ServerHello, State0, _Module) -> +start(internal, #server_hello{extensions = Extensions} = ServerHello, + #state{ssl_options = #{handshake := hello}, + handshake_env = HsEnv, + start_or_recv_from = From} + = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{ + hello = ServerHello}}, [{reply, From, {ok, Extensions}}]}; +start(internal, #server_hello{} = ServerHello, State0) -> case tls_handshake_1_3:do_start(ServerHello, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0); {State, NextState} -> {next_state, NextState, State, []} end; -start(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - +start(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +start(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -negotiated(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -negotiated(internal, Message, State0, _Module) -> +negotiated(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +negotiated(internal, Message, State0) -> case tls_handshake_1_3:do_negotiated(Message, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, negotiated, State0); {State, NextState} -> {next_state, NextState, State, []} - end. - + end; +negotiated(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State). -wait_cert(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cert(internal, #change_cipher_spec{}, State0) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0); wait_cert(internal, - #certificate_1_3{} = Certificate, State0, _Module) -> + #certificate_1_3{} = Certificate, State0) -> case tls_handshake_1_3:do_wait_cert(Certificate, State0) of {#alert{} = Alert, State} -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert, State); {State, NextState} -> - tls_connection:next_event(NextState, no_record, State) + tls_gen_connection:next_event(NextState, no_record, State) end; -wait_cert(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +wait_cert(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_cert(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). - -wait_cv(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cv(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); wait_cv(internal, - #certificate_verify_1_3{} = CertificateVerify, State0, _Module) -> + #certificate_verify_1_3{} = CertificateVerify, State0) -> case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of {#alert{} = Alert, State} -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cv, State); {State, NextState} -> - tls_connection:next_event(NextState, no_record, State) + tls_gen_connection:next_event(NextState, no_record, State) end; -wait_cv(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - +wait_cv(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_cv(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_finished(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_finished(internal, #change_cipher_spec{}, State0) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0); wait_finished(internal, - #finished{} = Finished, State0, Module) -> + #finished{} = Finished, State0) -> case tls_handshake_1_3:do_wait_finished(Finished, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, finished, State0); State1 -> - {Record, State} = ssl_connection:prepare_connection(State1, Module), - tls_connection:next_event(connection, Record, State, + {Record, State} = ssl_gen_statem:prepare_connection(State1, tls_gen_connection), + tls_gen_connection:next_event(connection, Record, State, [{{timeout, handshake}, cancel}]) end; -wait_finished(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +wait_finished(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_finished(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_sh(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_sh(internal, #server_hello{} = Hello, State0, _Module) -> +wait_sh(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_sh(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello}, + start_or_recv_from = From, + handshake_env = HsEnv} = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{ + hello = Hello}}, [{reply, From, {ok, Extensions}}]}; +wait_sh(internal, #server_hello{} = Hello, State0) -> case tls_handshake_1_3:do_wait_sh(Hello, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_sh, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_sh, State0); {State1, start, ServerHello} -> %% hello_retry_request: go to start {next_state, start, State1, [{next_event, internal, ServerHello}]}; {State1, wait_ee} -> - tls_connection:next_event(wait_ee, no_record, State1) + tls_gen_connection:next_event(wait_ee, no_record, State1) end; -wait_sh(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +wait_sh(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_sh(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_ee(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_ee(internal, #encrypted_extensions{} = EE, State0, _Module) -> +wait_ee(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_ee(internal, #encrypted_extensions{} = EE, State0) -> case tls_handshake_1_3:do_wait_ee(EE, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_ee, State0); {State1, NextState} -> - tls_connection:next_event(NextState, no_record, State1) + tls_gen_connection:next_event(NextState, no_record, State1) end; -wait_ee(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +wait_ee(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_ee(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_cert_cr(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0, _Module) -> +wait_cert_cr(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) -> case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of {#alert{} = Alert, State} -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State); {State1, NextState} -> - tls_connection:next_event(NextState, no_record, State1) + tls_gen_connection:next_event(NextState, no_record, State1) end; -wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0, _Module) -> +wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0) -> case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0); {State1, NextState} -> - tls_connection:next_event(NextState, no_record, State1) + tls_gen_connection:next_event(NextState, no_record, State1) end; -wait_cert_cr(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +wait_cert_cr(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_cert_cr(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +wait_eoed(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_eoed(internal, #end_of_early_data{} = EOED, State0) -> + case tls_handshake_1_3:do_wait_eoed(EOED, State0) of + {#alert{} = Alert, State} -> + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_eoed, State); + {State1, NextState} -> + tls_gen_connection:next_event(NextState, no_record, State1) + end; +wait_eoed(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_eoed(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +connection(internal, #new_session_ticket{} = NewSessionTicket, State) -> + handle_new_session_ticket(NewSessionTicket, State), + tls_gen_connection:next_event(?FUNCTION_NAME, 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); + {error, State, Alert} -> + ssl_gen_statem:handle_own_alert(Alert, {3,4}, connection, State), + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State) + end; +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined}} = State) -> + ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = SelectedProtocol, + negotiated_protocol = undefined}} = State) -> + ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + +downgrade(Type, Event, State) -> + tls_connection:?FUNCTION_NAME(Type, Event, State). + +%-------------------------------------------------------------------- +%% internal functions +%%-------------------------------------------------------------------- +initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User, + {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + #{erl_dist := IsErlDist, + client_renegotiation := ClientRenegotiation} = SSLOptions, + MaxEarlyDataSize = init_max_early_data_size(Role), + ConnectionStates = tls_record:init_connection_states(Role, disabled, MaxEarlyDataSize), + InternalActiveN = case application:get_env(ssl, internal_active_n) of + {ok, N} when is_integer(N) andalso (not IsErlDist) -> + N; + _ -> + ?INTERNAL_ACTIVE_N + end, + UserMonitor = erlang:monitor(process, User), + 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, + trackers = Trackers + }, + #state{ + static_env = InitStatEnv, + handshake_env = #handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + renegotiation = {false, first}, + allow_renegotiate = ClientRenegotiation + }, + connection_env = #connection_env{user_application = {UserMonitor, User}}, + socket_options = SocketOptions, + ssl_options = SSLOptions, + session = #session{is_resumable = false, + session_id = ssl_session:legacy_session_id()}, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_data_buffer = {[],0,[]}, + start_or_recv_from = undefined, + flight_buffer = [], + protocol_specific = #{sender => Sender, + active_n => InternalActiveN, + active_n_toggle => true + } + }. + +handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) -> + ok; +handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket, + #state{connection_states = ConnectionStates, + ssl_options = #{session_tickets := SessionTickets, + server_name_indication := SNI}, + connection_env = #connection_env{user_application = {_, User}}}) + when SessionTickets =:= manual -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates, read), + CipherSuite = SecParams#security_parameters.cipher_suite, + #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + HKDF = SecParams#security_parameters.prf_algorithm, + RMS = SecParams#security_parameters.resumption_master_secret, + PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), + send_ticket_data(User, NewSessionTicket, {Cipher, HKDF}, SNI, PSK); +handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket, + #state{connection_states = ConnectionStates, + ssl_options = #{session_tickets := SessionTickets, + server_name_indication := SNI}}) + when SessionTickets =:= auto -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates, read), + CipherSuite = SecParams#security_parameters.cipher_suite, + #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + HKDF = SecParams#security_parameters.prf_algorithm, + RMS = SecParams#security_parameters.resumption_master_secret, + PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), + tls_client_ticket_store:store_ticket(NewSessionTicket, {Cipher, HKDF}, SNI, PSK). + +send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) -> + Timestamp = erlang:system_time(seconds), + TicketData = #{cipher_suite => CipherSuite, + sni => SNI, + psk => PSK, + timestamp => Timestamp, + ticket => NewSessionTicket}, + User ! {ssl, session_ticket, TicketData}. + +handle_key_update(#key_update{request_update = update_not_requested}, State0) -> + %% Update read key in connection + {ok, update_cipher_key(current_read, State0)}; +handle_key_update(#key_update{request_update = update_requested}, + #state{protocol_specific = #{sender := Sender}} = State0) -> + %% Update read key in connection + State1 = update_cipher_key(current_read, State0), + %% Send key_update and update sender's write key + case send_key_update(Sender, update_not_requested) of + ok -> + {ok, State1}; + {error, Reason} -> + {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)} + end. + +init_max_early_data_size(client) -> + %% Disable trial decryption on the client side + %% Servers do trial decryption of max_early_data bytes of plain text. + %% Setting it to 0 means that a decryption error will result in an Alert. + 0; +init_max_early_data_size(server) -> + ssl_config:get_max_early_data_size(). + diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl index d5b228dc94..b7f80ad524 100644 --- a/lib/ssl/src/tls_connection_sup.erl +++ b/lib/ssl/src/tls_connection_sup.erl @@ -40,13 +40,13 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). start_link_dist() -> - supervisor:start_link({local, ssl_connection_sup_dist}, ?MODULE, []). + supervisor:start_link({local, tls_dist_connection_sup}, ?MODULE, []). start_child(Args) -> supervisor:start_child(?MODULE, Args). start_child_dist(Args) -> - supervisor:start_child(ssl_connection_sup_dist, Args). + supervisor:start_child(tls_dist_connection_sup, Args). %%%========================================================================= %%% Supervisor callback @@ -57,10 +57,10 @@ init(_O) -> MaxT = 3600, Name = undefined, % As simple_one_for_one is used. - StartFunc = {tls_connection, start_link, []}, + StartFunc = {ssl_gen_statem, start_link, []}, Restart = temporary, % E.g. should not be restarted Shutdown = 4000, - Modules = [tls_connection, ssl_connection], + Modules = [ssl_gen_statem, tls_connection, tls_connection_1_3], Type = worker, ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, diff --git a/lib/ssl/src/tls_dist_server_sup.erl b/lib/ssl/src/tls_dist_server_sup.erl new file mode 100644 index 0000000000..96603a7495 --- /dev/null +++ b/lib/ssl/src/tls_dist_server_sup.erl @@ -0,0 +1,89 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2021-2021. 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(tls_dist_server_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + ListenTracker = listen_options_tracker_child_spec(), + SessionTracker = tls_server_session_child_spec(), + Pre_1_3SessionTracker = ssl_server_session_child_spec(), + + {ok, {{one_for_all, 10, 3600}, [ListenTracker, + SessionTracker, + Pre_1_3SessionTracker + ]}}. + + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +%% Handles emulated options so that they inherited by the accept +%% socket, even when setopts is performed on the listen socket +listen_options_tracker_child_spec() -> + Name = dist_tls_socket, + StartFunc = {ssl_listen_tracker_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_listen_tracker_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +tls_server_session_child_spec() -> + Name = dist_tls_server_session_ticket, + StartFunc = {tls_server_session_ticket_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_server_session_ticket_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +ssl_server_session_child_spec() -> + Name = dist_ssl_server_session_cache_sup, + StartFunc = {ssl_upgrade_server_session_cache_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_server_session_cache_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + diff --git a/lib/ssl/src/tls_dist_sup.erl b/lib/ssl/src/tls_dist_sup.erl new file mode 100644 index 0000000000..54e0a6a514 --- /dev/null +++ b/lib/ssl/src/tls_dist_sup.erl @@ -0,0 +1,75 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2021-2021. 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(tls_dist_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + + TLSConnetionSup = tls_connection_child_spec(), + ServerInstanceSup = server_instance_child_spec(), + + {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup, + ServerInstanceSup + ]}}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +tls_connection_child_spec() -> + Name = dist_tls_connection, + StartFunc = {tls_connection_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_connection_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +server_instance_child_spec() -> + Name = dist_tls_server_sup, + StartFunc = {tls_dist_server_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_dist_server_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl new file mode 100644 index 0000000000..c27feadfcf --- /dev/null +++ b/lib/ssl/src/tls_dtls_connection.erl @@ -0,0 +1,1687 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also +%% tls_connection.erl and dtls_connection.erl +%%---------------------------------------------------------------------- + +-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 wrapps +%% handling of common state handling for handshake messages for error handling +-export([hello/3, + user_hello/3, + abbreviated/3, + certify/3, + wait_ocsp_stapling/3, + cipher/3, + connection/3, + downgrade/3, + gen_handshake/4]). + +%%-------------------------------------------------------------------- +-spec internal_renegotiation(pid(), ssl_record:connection_states()) -> + ok. +%% +%% Description: Starts 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{session_id = OldId}, + 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(OldId, 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{connection_env = #connection_env{negotiated_version = Version}} = State) -> + gen_statem:reply(From, ok), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), + Version, ?FUNCTION_NAME, State); +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{hello = Hello}, + ssl_options = Options0} = State0) -> + Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), + State = ssl_gen_statem:ssl_config(Options, Role, State0), + {next_state, hello, State#state{start_or_recv_from = From}, + [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; +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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = client, + 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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation +abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{static_env = #static_env{role = server, + 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 + Connection:next_event(?FUNCTION_NAME, no_record, 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) -> + Connection:next_event(certify, no_record, + State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state = + OcspState#{ocsp_expect => stapled, + ocsp_response => CertStatus}}}); +%% Server did not send OCSP staple message +wait_ocsp_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = #handshake_env{ + ocsp_stapling_state = OcspState} = HsEnv} = State) + when is_record(Msg, server_key_exchange) orelse + is_record(Msg, hello_request) orelse + is_record(Msg, certificate_request) orelse + is_record(Msg, server_hello_done) orelse + is_record(Msg, client_key_exchange) -> + Connection:next_event(certify, no_record, + State#state{handshake_env = + HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}}, + [{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}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{verify := verify_peer, + fail_if_no_peer_cert := true}} = + State) -> + Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided), + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +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_requested = false}); +certify(internal, #certificate{}, + #state{static_env = #static_env{role = server}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{verify := verify_none}} = + State) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +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, + ensure_tls(Version), OcspInfo) of + {PeerCert, PublicKeyInfo} -> + handle_peer_cert(Role, PeerCert, PublicKeyInfo, + State#state{client_certificate_requested = false}, Connection, []); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + end; +certify(internal, #server_key_exchange{exchange_keys = Keys}, + #state{static_env = #static_env{role = client, + 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 otherwhise 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 -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), + Version, ?FUNCTION_NAME, State) + end + end; +certify(internal, #certificate_request{}, + #state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = KexAlg}, + connection_env = #connection_env{negotiated_version = Version}} = State) + 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 -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), + Version, ?FUNCTION_NAME, State); +certify(internal, #certificate_request{}, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + session = #session{own_certificates = undefined}} = State) -> + %% The client does not have a certificate and will send an empty reply, the server may fail + %% or accept the connection by its own preference. No signature algorihms needed as there is + %% no certificate to verify. + Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); +certify(internal, #certificate_request{} = CertRequest, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + handshake_env = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{own_certificates = [Cert|_]}, + ssl_options = #{signature_algs := SupportedHashSigns}} = State) -> + case ssl_handshake:select_hashsign(CertRequest, Cert, + SupportedHashSigns, ssl:tls_version(Version)) of + #alert {} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); + NegotiatedHashSign -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{client_certificate_requested = true, + handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}}) + end; +%% 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}, + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + premaster_secret = undefined, + server_psk_identity = PSKIdentity} = HsEnv, + ssl_options = #{user_lookup_fun := PSKLookup}} = State0) + when KexAlg == psk -> + case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); + 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}} = Version, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + premaster_secret = undefined, + server_psk_identity = PSKIdentity} = HsEnv, + session = #session{master_secret = undefined}, + ssl_options = #{user_lookup_fun := PSKLookup}} = State0) + when KexAlg == rsa_psk -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, + case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup, + RSAPremasterSecret) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); + 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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +%% Master secret is calculated from premaster_secret +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client, + 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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +certify(internal = Type, #client_key_exchange{} = Msg, + #state{static_env = #static_env{role = server}, + client_certificate_requested = true, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{fail_if_no_peer_cert := true}} = State) -> + %% We expect a certificate here + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}), + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +certify(internal, #client_key_exchange{exchange_keys = Keys}, + State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg}, + 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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + 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 cipher(gen_statem:event_type(), + #hello_request{} | #certificate_verify{} | #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, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + kex_algorithm = KexAlg, + public_key_info = PubKeyInfo} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} + } = State) -> + + TLSVersion = ssl:tls_version(Version), + %% Use negotiated value if TLS-1.2 otherwhise 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(?FUNCTION_NAME, no_record, + State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}}); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + end; +%% client must send a next protocol message if we are expecting it +cipher(internal, #finished{}, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{expecting_next_protocol_negotiation = true, + negotiated_protocol = undefined}, + connection_env = #connection_env{negotiated_version = Version}} = State0) -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); +cipher(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = Role, + host = Host, + 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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + end; +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation +cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{static_env = #static_env{role = server, 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(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{connection_env = #connection_env{negotiated_version = Version}} = State) -> + try tls_dtls_connection:StateName(Type, Event, State) of + Result -> + Result + catch + _:_ -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + 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:_ -> {error, badarg}; + error:Reason -> {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(<<Random0:24/binary,_:8/binary>> = 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 -> %% Negotating TLS 1.2 + Down = ?RANDOM_OVERRIDE_TLS12, + <<Random0/binary,Down/binary>>; + M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <<Random0/binary,Down/binary>>; + true -> + Random + end; +override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) + when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 + if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <<Random0/binary,Down/binary>>; + 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}, + connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> + try server_certify_and_key_exchange(State0, Connection) of + #state{} = State1 -> + {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) + catch + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0) + end. + +resumed_server_hello(#state{session = Session, + connection_states = ConnectionStates0, + static_env = #static_env{protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> + + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, + 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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0) + 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_requested = true, + 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_requested = false} = State, _) -> + State. + +verify_client_cert(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + cert_hashsign_algorithm = HashSign}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + client_certificate_requested = true, + session = #session{master_secret = MasterSecret, + 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_requested = false} = State, _) -> + State. + +client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} = + State0, Connection) -> + try do_client_certify_and_key_exchange(State0, Connection) of + State1 = #state{} -> + {State2, Actions} = finalize_handshake(State1, certify, Connection), + State = State2#state{ + %% Reinitialize + client_certificate_requested = false}, + Connection:next_event(cipher, no_record, State, Actions) + catch + throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0) + end. + +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{connection_env = #connection_env{private_key = Key}, + handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}} + = State, Connection) -> + FakeSecret = make_premaster_secret(Version, rsa), + %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret + %% and fail handshake later.RFC 5246 section 7.4.7.1. + PremasterSecret = + try ssl_handshake:premaster_secret(EncPMS, Key) of + Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> + case Secret of + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>; + <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> + end; + _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES + FakeSecret + catch + #alert{description = ?DECRYPT_ERROR} -> + FakeSecret + end, + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); +certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, + #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, + kex_keys = {_, ServerDhPrivateKey}} + } = State, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + +certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, + #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); +certify_client_key_exchange(#client_psk_identity{} = ClientKey, + #state{ssl_options = + #{user_lookup_fun := PSKLookup}} = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); +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}} = State0, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); +certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, + #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, + ssl_options = + #{user_lookup_fun := PSKLookup}} = State, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); +certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, + #state{connection_env = #connection_env{private_key = Key}, + ssl_options = + #{user_lookup_fun := PSKLookup}} = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); +certify_client_key_exchange(#client_srp_public{} = ClientKey, + #state{handshake_env = #handshake_env{srp_params = Params, + kex_keys = Key} + } = State0, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), + calculate_master_secret(PremasterSecret, State0, Connection, certify, 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) -> + case ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, server) of + Cert = #certificate{} -> + Connection:queue_handshake(Cert, State); + Alert = #alert{} -> + throw(Alert) + end. + +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, + 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, + connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key}, + session = 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, + private_key = PrivateKey}, + session = #session{ecc = ECCCurve}, + 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, + 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, + 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, + private_key = PrivateKey}, + session = #session{ecc = ECCCurve}, + 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, + 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, + private_key = PrivateKey}, + session = #session{srp_username = Username}, + connection_states = ConnectionStates0 + } = State0, Connection) + when KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + SrpParams = handle_srp_identity(Username, LookupFun), + Keys = case generate_srp_server_keys(SrpParams, 0) of + Alert = #alert{} -> + throw(Alert); + Keys0 = {_,_} -> + Keys0 + end, + #{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, + signature_algs := SupportedHashSigns}, + connection_states = ConnectionStates0} = State0, Connection) -> + #{security_parameters := + #security_parameters{cipher_suite = CipherSuite}} = + ssl_record:pending_connection_state(ConnectionStates0, read), + TLSVersion = ssl:tls_version(Version), + HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, + TLSVersion), + Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, + HashSigns, TLSVersion), + State = Connection:queue_handshake(Msg, State0), + State#state{client_certificate_requested = true}; + +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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0) + 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, _) -> + 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 -> + Alert + end. + +generate_srp_server_keys(_SrpParams, 10) -> + ?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']}) of + Keys -> + Keys + catch + error:_ -> + generate_srp_server_keys(SrpParams, N+1) + end. + +generate_srp_client_keys(_Generator, _Prime, 10) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); +generate_srp_client_keys(Generator, Prime, N) -> + + try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of + Keys -> + Keys + catch + error:_ -> + 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 -> + ssl_gen_statem:handle_own_alert(Alert, Version, hello, State) + end. + +make_premaster_secret({MajVer, MinVer}, rsa) -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; +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 + end. + +ensure_tls({254, _} = Version) -> + dtls_v1:corresponding_tls_version(Version); +ensure_tls(Version) -> + Version. + +ocsp_info(#{ocsp_expect := stapled, + ocsp_response := CertStatus} = OcspState, + #{ocsp_responder_certs := OcspResponderCerts}, PeerCert) -> + #{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 + }. diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl new file mode 100644 index 0000000000..5da87e79d6 --- /dev/null +++ b/lib/ssl/src/tls_gen_connection.erl @@ -0,0 +1,778 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: +%%---------------------------------------------------------------------- + +-module(tls_gen_connection). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-include("tls_connection.hrl"). +-include("tls_handshake.hrl"). +-include("tls_record.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). + +%% Setup +-export([start_fsm/8, + pids/1, + initialize_tls_sender/1]). + +%% Handshake handling +-export([renegotiation/2, + renegotiate/2, + send_handshake/2, + send_handshake_flight/1, + queue_handshake/2, + queue_change_cipher/2, + reinit/1, + reinit_handshake_data/1, + select_sni_extension/1, + empty_connection_state/2, + encode_handshake/4]). + +%% State transition handling +-export([next_event/3, + next_event/4, + handle_protocol_record/3]). + +%% Data handling +-export([socket/4, + setopts/3, + getopts/3, + handle_info/3]). + +%% Alert and close handling +-export([send_alert/2, + send_alert_in_connection/2, + send_sync_alert/2, + close/5, + protocol_name/0]). + +-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). + +%%==================================================================== +%% Internal application API +%%==================================================================== +%%==================================================================== +%% Setup +%%==================================================================== +start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts, + User, {CbModule, _,_, _, _} = CbInfo, + Timeout) -> + try + {ok, Sender} = tls_sender:start(), + {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), + ssl_gen_statem:handshake(SslSocket, Timeout) + catch + error:{badmatch, {error, _} = Error} -> + Error + end; + +start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts, + User, {CbModule, _,_, _, _} = CbInfo, + Timeout) -> + try + {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]), + {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), + ssl_gen_statem:handshake(SslSocket, Timeout) + catch + error:{badmatch, {error, _} = Error} -> + Error + end. + +pids(#state{protocol_specific = #{sender := Sender}}) -> + [self(), Sender]. + +initialize_tls_sender(#state{static_env = #static_env{ + role = Role, + transport_cb = Transport, + socket = Socket, + trackers = Trackers + }, + connection_env = #connection_env{negotiated_version = Version}, + socket_options = SockOpts, + ssl_options = #{renegotiate_at := RenegotiateAt, + key_update_at := KeyUpdateAt, + log_level := LogLevel}, + connection_states = #{current_write := ConnectionWriteState}, + protocol_specific = #{sender := Sender}}) -> + Init = #{current_write => ConnectionWriteState, + role => Role, + socket => Socket, + socket_options => SockOpts, + trackers => Trackers, + transport_cb => Transport, + negotiated_version => Version, + renegotiate_at => RenegotiateAt, + key_update_at => KeyUpdateAt, + log_level => LogLevel}, + tls_sender:initialize(Sender, Init). + +%%==================================================================== +%% 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, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = Flight0, + ssl_options = #{log_level := LogLevel}, + connection_states = ConnectionStates0} = State0) -> + {BinHandshake, ConnectionStates, Hist} = + encode_handshake(Handshake, Version, ConnectionStates0, Hist0), + ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake), + ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake), + + State0#state{connection_states = ConnectionStates, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}, + flight_buffer = Flight0 ++ [BinHandshake]}. + +-spec send_handshake_flight(StateIn) -> {StateOut, FlightBuffer} when + StateIn :: #state{}, + StateOut :: #state{}, + FlightBuffer :: list(). +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + flight_buffer = Flight} = State0) -> + tls_socket:send(Transport, Socket, Flight), + {State0#state{flight_buffer = []}, []}. + + +queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = Flight0, + ssl_options = #{log_level := LogLevel}, + connection_states = ConnectionStates0} = State0) -> + {BinChangeCipher, ConnectionStates} = + encode_change_cipher(Msg, Version, ConnectionStates0), + ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher), + State0#state{connection_states = ConnectionStates, + flight_buffer = Flight0 ++ [BinChangeCipher]}. + +reinit(#state{protocol_specific = #{sender := Sender}, + connection_env = #connection_env{negotiated_version = Version}, + connection_states = #{current_write := Write}} = State) -> + tls_sender:update_connection_state(Sender, Write, Version), + reinit_handshake_data(State). + +reinit_handshake_data(#state{handshake_env = HsEnv} =State) -> + %% premaster_secret, public_key_info and tls_handshake_info + %% are only needed during the handshake phase. + %% To reduce memory foot print of a connection reinitialize them. + State#state{ + handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), + public_key_info = undefined, + premaster_secret = undefined} + }. + +select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> + SNI; +select_sni_extension(_) -> + undefined. + +empty_connection_state(ConnectionEnd, BeastMitigation) -> + ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation). + +%%==================================================================== +%% Data handling +%%==================================================================== + +socket(Pids, Transport, Socket, Trackers) -> + tls_socket:socket(Pids, Transport, Socket, tls_connection, Trackers). + +setopts(Transport, Socket, Other) -> + tls_socket:setopts(Transport, Socket, Other). + +getopts(Transport, Socket, Tag) -> + tls_socket:getopts(Transport, Socket, Tag). + +%% raw data from socket, upack records +handle_info({Protocol, _, Data}, StateName, + #state{static_env = #static_env{data_tag = Protocol}, + connection_env = #connection_env{negotiated_version = Version}} = State0) -> + case next_tls_record(Data, StateName, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0) + end; +handle_info({PassiveTag, Socket}, StateName, + #state{static_env = #static_env{socket = Socket, + passive_tag = PassiveTag}, + start_or_recv_from = From, + protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, + protocol_specific = PS + } = State0) -> + case (From =/= undefined) andalso (CTs == []) of + 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, + State0#state{protocol_specific = PS#{active_n_toggle => true}}) + end; +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{ + role = Role, + host = Host, + port = Port, + socket = Socket, + close_tag = CloseTag}, + handshake_env = #handshake_env{renegotiation = Type}, + session = Session} = State) when StateName =/= connection -> + ssl_gen_statem:maybe_invalidate_session(Type, Role, Host, Port, Session), + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown, transport_closed}, State}; +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{ + role = Role, + socket = Socket, + close_tag = CloseTag}, + start_or_recv_from = From, + socket_options = #socket_options{active = Active}, + protocol_specific = PS} = State) -> + + %% Note that as of TLS 1.1, + %% failure to properly close a connection no longer requires that a + %% session not be resumed. This is a change from TLS 1.0 to conform + %% with widespread implementation practice. + + case (Active == false) andalso (From == undefined) of + false -> + %% As invalidate_sessions here causes performance issues, + %% we will conform to the widespread implementation + %% practice and go aginst the spec + %% case Version of + %% {3, N} when N >= 1 -> + %% ok; + %% _ -> + %% invalidate_session(Role, Host, Port, Session) + %% ok + %% end, + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown, transport_closed}, State}; + true -> + %% Wait for next socket operation (most probably + %% ssl:setopts(S, [{active, true | once | N}]) or + %% ssl:recv(S, N, Timeout) before closing. Possible + %% buffered data will be deliverd by the code handling + %% these options before closing. In the case of the + %% peer resetting the connection hard, that is + %% we do not receive any close ALERT, and an active once (or possible N) + %% strategy is used by the client we want to later trigger a new + %% "transport closed" message. This is achieved by setting the internal + %% 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}}, []} + end; +handle_info({'EXIT', Sender, Reason}, _, + #state{protocol_specific = #{sender := Sender}} = State) -> + {stop, {shutdown, {sender_died, Reason}}, State}; +handle_info(Msg, StateName, State) -> + ssl_gen_statem:handle_info(Msg, StateName, State). + +%%==================================================================== +%% State transition handling +%%==================================================================== +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). + +next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) -> + case next_record(StateName, State0) of + {no_record, State} -> + ssl_gen_statem:hibernate_after(StateName, State, Actions); + {Record, State} -> + next_event(StateName, Record, State, Actions); + #alert{} = Alert -> + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), + {stop, {shutdown, own_alert}, State0} + end; +next_event(StateName, #ssl_tls{} = Record, State, Actions) -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; +next_event(StateName, #alert{} = Alert, State, Actions) -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}. + +%%% TLS record protocol level application data messages +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, + #state{start_or_recv_from = From, + socket_options = #socket_options{active = false}} = State0) when From =/= undefined -> + case ssl_gen_statem:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, #state{start_or_recv_from = Caller} = State} -> + TimerAction = case Caller of + undefined -> %% Passive recv complete cancel timer + [{{timeout, recv}, infinity, timeout}]; + _ -> + [] + end, + next_event(StateName, Record, State, TimerAction) + end; +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) -> + case ssl_gen_statem:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State} -> + next_event(StateName, Record, State) + end; +%%% TLS record protocol level handshake messages +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, + StateName, #state{protocol_buffers = + #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, + connection_env = #connection_env{negotiated_version = Version}, + static_env = #static_env{role = Role}, + ssl_options = Options} = State0) -> + try + %% Calculate the effective version that should be used when decoding an incoming handshake + %% message. + EffectiveVersion = effective_version(Version, Options, Role), + {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options), + State = + State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, + case Packets of + [] -> + assert_buffer_sanity(Buf, Options), + next_event(StateName, no_record, State); + _ -> + Events = tls_handshake_events(Packets), + case StateName of + connection -> + ssl_gen_statem:hibernate_after(StateName, State, Events); + _ -> + HsEnv = State#state.handshake_env, + {next_state, StateName, + State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events + = unprocessed_events(Events)}}, Events} + end + end + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0) + end; +%%% TLS record protocol level change cipher messages +handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% TLS record protocol level Alert messages +handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + try decode_alerts(EncAlerts) of + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + [] -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert), + Version, StateName, State); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State) + catch + _:_ -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), + Version, StateName, State) + + end; +%% Ignore unknown TLS record level protocol messages +handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. + +%%==================================================================== +%% Alert and close handling +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +%% +%% Description: Encodes an alert +%%-------------------------------------------------------------------- +encode_alert(#alert{} = Alert, Version, ConnectionStates) -> + tls_record:encode_alert_record(Alert, Version, ConnectionStates). + +send_alert(Alert, #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{log_level := LogLevel}, + connection_states = ConnectionStates0} = StateData0) -> + {BinMsg, ConnectionStates} = + encode_alert(Alert, Version, ConnectionStates0), + tls_socket:send(Transport, Socket, BinMsg), + ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), + StateData0#state{connection_states = ConnectionStates}. + +%% If an ALERT sent in the connection state, should cause the TLS +%% connection to end, we need to synchronize with the tls_sender +%% process so that the ALERT if possible (that is the tls_sender process is +%% not blocked) is sent before the connection process terminates and +%% thereby closes the transport socket. +send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) -> + send_sync_alert(Alert, State); +send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) -> + send_sync_alert(Alert, State); +send_alert_in_connection(Alert, + #state{protocol_specific = #{sender := Sender}}) -> + tls_sender:send_alert(Sender, Alert). +send_sync_alert( + Alert, #state{protocol_specific = #{sender := Sender}} = State) -> + try tls_sender:send_and_ack_alert(Sender, Alert) + catch + _:_ -> + throw({stop, {shutdown, own_alert}, State}) + end. + +%% User closes or recursive call! +close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> + tls_socket:setopts(Transport, Socket, [{active, false}]), + Transport:shutdown(Socket, write), + _ = Transport:recv(Socket, 0, Timeout), + ok; +%% Peer closed socket +close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + close({close, 0}, Socket, Transport, ConnectionStates, Check); +%% We generate fatal alert +close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + %% Standard trick to try to make sure all + %% data sent to the tcp port is really delivered to the + %% peer application before tcp port is closed so that the peer will + %% get the correct TLS alert message and not only a transport close. + %% Will return when other side has closed or after timout millisec + %% e.g. we do not want to hang if something goes wrong + %% with the network but we want to maximise the odds that + %% peer application gets all data sent on the tcp connection. + close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +close(downgrade, _,_,_,_) -> + ok; +%% Other +close(_, Socket, Transport, _,_) -> + tls_socket:close(Transport, Socket). +protocol_name() -> + "TLS". + + +%%==================================================================== +%% Internal functions +%%==================================================================== +tls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). + +unprocessed_events(Events) -> + %% The first handshake event will be processed immediately + %% as it is entered first in the event queue and + %% when it is processed there will be length(Events)-1 + %% handshake events left to process before we should + %% process more TLS-records received on the socket. + erlang:length(Events)-1. + +encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> + Frag = tls_handshake:encode_handshake(Handshake, Version), + Hist = ssl_handshake:update_handshake_history(Hist0, Frag), + {Encoded, ConnectionStates} = + tls_record:encode_handshake(Frag, Version, ConnectionStates0), + {Encoded, ConnectionStates, Hist}. + +encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> + tls_record:encode_change_cipher_spec(Version, ConnectionStates). + +next_tls_record(Data, StateName, + #state{protocol_buffers = + #protocol_buffers{tls_record_buffer = Buf0, + tls_cipher_texts = CT0} = Buffers, + ssl_options = SslOpts} = State0) -> + Versions = + %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all + %% record version are accepted when receiving initial ClientHello and + %% ServerHello. This can happen in state 'hello' in case of all TLS + %% versions and also in state 'start' when TLS 1.3 is negotiated. + %% After the version is negotiated all subsequent TLS records shall have + %% the proper legacy_record_version (= negotiated_version). + %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this + %% point it is the same as the negotiated protocol version. + %% TODO: Refactor state machine and introduce a record_protocol_version beside + %% the negotiated_version. + case StateName of + State when State =:= hello orelse + State =:= start -> + [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]; + _ -> + State0#state.connection_env#connection_env.negotiated_version + end, + #{current_write := #{max_fragment_length := MaxFragLen}} = State0#state.connection_states, + case tls_record:get_tls_records(Data, Versions, Buf0, MaxFragLen, SslOpts) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(StateName, State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_record_buffer = Buf1, + tls_cipher_texts = CT1}}); + #alert{} = Alert -> + handle_record_alert(Alert, State0) + end. + +next_record(_, #state{handshake_env = + #handshake_env{unprocessed_handshake_events = N} = HsEnv} + = State) when N > 0 -> + {no_record, State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; +next_record(_, #state{protocol_buffers = + #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts}, + connection_states = ConnectionStates, + ssl_options = #{padding_check := Check}} = State) -> + next_record(State, CipherTexts, ConnectionStates, Check); +next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, + protocol_specific = #{active_n_toggle := true} + } = State) -> + %% If ssl application user is not reading data wait to activate socket + flow_ctrl(State); + +next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, + protocol_specific = #{active_n_toggle := true} + } = State) -> + activate_socket(State); +next_record(_, State) -> + {no_record, State}. + +%%% bytes_to_read equals the integer Length arg of ssl:recv +%%% the actual value is only relevant for packet = raw | 0 +%%% bytes_to_read = undefined means no recv call is ongoing +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = undefined} = State) when Size =/= 0 -> + %% Passive mode wait for new recv request or socket activation + %% that is preserv some tcp back pressure by waiting to activate + %% socket + {no_record, State}; +%%%%%%%%%% A packet mode is set and socket is passive %%%%%%%%%% +flow_ctrl(#state{socket_options = #socket_options{active = false, + packet = Packet}} = State) + when ((Packet =/= 0) andalso (Packet =/= raw)) -> + %% We need more data to complete the packet. + activate_socket(State); +%%%%%%%%% No packet mode set and socket is passive %%%%%%%%%%%% +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = 0} = State) when Size == 0 -> + %% Passive mode no available bytes, get some + activate_socket(State); +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = 0} = State) when Size =/= 0 -> + %% There is data in the buffer to deliver + {no_record, State}; +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = BytesToRead} = State) when (BytesToRead > 0) -> + case (Size >= BytesToRead) of + true -> %% There is enough data bufferd + {no_record, State}; + false -> %% We need more data to complete the delivery of <BytesToRead> size + activate_socket(State) + end; +%%%%%%%%%%% Active mode or more data needed %%%%%%%%%% +flow_ctrl(State) -> + activate_socket(State). + + +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) -> + case tls_socket:setopts(Transport, Socket, [{active, N}]) of + ok -> + {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, Socket}, + {no_record, State} + end. + +%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one +%% +next_record(State, CipherTexts, ConnectionStates, Check) -> + next_record(State, CipherTexts, ConnectionStates, Check, []). +%% +next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State, + [CT|CipherTexts], ConnectionStates0, Check, Acc) -> + case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of + {#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 + next_record_done(State, [], ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + [_|_] -> + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) + end; + {trial_decryption_failed, ConnectionStates} -> + case CipherTexts of + [] -> + %% End of cipher texts - build and deliver an ?APPLICATION_DATA record + %% from the accumulated fragments + next_record_done(State, [], ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc))}); + [_|_] -> + next_record(State, CipherTexts, ConnectionStates, Check, Acc) + end; + {Record, ConnectionStates} when Acc =:= [] -> + %% Singelton non-?APPLICATION_DATA record - deliver + next_record_done(State, CipherTexts, ConnectionStates, Record); + {_Record, _ConnectionStates_to_forget} -> + %% Not ?APPLICATION_DATA but we have accumulated fragments + %% -> build an ?APPLICATION_DATA record with concatenated fragments + %% and forget about decrypting this record - we'll decrypt it again next time + %% Will not work for stream ciphers + next_record_done(State, [CT|CipherTexts], ConnectionStates0, + #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))}); + #alert{} = Alert -> + Alert + end; +next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, + [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) -> + case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of + {#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 + next_record_done(State, [], ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + [_|_] -> + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) + end; + #alert{} = Alert -> + Alert + end; +next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) -> + next_record_done(State, CipherTexts, ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc))}); +next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, + [CT|CipherTexts], ConnectionStates0, Check, []) -> + case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of + {Record, ConnectionStates} -> + %% Singelton non-?APPLICATION_DATA record - deliver + next_record_done(State, CipherTexts, ConnectionStates, Record); + #alert{} = Alert -> + Alert + end. + +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}}. + +%% Special version handling for TLS 1.3 clients: +%% In the shared state 'init' negotiated_version is set to requested version and +%% that is expected by the legacy part of the state machine. However, in order to +%% be able to process new TLS 1.3 extensions, the effective version shall be set +%% {3,4}. +%% When highest supported version is {3,4} the negotiated version is set to {3,3}. +effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} -> + Version; +%% Use highest supported version during startup (TLS server, all versions). +effective_version(undefined, #{versions := [Version|_]}, _) -> + Version; +%% Use negotiated version in all other cases. +effective_version(Version, _, _) -> + Version. + +assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, + #{max_handshake_size := Max}) when + Length =< Max -> + case size(Rest) of + N when N < Length -> + true; + N when N > Length -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + too_big_handshake_data)); + _ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data)) + end; +assert_buffer_sanity(Bin, _) -> + case size(Bin) of + N when N < 3 -> + true; + _ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data)) + end. + +decode_alerts(Bin) -> + ssl_alert:decode(Bin). + +handle_alerts([], Result) -> + 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{terminated = true}}}; +handle_alerts([Alert | Alerts], {next_state, 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_record_alert(Alert, _) -> + Alert. + diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index f279e041be..d7c899c7cf 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -36,7 +36,7 @@ -include_lib("kernel/include/logger.hrl"). %% Handshake handling --export([client_hello/9, hello/4]). +-export([client_hello/10, hello/5, hello/4]). %% Handshake encoding -export([encode_handshake/2]). @@ -44,6 +44,9 @@ %% Handshake decodeing -export([get_tls_handshake/4, decode_handshake/3]). +%% Handshake helper +-export([ocsp_nonce/2]). + -type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake(). %%==================================================================== @@ -52,7 +55,8 @@ %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(), ssl_options(), binary(), boolean(), der_cert(), - #key_share_client_hello{} | undefined, tuple() | undefined) -> + #key_share_client_hello{} | undefined, tuple() | undefined, + binary() | undefined) -> #client_hello{}. %% %% Description: Creates a client hello message. @@ -62,7 +66,7 @@ client_hello(_Host, _Port, ConnectionStates, ciphers := UserSuites, fallback := Fallback } = SslOpts, - Id, Renegotiation, _OwnCert, KeyShare, TicketData) -> + Id, Renegotiation, _OwnCert, KeyShare, TicketData, OcspNonce) -> Version = tls_record:highest_protocol_version(Versions), %% In TLS 1.3, the client indicates its version preferences in the @@ -82,9 +86,10 @@ client_hello(_Host, _Port, ConnectionStates, Extensions = ssl_handshake:client_hello_extensions(Version, AvailableCipherSuites, SslOpts, ConnectionStates, - Renegotiation, - KeyShare, - TicketData), + Renegotiation, + KeyShare, + TicketData, + OcspNonce), CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback), #client_hello{session_id = Id, client_version = LegacyVersion, @@ -95,17 +100,14 @@ client_hello(_Host, _Port, ConnectionStates, }. %%-------------------------------------------------------------------- --spec hello(#server_hello{} | #client_hello{}, ssl_options(), +-spec hello(#server_hello{}, ssl_options(), ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(), atom(), ssl_record:connection_states(), binary() | undefined, ssl:kex_algo()}, - boolean()) -> - {tls_record:tls_version(), ssl:session_id(), - ssl_record:connection_states(), alpn | npn, binary() | undefined}| - {tls_record:tls_version(), {resumed | new, #session{}}, - ssl_record:connection_states(), binary() | undefined, - HelloExt::map(), {ssl:hash(), ssl:sign_algo()} | - undefined} | {atom(), atom()} | {atom(), atom(), tuple()} | #alert{}. + boolean(), #session{}) -> + {tls_record:tls_version(), ssl:session_id(), + ssl_record:connection_states(), alpn | npn, binary() | undefined, map()}| + {atom(), atom(), tls_record:tls_version(), map()} | #alert{}. %% %% Description: Handles a received hello message %%-------------------------------------------------------------------- @@ -117,7 +119,7 @@ client_hello(_Host, _Port, ConnectionStates, %% values. hello(#server_hello{server_version = {Major, Minor}, random = <<_:24/binary,Down:8/binary>>}, - #{versions := [{M,N}|_]}, _, _) + #{versions := [{M,N}|_]}, _, _, _) when (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client (Major =:= 3 andalso Minor =:= 3 andalso %% Negotiating TLS 1.2 Down =:= ?RANDOM_OVERRIDE_TLS12) orelse @@ -131,7 +133,7 @@ hello(#server_hello{server_version = {Major, Minor}, %% equal to the second value if the ServerHello indicates TLS 1.1 or below. hello(#server_hello{server_version = {Major, Minor}, random = <<_:24/binary,Down:8/binary>>}, - #{versions := [{M,N}|_]}, _, _) + #{versions := [{M,N}|_]}, _, _, _) when (M =:= 3 andalso N =:= 3) andalso %% TLS 1.2 client (Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior Down =:= ?RANDOM_OVERRIDE_TLS11) -> @@ -156,8 +158,9 @@ hello(#server_hello{server_version = LegacyVersion, extensions = #{server_hello_selected_version := #server_hello_selected_version{selected_version = Version} = HelloExt} }, - #{versions := SupportedVersions} = SslOpt, - ConnectionStates0, Renegotiation) -> + #{versions := SupportedVersions, + ocsp_stapling := Stapling} = SslOpt, + ConnectionStates0, Renegotiation, OldId) -> %% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension %% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version %% number for TLS 1.2. @@ -171,13 +174,15 @@ hello(#server_hello{server_version = LegacyVersion, true -> case Version of {3,3} -> + IsNew = ssl_session:is_new(OldId, SessionId), %% TLS 1.2 ServerHello with "supported_versions" (special case) handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, - ConnectionStates0, Renegotiation); + ConnectionStates0, Renegotiation, IsNew); SelectedVersion -> %% TLS 1.3 - {next_state, wait_sh, SelectedVersion} + {next_state, wait_sh, SelectedVersion, #{ocsp_stapling => Stapling, + ocsp_expect => ocsp_expect(Stapling)}} end; false -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) @@ -191,17 +196,29 @@ hello(#server_hello{server_version = Version, session_id = SessionId, extensions = HelloExt}, #{versions := SupportedVersions} = SslOpt, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, OldId) -> + IsNew = ssl_session:is_new(OldId, SessionId), case tls_record:is_acceptable_version(Version, SupportedVersions) of true -> handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, - ConnectionStates0, Renegotiation); + ConnectionStates0, Renegotiation, IsNew); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) - end; + end. +%%-------------------------------------------------------------------- +-spec hello(#client_hello{}, ssl_options(), + {pid(), #session{}, ssl_record:connection_states(), + binary() | undefined, ssl:kex_algo()}, + boolean()) -> + {tls_record:tls_version(), ssl:session_id(), + ssl_record:connection_states(), alpn | npn, binary() | undefined}| + {tls_record:tls_version(), {resumed | new, #session{}}, + ssl_record:connection_states(), binary() | undefined, + HelloExt::map(), {ssl:hash(), ssl:sign_algo()} | + undefined} | {atom(), atom()} | {atom(), atom(), tuple()} | #alert{}. %% TLS 1.2 Server %% - If "supported_versions" is present (ClientHello): %% - Select version from "supported_versions" (ignore ClientHello.legacy_version) @@ -282,6 +299,20 @@ get_tls_handshake(Version, Data, Buffer, Options) -> get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []). %%-------------------------------------------------------------------- +%%% Handshake helper +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +-spec ocsp_nonce(boolean(), boolean()) -> binary() | undefined. +%% +%% Description: Get an OCSP nonce +%%-------------------------------------------------------------------- +ocsp_nonce(true, true) -> + public_key:der_encode('Nonce', crypto:strong_rand_bytes(8)); +ocsp_nonce(_OcspNonceOpt, _OcspStaplingOpt) -> + undefined. + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- handle_client_hello(Version, @@ -294,28 +325,29 @@ handle_client_hello(Version, signature_algs := SupportedHashSigns, eccs := SupportedECCs, honor_ecc_order := ECCOrder} = SslOpts, - {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, + {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _}, Renegotiation) -> + OwnCert = ssl_handshake:select_own_cert(OwnCerts), case tls_record:is_acceptable_version(Version, Versions) of true -> Curves = maps:get(elliptic_curves, HelloExt, undefined), ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined), AvailableHashSigns = ssl_handshake:available_signature_algs( - ClientHashSigns, SupportedHashSigns, Cert, Version), + ClientHashSigns, SupportedHashSigns, OwnCert, Version), ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, - Port, Session0#session{ecc = ECCCurve}, - Version, SslOpts, Cache, CacheCb, Cert), + SessIdTracker, Session0#session{ecc = ECCCurve}, + Version, SslOpts, OwnCert), case CipherSuite of no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers); _ -> #{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), case ssl_handshake:select_hashsign({ClientHashSigns, ClientSignatureSchemes}, - Cert, KeyExAlg, + OwnCert, KeyExAlg, SupportedHashSigns, Version) of #alert{} = Alert -> @@ -338,7 +370,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites, HelloExt, Version, SslOpts, Session0, ConnectionStates0, - Renegotiation) of + Renegotiation, + Session0#session.is_resumable) of {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} @@ -348,13 +381,13 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, - Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) -> try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, Compression, HelloExt, Version, SslOpt, ConnectionStates0, - Renegotiation) of - {ConnectionStates, ProtoExt, Protocol} -> - {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + Renegotiation, IsNew) of + {ConnectionStates, ProtoExt, Protocol, OcspState} -> + {Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState} catch throw:Alert -> Alert end. @@ -442,3 +475,9 @@ decode_handshake({3, 4}, Tag, Msg) -> tls_handshake_1_3:decode_handshake(Tag, Msg); decode_handshake(Version, Tag, Msg) -> ssl_handshake:decode_handshake(Version, Tag, Msg). + + +ocsp_expect(true) -> + staple; +ocsp_expect(_) -> + no_staple. diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index c0c47c26c4..cd9beecb3f 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -31,6 +31,7 @@ -include("ssl_connection.hrl"). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). +-include("tls_record_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). %% Encode @@ -50,12 +51,19 @@ do_wait_sh/2, do_wait_ee/2, do_wait_cert_cr/2, + do_wait_eoed/2, + early_data_size/1, get_ticket_data/3, maybe_add_binders/3, maybe_add_binders/4, - maybe_automatic_session_resumption/1]). + maybe_add_early_data_indication/3, + maybe_automatic_session_resumption/1, + maybe_send_early_data/1, + update_current_read/3]). --export([is_valid_binder/4]). +-export([get_max_early_data/1, + is_valid_binder/4, + maybe/0]). %% crypto:hash(sha256, "HelloRetryRequest"). -define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17, @@ -115,15 +123,82 @@ server_hello_random(server_hello, #security_parameters{server_random = Random}) server_hello_random(hello_retry_request, _) -> ?HELLO_RETRY_REQUEST_RANDOM. +maybe_add_cookie_extension(#state{ssl_options = #{cookie := false}} = State, + ServerHello) -> + {State, ServerHello}; +maybe_add_cookie_extension(#state{connection_states = ConnectionStates, + ssl_options = #{cookie := true}, + handshake_env = + #handshake_env{ + tls_handshake_history = + {[CH1|_], _}} = HsEnv0} = State, + #server_hello{extensions = Extensions0} = ServerHello) -> + HKDFAlgo = get_hkdf_algorithm(ConnectionStates), + MessageHash0 = message_hash(CH1, HKDFAlgo), + MessageHash = iolist_to_binary(MessageHash0), + + %% Encrypt MessageHash + IV = crypto:strong_rand_bytes(16), + Shard = crypto:strong_rand_bytes(32), + Cookie = ssl_cipher:encrypt_data(<<"cookie">>, MessageHash, Shard, IV), + + HsEnv = HsEnv0#handshake_env{cookie_iv_shard = {IV, Shard}}, + Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}}, + {State#state{handshake_env = HsEnv}, + ServerHello#server_hello{extensions = Extensions}}; +maybe_add_cookie_extension(undefined, ClientHello) -> + ClientHello; +maybe_add_cookie_extension(Cookie, + #client_hello{extensions = Extensions0} = ClientHello) -> + Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}}, + ClientHello#client_hello{extensions = Extensions}. + +validate_cookie(_Cookie, #state{ssl_options = #{cookie := false}}) -> + ok; +validate_cookie(undefined, #state{ssl_options = #{cookie := true}}) -> + ok; +validate_cookie(Cookie0, #state{ssl_options = #{cookie := true}, + handshake_env = + #handshake_env{ + tls_handshake_history = + {[_CH2,_HRR,MessageHash|_], _}, + cookie_iv_shard = {IV, Shard}}}) -> + Cookie = ssl_cipher:decrypt_data(<<"cookie">>, Cookie0, Shard, IV), + case Cookie =:= iolist_to_binary(MessageHash) of + true -> + ok; + false -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)} + end. -encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) -> - #encrypted_extensions{ - extensions = #{} - }; -encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) -> - Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol), +encrypted_extensions(#state{handshake_env = HandshakeEnv}) -> + E0 = #{}, + E1 = case HandshakeEnv#handshake_env.alpn of + undefined -> + E0; + ALPNProtocol -> + ssl_handshake:add_alpn(#{}, ALPNProtocol) + end, + E2 = case HandshakeEnv#handshake_env.max_frag_enum of + undefined -> + E1; + MaxFragEnum -> + E1#{max_frag_enum => MaxFragEnum} + end, + E3 = case HandshakeEnv#handshake_env.sni_guided_cert_selection of + false -> + E2; + true -> + E2#{sni => #sni{hostname = ""}} + end, + E = case HandshakeEnv#handshake_env.early_data_accepted of + false -> + E3; + true -> + E3#{early_data => #early_data_indication{}} + end, #encrypted_extensions{ - extensions = Extensions + extensions = E }. @@ -179,7 +254,11 @@ filter_tls13_algs(Algo) -> %% opaque certificate_request_context<0..2^8-1>; %% CertificateEntry certificate_list<0..2^24-1>; %% } Certificate; -certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> +certificate(undefined, _, _, _, client) -> + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = []}}; +certificate([OwnCert], CertDbHandle, CertDbRef, _CRContext, Role) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, Chain} -> CertList = chain_to_cert_list(Chain), @@ -201,8 +280,12 @@ certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> {ok, #certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}} - end. - + end; +certificate([_,_| _] = Chain, _,_,_,_) -> + CertList = chain_to_cert_list(Chain), + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = CertList}}. certificate_verify(PrivateKey, SignatureScheme, #state{connection_states = ConnectionStates, @@ -213,7 +296,7 @@ certificate_verify(PrivateKey, SignatureScheme, ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, - {HashAlgo, _, _} = + {HashAlgo, SignAlgo, _} = ssl_cipher:scheme_to_components(SignatureScheme), Context = lists:reverse(Messages), @@ -224,7 +307,7 @@ certificate_verify(PrivateKey, SignatureScheme, %% Digital signatures use the hash function defined by the selected signature %% scheme. - case sign(THash, ContextString, HashAlgo, PrivateKey) of + case sign(THash, ContextString, HashAlgo, PrivateKey, SignAlgo) of {ok, Signature} -> {ok, #certificate_verify_1_3{ algorithm = SignatureScheme, @@ -486,24 +569,9 @@ certificate_entry(DER) -> %% 79 %% 00 %% 0101010101010101010101010101010101010101010101010101010101010101 -sign(THash, Context, HashAlgo, #'ECPrivateKey'{} = PrivateKey) -> - Content = build_content(Context, THash), - try public_key:sign(Content, HashAlgo, PrivateKey) of - Signature -> - {ok, Signature} - catch - error:badarg -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} - end; -sign(THash, Context, HashAlgo, PrivateKey) -> +sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) -> Content = build_content(Context, THash), - - %% The length of the Salt MUST be equal to the length of the output - %% of the digest algorithm: rsa_pss_saltlen = -1 - try public_key:sign(Content, HashAlgo, PrivateKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]) of + try ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo) of Signature -> {ok, Signature} catch @@ -511,25 +579,9 @@ sign(THash, Context, HashAlgo, PrivateKey) -> {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} end. - -verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> +verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) -> Content = build_content(Context, THash), - try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of - Result -> - {ok, Result} - catch - error:badarg -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} - end; -verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyParams}) -> - Content = build_content(Context, THash), - - %% The length of the Salt MUST be equal to the length of the output - %% of the digest algorithm: rsa_pss_saltlen = -1 - try public_key:verify(Content, HashAlgo, Signature, PublicKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]) of + try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of Result -> {ok, Result} catch @@ -537,7 +589,6 @@ verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyP {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} end. - build_content(Context, THash) -> Prefix = binary:copy(<<32>>, 64), <<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>. @@ -551,34 +602,44 @@ build_content(Context, THash) -> %% TLS Server do_start(#client_hello{cipher_suites = ClientCiphers, session_id = SessionId, - extensions = Extensions} = _Hello, - #state{connection_states = _ConnectionStates0, - ssl_options = #{ciphers := ServerCiphers, + extensions = Extensions} = Hello, + #state{ssl_options = #{ciphers := ServerCiphers, signature_algs := ServerSignAlgs, supported_groups := ServerGroups0, alpn_preferred_protocols := ALPNPreferredProtocols, - honor_cipher_order := HonorCipherOrder}, - session = #session{own_certificate = Cert}} = State0) -> + keep_secrets := KeepSecrets, + honor_cipher_order := HonorCipherOrder, + early_data := EarlyDataEnabled}} = State0) -> + SNI = maps:get(sni, Extensions, undefined), ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), - ClientGroups = get_supported_groups(ClientGroups0), - ServerGroups = get_supported_groups(ServerGroups0), - - ClientShares0 = maps:get(key_share, Extensions, undefined), - ClientShares = get_key_shares(ClientShares0), - - OfferedPSKs = get_offered_psks(Extensions), - - ClientALPN0 = maps:get(alpn, Extensions, undefined), - ClientALPN = ssl_handshake:decode_alpn(ClientALPN0), + EarlyDataIndication = maps:get(early_data, Extensions, undefined), + {Ref,Maybe} = maybe(), + try + ClientGroups = Maybe(get_supported_groups(ClientGroups0)), + ServerGroups = Maybe(get_supported_groups(ServerGroups0)), + + ClientShares0 = maps:get(key_share, Extensions, undefined), + ClientShares = get_key_shares(ClientShares0), + + OfferedPSKs = get_offered_psks(Extensions), + + ClientALPN0 = maps:get(alpn, Extensions, undefined), + ClientALPN = ssl_handshake:decode_alpn(ClientALPN0), + + ClientSignAlgs = get_signature_scheme_list( + maps:get(signature_algs, Extensions, undefined)), + ClientSignAlgsCert = get_signature_scheme_list( + maps:get(signature_algs_cert, Extensions, undefined)), + + CookieExt = maps:get(cookie, Extensions, undefined), + Cookie = get_cookie(CookieExt), - ClientSignAlgs = get_signature_scheme_list( - maps:get(signature_algs, Extensions, undefined)), - ClientSignAlgsCert = get_signature_scheme_list( - maps:get(signature_algs_cert, Extensions, undefined)), + #state{connection_states = ConnectionStates0, + session = #session{own_certificates = [Cert | _]}} = State1 = + Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)), - {Ref,Maybe} = maybe(), + Maybe(validate_cookie(Cookie, State1)), - try %% Handle ALPN extension if ALPN is configured ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)), @@ -587,17 +648,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), - Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), - - {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), + {PublicKeyAlgo, SignAlgo, SignHash, RSAKeySize} = get_certificate_params(Cert), %% Check if client supports signature algorithm of server certificate Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)), %% Select signature algorithm (used in CertificateVerify message). - SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)), + SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs)), %% Select client public key. If no public key found in ClientShares or %% ClientShares is empty, trigger HelloRetryRequest as we were able @@ -608,7 +667,23 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), - State1 = update_start_state(State0, + State2 = case maps:get(max_frag_enum, Extensions, undefined) of + 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}, + State1#state{handshake_env = HsEnv1, + connection_states = ConnectionStates1}; + _ -> + State1 + end, + + State3 = if KeepSecrets =:= true -> + set_client_random(State2, Hello#client_hello.random); + true -> + State2 + end, + + State4 = update_start_state(State3, #{cipher => Cipher, key_share => KeyShare, session_id => SessionId, @@ -623,13 +698,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% message if it is able to find an acceptable set of parameters but the %% ClientHello does not contain sufficient information to proceed with %% the handshake. - case Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)) of + case Maybe(send_hello_retry_request(State4, ClientPubKey, KeyShare, SessionId)) of {_, start} = NextStateTuple -> NextStateTuple; - {_, negotiated} = NextStateTuple -> + {State5, negotiated} -> + %% Determine if early data is accepted + State = handle_early_data(State5, EarlyDataEnabled, EarlyDataIndication), %% Exclude any incompatible PSKs. - PSK = Maybe(handle_pre_shared_key(State1, OfferedPSKs, Cipher)), - Maybe(session_resumption(NextStateTuple, PSK)) + PSK = Maybe(handle_pre_shared_key(State, OfferedPSKs, Cipher)), + Maybe(session_resumption({State, negotiated}, PSK)) end catch {Ref, #alert{} = Alert} -> @@ -642,22 +719,26 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, #state{static_env = #static_env{role = client, host = Host, port = Port, + protocol_cb = Connection, transport_cb = Transport, socket = Socket}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState}, connection_env = #connection_env{negotiated_version = NegotiatedVersion}, ssl_options = #{ciphers := ClientCiphers, supported_groups := ClientGroups0, use_ticket := UseTicket, session_tickets := SessionTickets, log_level := LogLevel} = SslOpts, - session = #session{own_certificate = Cert} = Session0, + session = #session{own_certificates = OwnCerts} = Session0, connection_states = ConnectionStates0 } = State0) -> - ClientGroups = get_supported_groups(ClientGroups0), - {Ref,Maybe} = maybe(), try + ClientGroups = Maybe(get_supported_groups(ClientGroups0)), + CookieExt = maps:get(cookie, Extensions, undefined), + Cookie = get_cookie(CookieExt), + ServerKeyShare = maps:get(key_share, Extensions, undefined), SelectedGroup = get_selected_group(ServerKeyShare), @@ -679,9 +760,12 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, %% of the triggering HelloRetryRequest. ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]), TicketData = get_ticket_data(self(), SessionTickets, UseTicket), + OcspNonce = maps:get(ocsp_nonce, OcspState, undefined), Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - SessionId, Renegotiation, Cert, ClientKeyShare, - TicketData), + SessionId, Renegotiation, OwnCerts, ClientKeyShare, + TicketData, OcspNonce), + %% Echo cookie received in HelloRetryrequest + Hello1 = maybe_add_cookie_extension(Cookie, Hello0), %% Update state State1 = update_start_state(State0, @@ -695,15 +779,20 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, #state{handshake_env = #handshake_env{tls_handshake_history = HHistory0}} = State2, %% Update pre_shared_key extension with binders (TLS 1.3) - Hello = tls_handshake_1_3:maybe_add_binders(Hello0, HHistory0, TicketData, NegotiatedVersion), + Hello = tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0, TicketData, NegotiatedVersion), + + {BinMsg0, ConnectionStates, HHistory} = + Connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0), + + %% D.4. Middlebox Compatibility Mode + {#state{handshake_env = HsEnv} = State3, BinMsg} = + maybe_prepend_change_cipher_spec(State2, BinMsg0), - {BinMsg, ConnectionStates, HHistory} = - tls_connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0), tls_socket:send(Transport, Socket, BinMsg), ssl_logger:debug(LogLevel, outbound, 'handshake', Hello), ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - State = State2#state{ + State = State3#state{ connection_states = ConnectionStates, session = Session0#session{session_id = Hello#client_hello.session_id}, handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory}, @@ -716,9 +805,12 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, Alert end. - do_negotiated({start_handshake, PSK0}, #state{connection_states = ConnectionStates0, + handshake_env = + #handshake_env{ + early_data_accepted = EarlyDataAccepted}, + static_env = #static_env{protocol_cb = Connection}, session = #session{session_id = SessionId, ecc = SelectedGroup, dh_public_value = ClientPublicKey}, @@ -730,47 +822,61 @@ do_negotiated({start_handshake, PSK0}, ssl_record:pending_connection_state(ConnectionStates0, read), #security_parameters{prf_algorithm = HKDF} = SecParamsR, - {Ref,Maybe} = maybe(), try %% Create server_hello ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0), - - {State1, _} = tls_connection:send_handshake(ServerHello, State0), + State1 = Connection:queue_handshake(ServerHello, State0), + %% D.4. Middlebox Compatibility Mode + State2 = maybe_queue_change_cipher_spec(State1, last), PSK = get_pre_shared_key(PSK0, HKDF), - State2 = + State3 = calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, - PSK, State1), - - State3 = ssl_record:step_encryption_state(State2), + PSK, State2), + + %% Step only write state if early_data is accepted + State4 = + case EarlyDataAccepted of + true -> + ssl_record:step_encryption_state_write(State3); + false -> + %% Read state is overwritten when hanshake secrets are set. + %% Trial_decryption and early_data_limit must be set here! + update_current_read( + ssl_record:step_encryption_state(State3), + true, %% trial_decryption + false %% early data limit + ) + + end, %% Create EncryptedExtensions - EncryptedExtensions = encrypted_extensions(State2), + EncryptedExtensions = encrypted_extensions(State4), %% Encode EncryptedExtensions - State4 = tls_connection:queue_handshake(EncryptedExtensions, State3), + State5 = Connection:queue_handshake(EncryptedExtensions, State4), %% Create and send CertificateRequest ({verify, verify_peer}) - {State5, NextState} = maybe_send_certificate_request(State4, SslOpts, PSK0), + {State6, NextState} = maybe_send_certificate_request(State5, SslOpts, PSK0), %% Create and send Certificate (if PSK is undefined) - State6 = Maybe(maybe_send_certificate(State5, PSK0)), + State7 = Maybe(maybe_send_certificate(State6, PSK0)), %% Create and send CertificateVerify (if PSK is undefined) - State7 = Maybe(maybe_send_certificate_verify(State6, PSK0)), + State8 = Maybe(maybe_send_certificate_verify(State7, PSK0)), %% Create Finished - Finished = finished(State7), + Finished = finished(State8), %% Encode Finished - State8 = tls_connection:queue_handshake(Finished, State7), + State9 = Connection:queue_handshake(Finished, State8), %% Send first flight - {State9, _} = tls_connection:send_handshake_flight(State8), + {State, _} = Connection:send_handshake_flight(State9), - {State9, NextState} + {State, NextState} catch {Ref, #alert{} = Alert} -> @@ -790,10 +896,15 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) -> end. -do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) -> +do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, #state{static_env = #static_env{role = Role}} = State0) -> {Ref,Maybe} = maybe(), try - State1 = Maybe(verify_signature_algorithm(State0, CertificateVerify)), + State1 = case Role of + server -> + Maybe(verify_signature_algorithm(State0, CertificateVerify)); + client -> + State0 + end, Maybe(verify_certificate_verify(State1, CertificateVerify)) catch {Ref, {#alert{} = Alert, State}} -> @@ -824,31 +935,29 @@ do_wait_finished(#finished{verify_data = VerifyData}, end; %% TLS Client do_wait_finished(#finished{verify_data = VerifyData}, - #state{static_env = #static_env{role = client}} = State0) -> - + #state{static_env = #static_env{role = client, + protocol_cb = Connection}} = State0) -> + {Ref,Maybe} = maybe(), try Maybe(validate_finished(State0, VerifyData)), - + %% D.4. Middlebox Compatibility Mode + State1 = maybe_queue_change_cipher_spec(State0, first), + %% Signal change of cipher + State2 = maybe_send_end_of_early_data(State1), %% Maybe send Certificate + CertificateVerify - State1 = Maybe(maybe_queue_cert_cert_cv(State0)), - - Finished = finished(State1), - + State3 = Maybe(maybe_queue_cert_cert_cv(State2)), + Finished = finished(State3), %% Encode Finished - State2 = tls_connection:queue_handshake(Finished, State1), - + State4 = Connection:queue_handshake(Finished, State3), %% Send first flight - {State3, _} = tls_connection:send_handshake_flight(State2), - - State4 = calculate_traffic_secrets(State3), - State5 = maybe_calculate_resumption_master_secret(State4), - State6 = forget_master_secret(State5), - + {State5, _} = Connection:send_handshake_flight(State4), + State6 = calculate_traffic_secrets(State5), + State7 = maybe_calculate_resumption_master_secret(State6), + State8 = forget_master_secret(State7), %% Configure traffic keys - ssl_record:step_encryption_state(State6) - + ssl_record:step_encryption_state(State8) catch {Ref, #alert{} = Alert} -> Alert @@ -863,14 +972,15 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, supported_groups := ClientGroups0, session_tickets := SessionTickets, use_ticket := UseTicket}} = State0) -> - ClientGroups = get_supported_groups(ClientGroups0), - ServerKeyShare0 = maps:get(key_share, Extensions, undefined), - ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined), - SelectedIdentity = get_selected_identity(ServerPreSharedKey), - ClientKeyShare = get_key_shares(ClientKeyShare0), - + {Ref,Maybe} = maybe(), try + ClientGroups = Maybe(get_supported_groups(ClientGroups0)), + ServerKeyShare0 = maps:get(key_share, Extensions, undefined), + ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined), + SelectedIdentity = get_selected_identity(ServerPreSharedKey), + ClientKeyShare = get_key_shares(ClientKeyShare0), + %% Go to state 'start' if server replies with 'HelloRetryRequest'. Maybe(maybe_hello_retry_request(ServerHello, State0)), @@ -902,8 +1012,8 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, PSK = Maybe(get_pre_shared_key(SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)), State3 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, PSK, State2), - State4 = ssl_record:step_encryption_state(State3), - + %% State4 = ssl_record:step_encryption_state(State3), + State4 = ssl_record:step_encryption_state_read(State3), {State4, wait_ee} catch @@ -918,21 +1028,30 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) -> ALPNProtocol0 = maps:get(alpn, Extensions, undefined), ALPNProtocol = get_alpn(ALPNProtocol0), + EarlyDataIndication = maps:get(early_data, Extensions, undefined), {Ref, Maybe} = maybe(), try + %% RFC 6066: handle received/expected maximum fragment length + Maybe(maybe_max_fragment_length(Extensions, State0)), + + %% Check if early_data is accepted/rejected + State1 = maybe_check_early_data_indication(EarlyDataIndication, State0), + %% Go to state 'wait_finished' if using PSK. - Maybe(maybe_resumption(State0)), + Maybe(maybe_resumption(State1)), %% Update state - #state{handshake_env = HsEnv} = State0, - State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}}, + #state{handshake_env = HsEnv} = State1, + State2 = State1#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}}, - {State1, wait_cert_cr} + {State2, wait_cert_cr} catch {Ref, {State, StateName}} -> - {State, StateName} + {State, StateName}; + {Ref, #alert{} = Alert} -> + Alert end. @@ -956,6 +1075,25 @@ do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) -> end. +do_wait_eoed(#end_of_early_data{}, State0) -> + {Ref,_Maybe} = maybe(), + try + %% Step read state to enable reading handshake messages from the client. + %% Write state is already stepped in state 'negotiated'. + State1 = ssl_record:step_encryption_state_read(State0), + + %% Early data has been received, no more early data is expected. + HsEnv = (State1#state.handshake_env)#handshake_env{early_data_accepted = false}, + State2 = State1#state{handshake_env = HsEnv}, + {State2, wait_finished} + catch + {Ref, #alert{} = Alert} -> + {Alert, State0}; + {Ref, {#alert{} = Alert, State}} -> + {Alert, State} + end. + + %% For reasons of backward compatibility with middleboxes (see %% Appendix D.4), the HelloRetryRequest message uses the same structure %% as the ServerHello, but with Random set to the special value of the @@ -972,6 +1110,16 @@ maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = maybe_hello_retry_request(_, _) -> ok. +maybe_max_fragment_length(Extensions, State) -> + ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined), + ClientMaxFragEnum = ssl_handshake:max_frag_enum( + maps:get(max_fragment_length, State#state.ssl_options, undefined)), + if ServerMaxFragEnum == ClientMaxFragEnum -> + ok; + true -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)} + end. + maybe_resumption(#state{handshake_env = #handshake_env{resumption = true}} = State) -> {error, {State, wait_finished}}; @@ -985,17 +1133,68 @@ handle_resumption(#state{handshake_env = HSEnv0} = State, _) -> HSEnv = HSEnv0#handshake_env{resumption = true}, State#state{handshake_env = HSEnv}. +%% @doc Enqueues a change_cipher_spec record as the first/last message of +%% the current flight buffer +%% @end +maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, first) -> + {State, FlightBuffer} = maybe_prepend_change_cipher_spec(State0, FlightBuffer0), + State#state{flight_buffer = FlightBuffer}; +maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, last) -> + {State, FlightBuffer} = maybe_append_change_cipher_spec(State0, FlightBuffer0), + State#state{flight_buffer = FlightBuffer}. + +%% @doc Prepends a change_cipher_spec record to the input binary +%% +%% It can only prepend the change_cipher_spec record only once in +%% order to accurately emulate a legacy TLS 1.2 connection. +%% +%% D.4. Middlebox Compatibility Mode +%% If not offering early data, the client sends a dummy +%% change_cipher_spec record (see the third paragraph of Section 5) +%% immediately before its second flight. This may either be before +%% its second ClientHello or before its encrypted handshake flight. +%% If offering early data, the record is placed immediately after the +%% first ClientHello. +%% @end +maybe_prepend_change_cipher_spec(#state{ + ssl_options = + #{middlebox_comp_mode := true}, + handshake_env = + #handshake_env{ + change_cipher_spec_sent = false} = HSEnv} = State, Bin) -> + CCSBin = create_change_cipher_spec(State), + {State#state{handshake_env = + HSEnv#handshake_env{change_cipher_spec_sent = true}}, + [CCSBin|Bin]}; +maybe_prepend_change_cipher_spec(State, Bin) -> + {State, Bin}. + +%% @doc Appends a change_cipher_spec record to the input binary +%% @end +maybe_append_change_cipher_spec(#state{ + ssl_options = + #{middlebox_comp_mode := true}, + handshake_env = + #handshake_env{ + change_cipher_spec_sent = false} = HSEnv} = State, Bin) -> + CCSBin = create_change_cipher_spec(State), + {State#state{handshake_env = + HSEnv#handshake_env{change_cipher_spec_sent = true}}, + Bin ++ [CCSBin]}; +maybe_append_change_cipher_spec(State, Bin) -> + {State, Bin}. maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) -> {ok, State}; maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, session = #session{session_id = _SessionId, - own_certificate = OwnCert}, + own_certificates = OwnCerts}, ssl_options = #{} = _SslOpts, key_share = _KeyShare, handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, static_env = #static_env{ role = client, + protocol_cb = Connection, cert_db = CertDbHandle, cert_db_ref = CertDbRef, socket = _Socket, @@ -1004,11 +1203,10 @@ maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, {Ref,Maybe} = maybe(), try %% Create Certificate - Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)), + Certificate = Maybe(certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, client)), %% Encode Certificate - State1 = tls_connection:queue_handshake(Certificate, State0), - + State1 = Connection:queue_handshake(Certificate, State0), %% Maybe create and queue CertificateVerify State = Maybe(maybe_queue_cert_verify(Certificate, State1)), {ok, State} @@ -1026,12 +1224,13 @@ maybe_queue_cert_verify(_Certificate, #state{connection_states = _ConnectionStates0, session = #session{sign_alg = SignatureScheme}, connection_env = #connection_env{private_key = CertPrivateKey}, - static_env = #static_env{role = client} + static_env = #static_env{role = client, + protocol_cb = Connection} } = State) -> {Ref,Maybe} = maybe(), try CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)), - {ok, tls_connection:queue_handshake(CertificateVerify, State)} + {ok, Connection:queue_handshake(CertificateVerify, State)} catch {Ref, #alert{} = Alert} -> {error, Alert} @@ -1064,15 +1263,21 @@ compare_verify_data(_, _) -> {error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}. -send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0, +send_hello_retry_request(#state{connection_states = ConnectionStates0, + static_env = #static_env{protocol_cb = Connection}} = State0, no_suitable_key, KeyShare, SessionId) -> - ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0), - {State1, _} = tls_connection:send_handshake(ServerHello, State0), + ServerHello0 = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0), + {State1, ServerHello} = maybe_add_cookie_extension(State0, ServerHello0), + + State2 = Connection:queue_handshake(ServerHello, State1), + %% D.4. Middlebox Compatibility Mode + State3 = maybe_queue_change_cipher_spec(State2, last), + {State4, _} = Connection:send_handshake_flight(State3), %% Update handshake history - State2 = replace_ch1_with_message_hash(State1), + State5 = replace_ch1_with_message_hash(State4), - {ok, {State2, start}}; + {ok, {State5, start}}; send_hello_retry_request(State0, _, _, _) -> %% Suitable key found. {ok, {State0, negotiated}}. @@ -1082,33 +1287,55 @@ session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined) when Tickets =/= disabled -> {ok, {State, negotiated}}; -session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State0, negotiated}, PSK) +session_resumption({#state{ssl_options = #{session_tickets := Tickets}, + handshake_env = #handshake_env{ + early_data_accepted = false}} = State0, negotiated}, PSK) when Tickets =/= disabled -> State = handle_resumption(State0, ok), - {ok, {State, negotiated, PSK}}. - - + {ok, {State, negotiated, PSK}}; +session_resumption({#state{ssl_options = #{session_tickets := Tickets}, + handshake_env = #handshake_env{ + early_data_accepted = true}} = State0, negotiated}, PSK0) + when Tickets =/= disabled -> + State1 = handle_resumption(State0, ok), + %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed. + {_ , PSK} = PSK0, + State2 = calculate_client_early_traffic_secret(State1, PSK), + %% Set 0-RTT traffic keys for reading early_data + State3 = ssl_record:step_encryption_state_read(State2), + State = update_current_read(State3, true, true), + {ok, {State, negotiated, PSK0}}. + +%% Session resumption with early_data +maybe_send_certificate_request(#state{ + handshake_env = + #handshake_env{ + early_data_accepted = true}} = State, + _, PSK) when PSK =/= undefined -> + %% Go wait for End of Early Data + {State, wait_eoed}; %% Do not send CR during session resumption maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined -> {State, wait_finished}; maybe_send_certificate_request(State, #{verify := verify_none}, _) -> {State, wait_finished}; -maybe_send_certificate_request(State, #{verify := verify_peer, - signature_algs := SignAlgs, - signature_algs_cert := SignAlgsCert}, _) -> +maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Connection}} = State, + #{verify := verify_peer, + signature_algs := SignAlgs, + signature_algs_cert := SignAlgsCert}, _) -> CertificateRequest = certificate_request(SignAlgs, SignAlgsCert), - {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}. - + {Connection:queue_handshake(CertificateRequest, State), wait_cert}. maybe_send_certificate(State, PSK) when PSK =/= undefined -> {ok, State}; -maybe_send_certificate(#state{session = #session{own_certificate = OwnCert}, +maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts}, static_env = #static_env{ + protocol_cb = Connection, cert_db = CertDbHandle, cert_db_ref = CertDbRef}} = State, _) -> - case certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server) of + case certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of {ok, Certificate} -> - {ok, tls_connection:queue_handshake(Certificate, State)}; + {ok, Connection:queue_handshake(Certificate, State)}; Error -> Error end. @@ -1117,11 +1344,12 @@ maybe_send_certificate(#state{session = #session{own_certificate = OwnCert}, maybe_send_certificate_verify(State, PSK) when PSK =/= undefined -> {ok, State}; maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme}, + static_env = #static_env{protocol_cb = Connection}, connection_env = #connection_env{ private_key = CertPrivateKey}} = State, _) -> case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of {ok, CertificateVerify} -> - {ok, tls_connection:queue_handshake(CertificateVerify, State)}; + {ok, Connection:queue_handshake(CertificateVerify, State)}; Error -> Error end. @@ -1143,41 +1371,71 @@ maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} = maybe_send_session_ticket(State, 0) -> State; maybe_send_session_ticket(#state{connection_states = ConnectionStates, - static_env = #static_env{trackers = Trackers}} = State0, N) -> + static_env = #static_env{trackers = Trackers, + protocol_cb = Connection} + + } = State0, N) -> Tracker = proplists:get_value(session_tickets_tracker, Trackers), #{security_parameters := SecParamsR} = ssl_record:current_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = HKDF, resumption_master_secret = RMS} = SecParamsR, Ticket = tls_server_session_ticket:new(Tracker, HKDF, RMS), - {State, _} = tls_connection:send_handshake(Ticket, State0), + {State, _} = Connection:send_handshake(Ticket, State0), maybe_send_session_ticket(State, N - 1). +create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) -> + %% Dummy connection_states with NULL cipher + ConnectionStates = + #{current_write => + #{compression_state => undefined, + cipher_state => undefined, + sequence_number => 1, + security_parameters => + #security_parameters{ + bulk_cipher_algorithm = 0, + compression_algorithm = ?NULL, + mac_algorithm = ?NULL + }, + mac_secret => undefined}}, + {BinChangeCipher, _} = + tls_record:encode_change_cipher_spec(?LEGACY_VERSION, ConnectionStates), + ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher), + [BinChangeCipher]. + process_certificate_request(#certificate_request_1_3{}, - #state{session = #session{own_certificate = undefined}} = State) -> + #state{session = #session{own_certificates = undefined}} = State) -> {ok, {State#state{client_certificate_requested = true}, wait_cert}}; process_certificate_request(#certificate_request_1_3{ - extensions = Extensions}, - #state{session = #session{own_certificate = Cert} = Session} = State) -> + extensions = Extensions}, + #state{ssl_options = #{signature_algs := ClientSignAlgs}, + session = #session{own_certificates = [Cert|_]} = Session} = + State) -> ServerSignAlgs = get_signature_scheme_list( maps:get(signature_algs, Extensions, undefined)), ServerSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), - - %% Check if server supports signature algorithm of client certificate - case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of - ok -> - {ok, {State#state{client_certificate_requested = true}, wait_cert}}; - {error, _} -> - %% Certificate not supported: send empty certificate in state 'wait_finished' + {PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize} = get_certificate_params(Cert), + {Ref, Maybe} = maybe(), + try + SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs)), + %% Check if server supports signature algorithm of client certificate + case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of + ok -> {ok, {State#state{client_certificate_requested = true, - session = Session#session{own_certificate = undefined}}, wait_cert}} + session = Session#session{sign_alg = SelectedSignAlg}}, wait_cert}}; + {error, _} -> + %% Certificate not supported: send empty certificate in state 'wait_finished' + {ok, {State#state{client_certificate_requested = true, + session = Session#session{own_certificates = undefined}}, wait_cert}} + end + catch + {Ref, #alert{} = Alert} -> + Alert end. - process_certificate(#certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}, @@ -1189,7 +1447,6 @@ process_certificate(#certificate_1_3{ certificate_list = []}, #state{ssl_options = #{fail_if_no_peer_cert := true}} = State0) -> - %% At this point the client believes that the connection is up and starts using %% its traffic secrets. In order to be able send an proper Alert to the client %% the server should also change its connection state and use the traffic @@ -1197,56 +1454,30 @@ process_certificate(#certificate_1_3{ State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), {error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}}; -process_certificate(#certificate_1_3{certificate_list = Certs0}, - #state{ssl_options = - #{signature_algs := SignAlgs, - signature_algs_cert := SignAlgsCert} = SslOptions, - static_env = - #static_env{ - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbHandle}} = State0) -> - %% TODO: handle extensions! - %% Remove extensions from list of certificates! - Certs = convert_certificate_chain(Certs0), - case is_supported_signature_algorithm(Certs, SignAlgs, SignAlgsCert) of - true -> - case validate_certificate_chain(Certs, CertDbHandle, CertDbRef, - SslOptions, CRLDbHandle, Role, Host) of - {ok, {PeerCert, PublicKeyInfo}} -> - State = store_peer_cert(State0, PeerCert, PublicKeyInfo), - {ok, {State, wait_cv}}; - {error, Reason} -> - State = update_encryption_state(Role, State0), - {error, {Reason, State}}; - {ok, #alert{} = Alert} -> - State = update_encryption_state(Role, State0), - {error, {Alert, State}} - end; - false -> - State1 = calculate_traffic_secrets(State0), - State = ssl_record:step_encryption_state(State1), - {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - "Client certificate uses unsupported signature algorithm"), State}} +process_certificate(#certificate_1_3{certificate_list = CertEntries}, + #state{ssl_options = SslOptions, + static_env = + #static_env{ + role = Role, + host = Host, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + crl_db = CRLDbHandle}, + handshake_env = #handshake_env{ + ocsp_stapling_state = OcspState}} = State0) -> + case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef, + SslOptions, CRLDbHandle, Role, Host, OcspState) of + {ok, {PeerCert, PublicKeyInfo}} -> + State = store_peer_cert(State0, PeerCert, PublicKeyInfo), + {ok, {State, wait_cv}}; + {error, Reason} -> + State = update_encryption_state(Role, State0), + {error, {Reason, State}}; + {ok, #alert{} = Alert} -> + State = update_encryption_state(Role, State0), + {error, {Alert, State}} end. - -%% TODO: check whole chain! -is_supported_signature_algorithm(Certs, SignAlgs, undefined) -> - is_supported_signature_algorithm(Certs, SignAlgs); -is_supported_signature_algorithm(Certs, _, SignAlgsCert) -> - is_supported_signature_algorithm(Certs, SignAlgsCert). -%% -is_supported_signature_algorithm([BinCert|_], SignAlgs0) -> - #'OTPCertificate'{signatureAlgorithm = SignAlg} = - public_key:pkix_decode_cert(BinCert, otp), - SignAlgs = filter_tls13_algs(SignAlgs0), - Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg), - lists:member(Scheme, SignAlgs). - - %% Sets correct encryption state when sending Alerts in shared states that use different secrets. %% - If client: use handshake secrets. %% - If server: use traffic secrets as by this time the client's state machine @@ -1258,46 +1489,34 @@ update_encryption_state(client, State) -> State. -validate_certificate_chain(Certs, CertDbHandle, CertDbRef, +validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef, #{server_name_indication := ServerNameIndication, partial_chain := PartialChain, - verify_fun := VerifyFun, - customize_hostname_check := CustomizeHostnameCheck, - crl_check := CrlCheck, - log_level := LogLevel, - depth := Depth} = SslOptions, - CRLDbHandle, Role, Host) -> + ocsp_responder_certs := OcspResponderCerts + } = SslOptions, CRLDbHandle, Role, Host, OcspState0) -> + {Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0), ServerName = ssl_handshake:server_name(ServerNameIndication, Host, Role), - [PeerCert | ChainCerts ] = Certs, - try - {TrustedCert, CertPath} = - ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef, + [PeerCert | _ChainCerts ] = Certs, + try + PathsAndAnchors = + ssl_certificate:trusted_cert_and_paths(Certs, CertDbHandle, CertDbRef, PartialChain), - ValidationFunAndState = - ssl_handshake:validation_fun_and_state(VerifyFun, Role, - CertDbHandle, CertDbRef, ServerName, - CustomizeHostnameCheck, - CrlCheck, CRLDbHandle, CertPath, LogLevel), - Options = [{max_path_length, Depth}, - {verify_fun, ValidationFunAndState}], - %% TODO: Validate if Certificate is using a supported signature algorithm - %% (signature_algs_cert)! - case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of - {ok, {PublicKeyInfo,_}} -> - {ok, {PeerCert, PublicKeyInfo}}; - {error, Reason} -> - {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts, - SslOptions, Options, - CertDbHandle, CertDbRef)} - end - catch - error:{badmatch,{error, {asn1, Asn1Reason}}} -> - %% ASN-1 decode of certificate somehow failed - {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})}; - error:OtherReason -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})} - end. - + case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + {3, 4}, SslOptions, #{cert_ext => CertExt, + ocsp_state => OcspState, + ocsp_responder_certs => OcspResponderCerts}) of + {ok, {PublicKeyInfo,_}} -> + {ok, {PeerCert, PublicKeyInfo}}; + {error, Reason} -> + {ok, ssl_handshake:path_validation_alert(Reason)} + end + catch + error:{badmatch,{error, {asn1, Asn1Reason}}} -> + %% ASN-1 decode of certificate somehow failed + {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})}; + error:OtherReason -> + {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})} + end. store_peer_cert(#state{session = Session, handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) -> @@ -1305,13 +1524,21 @@ store_peer_cert(#state{session = Session, handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}. -convert_certificate_chain(Certs) -> - Fun = fun(#certificate_entry{data = Data}) -> - {true, Data}; - (_) -> - false - end, - lists:filtermap(Fun, Certs). +split_cert_entries(CertEntries, OcspState) -> + split_cert_entries(CertEntries, OcspState, [], #{}). +split_cert_entries([], OcspState, Chain, Ext) -> + {lists:reverse(Chain), Ext, OcspState}; +split_cert_entries([#certificate_entry{data = DerCert, + extensions = Extensions0} | CertEntries], OcspState0, Chain, Ext) -> + Id = public_key:pkix_subject_id(DerCert), + Extensions = [ExtValue || {_, ExtValue} <- maps:to_list(Extensions0)], + OcspState = case maps:get(status_request, Extensions0, undefined) of + undefined -> + OcspState0; + _ -> + OcspState0#{ocsp_expect => stapled} + end, + split_cert_entries(CertEntries, OcspState, [DerCert | Chain], Ext#{Id => Extensions}). %% 4.4.1. The Transcript Hash @@ -1343,6 +1570,11 @@ replace_ch1_with_message_hash(#state{connection_states = ConnectionStates, tls_handshake_history = {[HRR,MessageHash|HHistory], LM}}}. +get_hkdf_algorithm(ConnectionStates) -> + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, read), + #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, + HKDFAlgo. message_hash(ClientHello1, HKDFAlgo) -> [?MESSAGE_HASH, @@ -1366,26 +1598,88 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK, %% Calculate [sender]_handshake_traffic_secret {Messages, _} = HHistory, - ClientHSTrafficSecret = tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), ServerHSTrafficSecret = tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), %% Calculate traffic keys - #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret), - {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret), + KeyLength = tls_v1:key_length(CipherSuite), + {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientHSTrafficSecret), + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerHSTrafficSecret), %% Calculate Finished Keys ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo), WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo), - update_pending_connection_states(State0, HandshakeSecret, undefined, + State1 = maybe_store_handshake_traffic_secret(State0, ClientHSTrafficSecret, ServerHSTrafficSecret), + + update_pending_connection_states(State1, HandshakeSecret, undefined, undefined, undefined, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey). +%% Server +calculate_client_early_traffic_secret(#state{connection_states = ConnectionStates, + handshake_env = + #handshake_env{ + tls_handshake_history = {Hist, _}}} = State, PSK) -> + + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, read), + #security_parameters{cipher_suite = CipherSuite} = SecParamsR, + #{cipher := Cipher, + prf := HKDF} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State). + +%% Client +calculate_client_early_traffic_secret( + ClientHello, PSK, Cipher, HKDFAlgo, + #state{connection_states = ConnectionStates, + ssl_options = #{keep_secrets := KeepSecrets}, + static_env = #static_env{role = Role}} = State0) -> + EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}), + ClientEarlyTrafficSecret = + tls_v1:client_early_traffic_secret(HKDFAlgo, EarlySecret, ClientHello), + %% Calculate traffic key + KeyLength = ssl_cipher:key_material(Cipher), + {Key, IV} = + tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientEarlyTrafficSecret), + %% Update pending connection states + case Role of + client -> + PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write), + PendingWrite1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret, + PendingWrite0), + PendingWrite = update_connection_state(PendingWrite1, undefined, undefined, + undefined, + Key, IV, undefined), + State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}}; + server -> + PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read), + PendingRead1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret, + PendingRead0), + PendingRead2 = update_connection_state(PendingRead1, undefined, undefined, + undefined, + Key, IV, undefined), + %% Signal start of early data. This is to prevent handshake messages to be + %% counted in max_early_data_size. + PendingRead = PendingRead2#{count_early_data => true}, + State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}} + end. + +update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataLimit) -> + Read0 = ssl_record:current_connection_state(CS, read), + Read = Read0#{trial_decryption => TrialDecryption, + early_data_limit => EarlyDataLimit}, + State#state{connection_states = CS#{current_read => Read}}. + +maybe_store_early_data_secret(true, EarlySecret, State) -> + #{security_parameters := SecParams0} = State, + SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret}, + State#{security_parameters := SecParams}; +maybe_store_early_data_secret(false, _, State) -> + State. %% Server get_pre_shared_key(undefined, HKDFAlgo) -> @@ -1410,7 +1704,7 @@ get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentit {ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))}; illegal_parameter -> {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; - {_, PSK} -> + {_, PSK, _, _, _} -> {ok, PSK} end; get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) -> @@ -1422,19 +1716,35 @@ get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) illegal_parameter -> tls_client_ticket_store:unlock_tickets(self(), UseTicket), {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; - {Key, PSK} -> + {Key, PSK, _, _, _} -> tls_client_ticket_store:remove_tickets([Key]), %% Remove single-use ticket tls_client_ticket_store:unlock_tickets(self(), UseTicket -- [Key]), {ok, PSK} end. - +%% +%% Early Data +get_pre_shared_key_early_data(SessionTickets, UseTicket) -> + TicketData = get_ticket_data(self(), SessionTickets, UseTicket), + case choose_psk(TicketData, 0) of + undefined -> %% Should not happen + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; + illegal_parameter -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; + {_Key, PSK, Cipher, HKDF, MaxSize} -> + {ok, {PSK, Cipher, HKDF, MaxSize}} + end. choose_psk(undefined, _) -> undefined; choose_psk([], _) -> illegal_parameter; -choose_psk([{Key, SelectedIdentity, _, PSK, _, _}|_], SelectedIdentity) -> - {Key, PSK}; +choose_psk([#ticket_data{ + key = Key, + pos = SelectedIdentity, + psk = PSK, + cipher_suite = {Cipher, HKDF}, + max_size = MaxSize}|_], SelectedIdentity) -> + {Key, PSK, Cipher, HKDF, MaxSize}; choose_psk([_|T], SelectedIdentity) -> choose_psk(T, SelectedIdentity). @@ -1464,9 +1774,9 @@ calculate_traffic_secrets(#state{ tls_v1:server_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)), %% Calculate traffic keys - #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientAppTrafficSecret0), - {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerAppTrafficSecret0), + KeyLength = tls_v1:key_length(CipherSuite), + {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientAppTrafficSecret0), + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerAppTrafficSecret0), update_pending_connection_states(State0, MasterSecret, undefined, ClientAppTrafficSecret0, ServerAppTrafficSecret0, @@ -1538,6 +1848,36 @@ overwrite_master_secret(ConnectionState = #{security_parameters := SecurityParam ConnectionState#{security_parameters => SecurityParameters}. +set_client_random(#state{connection_states = + #{pending_read := PendingRead, + pending_write := PendingWrite, + current_read := CurrentRead, + current_write := CurrentWrite} = CS} = State, ClientRandom) -> + State#state{connection_states = CS#{pending_read => overwrite_client_random(PendingRead, ClientRandom), + pending_write => overwrite_client_random(PendingWrite, ClientRandom), + current_read => overwrite_client_random(CurrentRead, ClientRandom), + current_write => overwrite_client_random(CurrentWrite, ClientRandom)}}. + + +overwrite_client_random(ConnectionState = #{security_parameters := SecurityParameters0}, ClientRandom) -> + SecurityParameters = SecurityParameters0#security_parameters{client_random = ClientRandom}, + ConnectionState#{security_parameters => SecurityParameters}. + + +maybe_store_handshake_traffic_secret(#state{connection_states = + #{pending_read := PendingRead} = CS, + ssl_options = #{keep_secrets := true}} = State, + ClientHSTrafficSecret, ServerHSTrafficSecret) -> + PendingRead1 = store_handshake_traffic_secret(PendingRead, ClientHSTrafficSecret, ServerHSTrafficSecret), + State#state{connection_states = CS#{pending_read => PendingRead1}}; +maybe_store_handshake_traffic_secret(State, _, _) -> + State. + +store_handshake_traffic_secret(ConnectionState, ClientHSTrafficSecret, ServerHSTrafficSecret) -> + ConnectionState#{client_handshake_traffic_secret => ClientHSTrafficSecret, + server_handshake_traffic_secret => ServerHSTrafficSecret}. + + update_pending_connection_states(#state{ static_env = #static_env{role = server}, connection_states = @@ -1582,8 +1922,9 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam master_secret = HandshakeSecret, resumption_master_secret = ResumptionMasterSecret, application_traffic_secret = ApplicationTrafficSecret}, + BulkCipherAlgo = SecurityParameters#security_parameters.bulk_cipher_algorithm, ConnectionState#{security_parameters => SecurityParameters, - cipher_state => cipher_init(Key, IV, FinishedKey)}. + cipher_state => cipher_init(BulkCipherAlgo, Key, IV, FinishedKey)}. update_start_state(State, Map) -> @@ -1639,7 +1980,12 @@ update_resumption_master_secret(#state{connection_states = ConnectionStates0} = State#state{connection_states = ConnectionStates}. -cipher_init(Key, IV, FinishedKey) -> +cipher_init(?AES_CCM_8, Key, IV, FinishedKey) -> + #cipher_state{key = Key, + iv = IV, + finished_key = FinishedKey, + tag_len = 8}; +cipher_init(_BulkCipherAlgo, Key, IV, FinishedKey) -> #cipher_state{key = Key, iv = IV, finished_key = FinishedKey, @@ -1745,13 +2091,12 @@ maybe_update_selected_sign_alg(State, _, _) -> State. -verify_certificate_verify(#state{ - static_env = #static_env{role = Role}, - connection_states = ConnectionStates, - handshake_env = - #handshake_env{ - public_key_info = PublicKeyInfo, - tls_handshake_history = HHistory}} = State0, +verify_certificate_verify(#state{static_env = #static_env{role = Role}, + connection_states = ConnectionStates, + handshake_env = + #handshake_env{ + public_key_info = PublicKeyInfo, + tls_handshake_history = HHistory}} = State0, #certificate_verify_1_3{ algorithm = SignatureScheme, signature = Signature}) -> @@ -1759,7 +2104,7 @@ verify_certificate_verify(#state{ ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, - {HashAlgo, _, _} = + {HashAlgo, SignAlg, _} = ssl_cipher:scheme_to_components(SignatureScheme), Messages = get_handshake_context_cv(HHistory), @@ -1773,7 +2118,7 @@ verify_certificate_verify(#state{ %% Digital signatures use the hash function defined by the selected signature %% scheme. - case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of + case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of {ok, true} -> {ok, {State0, wait_finished}}; {ok, false} -> @@ -1935,7 +2280,7 @@ select_cipher_suite(_, [], _) -> select_cipher_suite(true, ClientCiphers, ServerCiphers) -> select_cipher_suite(false, ServerCiphers, ClientCiphers); select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) -> - case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso + case lists:member(Cipher, tls_v1:exclusive_suites(4)) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; @@ -1977,12 +2322,12 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) -> %% DSA keys are not supported by TLS 1.3 -select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) -> +select_sign_algo(dsa, _RSAKeySize, _PeerSignAlgs, _OwnSignAlgs) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)}; -select_sign_algo(_, [], _) -> +select_sign_algo(_, _RSAKeySize, [], _) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)}; -select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> - {_, S, _} = ssl_cipher:scheme_to_components(C), +select_sign_algo(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs) -> + {_, S, _} = ssl_cipher:scheme_to_components(PeerSignAlg), %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed %% TLS handshake messages: filter sha-1 and rsa_pkcs1. %% @@ -1991,16 +2336,48 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> %% RSASSA-PSS PSS algorithms: If the public key is carried in an X.509 certificate, %% it MUST use the RSASSA-PSS OID. case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae) - orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss) + orelse (PublicKeyAlgo =:= rsa_pss_pss andalso S =:= rsa_pss_pss) orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa)) andalso - lists:member(C, ServerSignAlgs) of + lists:member(PeerSignAlg, OwnSignAlgs) of true -> - {ok, C}; + validate_key_compatibility(PublicKeyAlgo, RSAKeySize, + [PeerSignAlg|PeerSignAlgs], OwnSignAlgs); false -> - select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs) + select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs) end. +validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs) + when PublicKeyAlgo =:= rsa orelse + PublicKeyAlgo =:= rsa_pss_pss -> + case is_rsa_key_compatible(RSAKeySize, PeerSignAlg) of + true -> + {ok, PeerSignAlg}; + false -> + select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs) + end; +validate_key_compatibility(_, _, [PeerSignAlg|_], _) -> + {ok, PeerSignAlg}. + +is_rsa_key_compatible(KeySize, SigAlg) -> + {Hash, _, _} = ssl_cipher:scheme_to_components(SigAlg), + HashSize = ssl_cipher:hash_size(Hash), + + %% OpenSSL crypto lib defines a limit on the size of the random salt + %% in PSS signatures based on the size of signing RSA key. + %% If the limit is unchecked, it causes handshake failures when the + %% configured certificates contain short (e.g. 1024-bit) RSA keys. + %% For more information see the OpenSSL crypto library + %% (rsa_pss:c{77,86}). + %% TODO: Move this check into crypto. Investigate if this is a bug in + %% OpenSSL crypto lib. + if (KeySize < (HashSize + 2)) -> + false; + (HashSize > (KeySize - HashSize - 2)) -> + false; + true -> + true + end. do_check_cert_sign_algo(_, _, []) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)}; @@ -2015,10 +2392,8 @@ do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) -> %% id-RSASSA-PSS (rsa_pss) indicates that the key may only be used for PSS signatures. -%% TODO: Uncomment when rsa_pss signatures are supported in certificates -%% compare_sign_algos(rsa_pss, Hash, Algo, Hash) -%% when Algo =:= rsa_pss_pss -> -%% true; +compare_sign_algos(rsa_pss_pss, Hash, rsa_pss_pss, Hash) -> + true; %% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or %% signature schemes. compare_sign_algos(rsa, Hash, Algo, Hash) @@ -2030,23 +2405,28 @@ compare_sign_algos(Algo, Hash, Algo, Hash) -> compare_sign_algos(_, _, _, _) -> false. - get_certificate_params(Cert) -> - {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert), - {SignHash0, SignAlgo} = public_key:pkix_sign_types(SignAlgo0), - %% Convert hash to new format - SignHash = case SignHash0 of - sha -> - sha1; - H -> H - end, - PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), - {PublicKeyAlgo, SignAlgo, SignHash}. - - + {SignAlgo0, Param, SubjectPublicKeyAlgo0, RSAKeySize} = + ssl_handshake:get_cert_params(Cert), + {SignHash, SignAlgo} = oids_to_atoms(SignAlgo0, Param), + SubjectPublicKeyAlgo = public_key_algo(SubjectPublicKeyAlgo0), + {SubjectPublicKeyAlgo, SignAlgo, SignHash, RSAKeySize}. + +oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm = + #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = #'HashAlgorithm'{algorithm = HashOid}}}) -> + Hash = public_key:pkix_hash_type(HashOid), + {Hash, rsa_pss_pss}; +oids_to_atoms(SignAlgo, _) -> + case public_key:pkix_sign_types(SignAlgo) of + {sha, Sign} -> + {sha1, Sign}; + {_,_} = Algs -> + Algs + end. %% Note: copied from ssl_handshake public_key_algo(?'id-RSASSA-PSS') -> - rsa_pss; + rsa_pss_pss; public_key_algo(?rsaEncryption) -> rsa; public_key_algo(?'id-ecPublicKey') -> @@ -2065,14 +2445,21 @@ get_signature_scheme_list(#signature_algorithms{ lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end, ClientSignatureSchemes). +get_supported_groups(undefined = Groups) -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})}; get_supported_groups(#supported_groups{supported_groups = Groups}) -> - Groups. + {ok, Groups}. get_key_shares(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; get_key_shares(#key_share_server_hello{server_share = ServerShare}) -> ServerShare. +get_cookie(undefined) -> + undefined; +get_cookie(#cookie{cookie = Cookie}) -> + Cookie. + get_selected_identity(undefined) -> undefined; get_selected_identity(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) -> @@ -2174,18 +2561,18 @@ maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, Version) when Ve maybe_add_binders(Hello, _, _, Version) when Version =< {3,3} -> Hello. - create_binders(Context, TicketData) -> create_binders(Context, TicketData, []). %% create_binders(_, [], Acc) -> lists:reverse(Acc); -create_binders(Context, [{_, _, _, PSK, _, HKDF}|T], Acc) -> +create_binders(Context, [#ticket_data{ + psk = PSK, + cipher_suite = {_, HKDF}}|T], Acc) -> FinishedKey = calculate_finished_key(PSK, HKDF), Binder = calculate_binder(FinishedKey, HKDF, Context), create_binders(Context, T, [Binder|Acc]). - %% Removes the binders list from the ClientHello. %% opaque PskBinderEntry<32..255>; %% @@ -2219,6 +2606,18 @@ truncate_client_hello(HelloBin0) -> {Truncated, _} = split_binary(HelloBin0, size(HelloBin0) - BindersSize - 2), Truncated. +maybe_add_early_data_indication(#client_hello{ + extensions = Extensions0} = ClientHello, + EarlyData, + Version) + when Version =:= {3,4} andalso + is_binary(EarlyData) andalso + size(EarlyData) > 0 -> + Extensions = Extensions0#{early_data => + #early_data_indication{}}, + ClientHello#client_hello{extensions = Extensions}; +maybe_add_early_data_indication(ClientHello, _, _) -> + ClientHello. %% The PskBinderEntry is computed in the same way as the Finished %% message (Section 4.4.4) but with the BaseKey being the binder_key @@ -2253,13 +2652,21 @@ update_binders(#client_hello{extensions = maybe_automatic_session_resumption(#state{ ssl_options = #{versions := [Version|_], ciphers := UserSuites, - session_tickets := SessionTickets} = SslOpts0 + early_data := EarlyData, + session_tickets := SessionTickets, + server_name_indication := SNI} = SslOpts0 } = State0) when Version >= {3,4} andalso SessionTickets =:= auto -> AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), HashAlgos = cipher_hash_algos(AvailableCipherSuites), - UseTicket = tls_client_ticket_store:find_ticket(self(), HashAlgos), + Ciphers = ciphers_for_early_data(AvailableCipherSuites), + %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where Ticket0 satisfies + %% requirements for early_data and session resumption while Ticket2 can only + %% be used for session resumption. + EarlyDataSize = early_data_size(EarlyData), + KeyPair = tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos, SNI, EarlyDataSize), + UseTicket = choose_ticket(KeyPair, EarlyData), tls_client_ticket_store:lock_tickets(self(), [UseTicket]), State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}}, {[UseTicket], State}; @@ -2268,6 +2675,144 @@ maybe_automatic_session_resumption(#state{ } = State) -> {UseTicket, State}. +early_data_size(undefined) -> + undefined; +early_data_size(EarlyData) when is_binary(EarlyData) -> + byte_size(EarlyData). + +%% Choose a ticket based on the intention of the user. The first argument is +%% a 2-tuple of ticket keys where the first element refers to a ticket that +%% fulfills all criteria for sending early_data (hash, cipher, early data size). +%% Second argument refers to a ticket that can only be used for session +%% resumption. +choose_ticket({Key, _}, _) when Key =/= undefined -> + Key; +choose_ticket({_, Key}, EarlyData) when EarlyData =:= undefined -> + Key; +choose_ticket(_, _) -> + %% No tickets found that fulfills the original intention of the user + %% (sending early_data). It is possible to do session resumption but + %% in that case the configured early data would have to be removed + %% and that would contradict the will of the user. Returning undefined + %% here prevents session resumption instead. + undefined. + +maybe_send_early_data(#state{ + handshake_env = #handshake_env{tls_handshake_history = {Hist, _}}, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + session_tickets := SessionTickets, + early_data := EarlyData} = _SslOpts0 + } = State0) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined -> + %% D.4. Middlebox Compatibility Mode + State1 = maybe_queue_change_cipher_spec(State0, last), + %% Early traffic secret + EarlyDataSize = early_data_size(EarlyData), + case get_pre_shared_key_early_data(SessionTickets, UseTicket) of + {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize -> + State2 = calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State1), + %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData + State3 = ssl_record:step_encryption_state_write(State2), + {ok, encode_early_data(Cipher, State3)}; + {ok, {_, _, _, _MaxSize}} -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, too_much_early_data)}; + {error, Alert} -> + {error, Alert} + end; +maybe_send_early_data(State) -> + {ok, State}. + +encode_early_data(Cipher, + #state{ + flight_buffer = Flight0, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + early_data := EarlyData} = _SslOpts0 + } = State0) -> + #state{connection_states = + #{current_write := + #{security_parameters := SecurityParameters0} = Write0} = ConnectionStates0} = State0, + BulkCipherAlgo = ssl_cipher:bulk_cipher_algorithm(Cipher), + SecurityParameters = SecurityParameters0#security_parameters{ + cipher_type = ?AEAD, + bulk_cipher_algorithm = BulkCipherAlgo}, + Write = Write0#{security_parameters => SecurityParameters}, + ConnectionStates1 = ConnectionStates0#{current_write => Write}, + {BinEarlyData, ConnectionStates} = tls_record:encode_data([EarlyData], Version, ConnectionStates1), + State0#state{connection_states = ConnectionStates, + flight_buffer = Flight0 ++ [BinEarlyData]}. + +maybe_send_end_of_early_data( + #state{ + handshake_env = #handshake_env{early_data_accepted = true}, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + early_data := EarlyData}, + static_env = #static_env{protocol_cb = Connection} + } = State0) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined -> + %% EndOfEarlydata is encrypted with the 0-RTT traffic keys + State1 = Connection:queue_handshake(#end_of_early_data{}, State0), + %% Use handshake keys after EndOfEarlyData is sent + ssl_record:step_encryption_state_write(State1); +maybe_send_end_of_early_data(State) -> + State. + +maybe_check_early_data_indication(EarlyDataIndication, + #state{ + handshake_env = HsEnv, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + early_data := EarlyData} + } = State) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined andalso + EarlyDataIndication =/= undefined -> + signal_user_early_data(State, accepted), + State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}}; +maybe_check_early_data_indication(EarlyDataIndication, + #state{ + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + early_data := EarlyData} = _SslOpts0 + } = State) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined andalso + EarlyDataIndication =:= undefined -> + signal_user_early_data(State, rejected), + %% Use handshake keys if early_data is rejected. + ssl_record:step_encryption_state_write(State); +maybe_check_early_data_indication(_, State) -> + %% Use handshake keys if there is no early_data. + ssl_record:step_encryption_state_write(State). + +signal_user_early_data(#state{ + connection_env = + #connection_env{ + user_application = {_, User}}, + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + trackers = Trackers}} = State, + Result) -> + CPids = Connection:pids(State), + SslSocket = Connection:socket(CPids, Transport, Socket, Trackers), + User ! {ssl, SslSocket, {early_data, Result}}. + +handle_early_data(State, enabled, #early_data_indication{}) -> + %% Accept early data + HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true}, + State#state{handshake_env = HsEnv}; +handle_early_data(State, _, _) -> + State. cipher_hash_algos(Ciphers) -> Fun = fun(Cipher) -> @@ -2276,6 +2821,14 @@ cipher_hash_algos(Ciphers) -> end, lists:map(Fun, Ciphers). +ciphers_for_early_data(CipherSuites0) -> + %% Use only supported TLS 1.3 cipher suites + Supported = lists:filter(fun(CipherSuite) -> + lists:member(CipherSuite, tls_v1:exclusive_suites(4)) end, + CipherSuites0), + %% Return supported block cipher algorithms + lists:map(fun(#{cipher := Cipher}) -> Cipher end, + lists:map(fun ssl_cipher_format:suite_bin_to_map/1, Supported)). get_ticket_data(_, undefined, _) -> undefined; @@ -2293,25 +2846,51 @@ process_user_tickets(UseTicket) -> process_user_tickets([], Acc, _) -> lists:reverse(Acc); process_user_tickets([H|T], Acc, N) -> - #{hkdf := HKDF, - sni := _SNI, - psk := PSK, - timestamp := Timestamp, - ticket := NewSessionTicket} = erlang:binary_to_term(H), + case process_ticket(H, N) of + error -> + process_user_tickets(T, Acc, N + 1); + TicketData -> + process_user_tickets(T, [TicketData|Acc], N + 1) + end. + +%% Used when session_tickets = manual +process_ticket(#{cipher_suite := CipherSuite, + sni := _SNI, %% TODO user's responsibility to handle SNI? + psk := PSK, + timestamp := Timestamp, + ticket := NewSessionTicket}, N) -> #new_session_ticket{ ticket_lifetime = _LifeTime, ticket_age_add = AgeAdd, ticket_nonce = Nonce, ticket = Ticket, - extensions = _Extensions + extensions = Extensions } = NewSessionTicket, TicketAge = erlang:system_time(seconds) - Timestamp, ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd), Identity = #psk_identity{ identity = Ticket, obfuscated_ticket_age = ObfuscatedTicketAge}, - process_user_tickets(T, [{undefined, N, Identity, PSK, Nonce, HKDF}|Acc], N + 1). - + MaxEarlyData = get_max_early_data(Extensions), + #ticket_data{ + key = undefined, + pos = N, + identity = Identity, + psk = PSK, + nonce = Nonce, + cipher_suite = CipherSuite, + max_size = MaxEarlyData}; +process_ticket(_, _) -> + error. + +get_max_early_data(Extensions) -> + EarlyDataIndication = maps:get(early_data, Extensions, undefined), + case EarlyDataIndication of + undefined -> + undefined; + #early_data_indication_nst{indication = MaxSize} -> + MaxSize + end. %% The "obfuscated_ticket_age" %% field of each PskIdentity contains an obfuscated version of the @@ -2320,3 +2899,53 @@ process_user_tickets([H|T], Acc, N) -> %% (see Section 4.6.1), modulo 2^32. obfuscate_ticket_age(TicketAge, AgeAdd) -> (TicketAge + AgeAdd) rem round(math:pow(2,32)). + +path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, + CRLDbHandle, Version, SslOptions, CertExt); +path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + case path_validation(TrustedCert, Path, ServerName, + Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) of + {ok, _} = Result -> + Result; + {error, _} -> + path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) + end. + +path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, + #{verify_fun := VerifyFun, + customize_hostname_check := CustomizeHostnameCheck, + crl_check := CrlCheck, + log_level := LogLevel, + signature_algs := SignAlgos, + signature_algs_cert := SignAlgosCert, + depth := Depth}, + #{cert_ext := CertExt, + ocsp_responder_certs := OcspResponderCerts, + ocsp_state := OcspState}) -> + ValidationFunAndState = + ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role, + certdb => CertDbHandle, + certdb_ref => CertDbRef, + server_name => ServerName, + customize_hostname_check => + CustomizeHostnameCheck, + crl_check => CrlCheck, + crl_db => CRLDbHandle, + signature_algs => filter_tls13_algs(SignAlgos), + signature_algs_cert => + filter_tls13_algs(SignAlgosCert), + version => Version, + issuer => TrustedCert, + cert_ext => CertExt, + ocsp_responder_certs => OcspResponderCerts, + ocsp_state => OcspState + }, + Path, LogLevel), + Options = [{max_path_length, Depth}, + {verify_fun, ValidationFunAndState}], + public_key:pkix_path_validation(TrustedCert, Path, Options). diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 9f41c84708..d506821f6c 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -87,9 +87,11 @@ %% RFC 8446 4.2.10. Early Data Indication -record(empty, { }). --record(early_data_indication, { - indication % uint32 max_early_data_size (new_session_ticket) | - %% #empty{} (client_hello, encrypted_extensions) + +%% #empty{} (client_hello, encrypted_extensions) +-record(early_data_indication, {}). +-record(early_data_indication_nst, { + indication % uint32 max_early_data_size (new_session_ticket) }). %% RFC 8446 4.2.11. Pre-Shared Key Extension diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index dc70cb2757..9ec5490aa6 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -33,12 +33,14 @@ -include_lib("kernel/include/logger.hrl"). %% Handling of incoming data --export([get_tls_records/4, init_connection_states/2]). +-export([get_tls_records/5, + init_connection_states/2, + init_connection_states/3]). %% Encoding TLS records -export([encode_handshake/3, encode_alert_record/3, encode_change_cipher_spec/2, encode_data/3]). --export([encode_plain_text/4, split_iovec/1]). +-export([encode_plain_text/4, split_iovec/2]). %% Decoding -export([decode_cipher_text/4]). @@ -49,13 +51,14 @@ %% Protocol version handling -export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2, highest_protocol_version/1, highest_protocol_version/2, - is_higher/2, supported_protocol_versions/0, + is_higher/2, supported_protocol_versions/0, sufficient_crypto_support/1, is_acceptable_version/1, is_acceptable_version/2, hello_version/1]). -export_type([tls_version/0, tls_atom_version/0]). -type tls_version() :: ssl_record:ssl_version(). --type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'. +-type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2' | 'tlsv1.3'. +-type tls_max_frag_len() :: undefined | 512 | 1024 | 2048 | 4096. -compile(inline). @@ -63,16 +66,29 @@ %% Handling of incoming data %%==================================================================== %%-------------------------------------------------------------------- --spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) -> - ssl_record:connection_states(). +-spec init_connection_states(Role, BeastMitigation) -> + ssl_record:connection_states() when + Role :: client | server, + BeastMitigation :: one_n_minus_one | zero_n | disabled. + %% %% Description: Creates a connection_states record with appropriate %% values for the initial SSL connection setup. %%-------------------------------------------------------------------- init_connection_states(Role, BeastMitigation) -> + MaxEarlyDataSize = ssl_config:get_max_early_data_size(), + init_connection_states(Role, BeastMitigation, MaxEarlyDataSize). +%% +-spec init_connection_states(Role, BeastMitigation, MaxEarlyDataSize) -> + ssl_record:connection_states() when + Role :: client | server, + BeastMitigation :: one_n_minus_one | zero_n | disabled, + MaxEarlyDataSize :: non_neg_integer(). + +init_connection_states(Role, BeastMitigation, MaxEarlyDataSize) -> ConnectionEnd = ssl_record:record_protocol_role(Role), - Current = initial_connection_state(ConnectionEnd, BeastMitigation), - Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + Current = initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize), + Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize), #{current_read => Current, pending_read => Pending, current_write => Current, @@ -83,6 +99,7 @@ init_connection_states(Role, BeastMitigation) -> binary(), [tls_version()] | tls_version(), Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}, + tls_max_frag_len(), ssl_options()) -> {Records :: [#ssl_tls{}], Buffer :: {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}} | @@ -92,10 +109,10 @@ init_connection_states(Role, BeastMitigation) -> %% Description: Given old buffer and new data from TCP, packs up a records %% data %%-------------------------------------------------------------------- -get_tls_records(Data, Versions, Buffer, SslOpts) when is_binary(Buffer) -> - parse_tls_records(Versions, {[Data],byte_size(Data),[]}, SslOpts, undefined); -get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, SslOpts) -> - parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, SslOpts, Hdr). +get_tls_records(Data, Versions, Buffer, MaxFragLen, SslOpts) when is_binary(Buffer) -> + parse_tls_records(Versions, {[Data],byte_size(Data),[]}, MaxFragLen, SslOpts, undefined); +get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, SslOpts) -> + parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, MaxFragLen, SslOpts, Hdr). %%==================================================================== %% Encoding @@ -112,12 +129,18 @@ encode_handshake(Frag, {3, 4}, ConnectionStates) -> encode_handshake(Frag, Version, #{current_write := #{beast_mitigation := BeastMitigation, + max_fragment_length := MaxFragmentLength, security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, case iolist_size(Frag) of - N when N > ?MAX_PLAIN_TEXT_LENGTH -> - Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation), + N when N > MaxLength -> + Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation, MaxLength), encode_fragments(?HANDSHAKE, Version, Data, ConnectionStates); _ -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates) @@ -155,10 +178,16 @@ encode_data(Data, {3, 4}, ConnectionStates) -> tls_record_1_3:encode_data(Data, ConnectionStates); encode_data(Data, Version, #{current_write := #{beast_mitigation := BeastMitigation, + max_fragment_length := MaxFragmentLength, security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> - Fragments = split_iovec(Data, Version, BCA, BeastMitigation), + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, + Fragments = split_iovec(Data, Version, BCA, BeastMitigation, MaxLength), encode_fragments(?APPLICATION_DATA, Version, Fragments, ConnectionStates). %%==================================================================== @@ -167,7 +196,8 @@ encode_data(Data, Version, %%-------------------------------------------------------------------- -spec decode_cipher_text(tls_version(), #ssl_tls{}, ssl_record:connection_states(), boolean()) -> - {#ssl_tls{}, ssl_record:connection_states()}| #alert{}. + {#ssl_tls{} | trial_decryption_failed, + ssl_record:connection_states()}| #alert{}. %% %% Description: Decode cipher text %%-------------------------------------------------------------------- @@ -353,27 +383,74 @@ supported_protocol_versions() -> end. supported_protocol_versions([]) -> - Vsns = case sufficient_tlsv1_2_crypto_support() of - true -> - ?ALL_SUPPORTED_VERSIONS; - false -> - ?MIN_SUPPORTED_VERSIONS - end, + Vsns = sufficient_support(?ALL_SUPPORTED_VERSIONS), application:set_env(ssl, protocol_version, Vsns), Vsns; supported_protocol_versions([_|_] = Vsns) -> - case sufficient_tlsv1_2_crypto_support() of - true -> - Vsns; - false -> - case Vsns -- ['tlsv1.2'] of - [] -> - ?MIN_SUPPORTED_VERSIONS; - NewVsns -> - NewVsns - end - end. + sufficient_support(Vsns). + +sufficient_crypto_support(Version) -> + sufficient_crypto_support(crypto:supports(), Version). + +sufficient_crypto_support(CryptoSupport, {_,_} = Version) -> + sufficient_crypto_support(CryptoSupport, protocol_version(Version)); +sufficient_crypto_support(CryptoSupport, Version) when Version == 'tlsv1'; + Version == 'tlsv1.1' -> + Hashes = proplists:get_value(hashs, CryptoSupport), + PKeys = proplists:get_value(public_keys, CryptoSupport), + proplists:get_bool(sha, Hashes) + andalso + proplists:get_bool(md5, Hashes) + andalso + proplists:get_bool(aes_cbc, proplists:get_value(ciphers, CryptoSupport)) + andalso + (proplists:get_bool(ecdsa, PKeys) orelse proplists:get_bool(rsa, PKeys) orelse proplists:get_bool(dss, PKeys)) + andalso + (proplists:get_bool(ecdh, PKeys) orelse proplists:get_bool(dh, PKeys)); + +sufficient_crypto_support(CryptoSupport, 'tlsv1.2') -> + PKeys = proplists:get_value(public_keys, CryptoSupport), + (proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport))) + andalso + (proplists:get_bool(aes_cbc, proplists:get_value(ciphers, CryptoSupport))) + andalso + (proplists:get_bool(ecdsa, PKeys) orelse proplists:get_bool(rsa, PKeys) orelse proplists:get_bool(dss, PKeys)) + andalso + (proplists:get_bool(ecdh, PKeys) orelse proplists:get_bool(dh, PKeys)); + +%% A TLS-compliant application MUST implement the TLS_AES_128_GCM_SHA256 +%% [GCM] cipher suite and SHOULD implement the TLS_AES_256_GCM_SHA384 +%% [GCM] and TLS_CHACHA20_POLY1305_SHA256 [RFC8439] cipher suites (see +%% Appendix B.4). +%% +%% A TLS-compliant application MUST support digital signatures with +%% rsa_pkcs1_sha256 (for certificates), rsa_pss_rsae_sha256 (for +%% CertificateVerify and certificates), and ecdsa_secp256r1_sha256. A +%% TLS-compliant application MUST support key exchange with secp256r1 +%% (NIST P-256) and SHOULD support key exchange with X25519 [RFC7748]. +sufficient_crypto_support(CryptoSupport, 'tlsv1.3') -> + Fun = fun({Group, Algorithm}) -> + is_algorithm_supported(CryptoSupport, Group, Algorithm) + end, + L = [{ciphers, aes_gcm}, %% TLS_AES_*_GCM_* + {ciphers, chacha20_poly1305}, %% TLS_CHACHA20_POLY1305_SHA256 + {hashs, sha256}, %% TLS_AES_128_GCM_SHA256 + {hashs, sha384}, %% TLS_AES_256_GCM_SHA384 + {rsa_opts, rsa_pkcs1_padding}, %% rsa_pkcs1_sha256 + {rsa_opts, rsa_pkcs1_pss_padding}, %% rsa_pss_rsae_* + {rsa_opts, rsa_pss_saltlen}, %% rsa_pss_rsae_* + {public_keys, ecdh}, + {public_keys, dh}, + {public_keys, rsa}, + {public_keys, ecdsa}, + %% {public_keys, eddsa}, %% TODO + {curves, secp256r1}, %% key exchange with secp256r1 + {curves, x25519}], %% key exchange with X25519 + lists:all(Fun, L). + +is_algorithm_supported(CryptoSupport, Group, Algorithm) -> + proplists:get_bool(Algorithm, proplists:get_value(Group, CryptoSupport)). -spec is_acceptable_version(tls_version()) -> boolean(). is_acceptable_version({N,_}) @@ -395,16 +472,16 @@ hello_version([Highest|_]) when Highest >= {3,3} -> hello_version(Versions) -> lowest_protocol_version(Versions). -split_iovec([]) -> +split_iovec([], _) -> []; -split_iovec(Data) -> - {Part,Rest} = split_iovec(Data, ?MAX_PLAIN_TEXT_LENGTH, []), - [Part|split_iovec(Rest)]. +split_iovec(Data, MaximumFragmentLength) -> + {Part,Rest} = split_iovec(Data, MaximumFragmentLength, []), + [Part|split_iovec(Rest, MaximumFragmentLength)]. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -initial_connection_state(ConnectionEnd, BeastMitigation) -> +initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) -> #{security_parameters => ssl_record:initial_security_params(ConnectionEnd), sequence_number => 0, @@ -414,7 +491,11 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> mac_secret => undefined, secure_renegotiation => undefined, client_verify_data => undefined, - server_verify_data => undefined + server_verify_data => undefined, + max_early_data_size => MaxEarlyDataSize, + max_fragment_length => undefined, + trial_decryption => false, + early_data_limit => false }. %% Used by logging to recreate the received bytes @@ -423,88 +504,92 @@ build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fr <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),?UINT16(Length), Fragment/binary>>. -parse_tls_records(Versions, Q, SslOpts, undefined) -> - decode_tls_records(Versions, Q, SslOpts, [], undefined, undefined, undefined); -parse_tls_records(Versions, Q, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) -> - decode_tls_records(Versions, Q, SslOpts, [], Type, Version, Length). +parse_tls_records(Versions, Q, MaxFragLen, SslOpts, undefined) -> + decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], undefined, undefined, undefined); +parse_tls_records(Versions, Q, MaxFragLen, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) -> + decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], Type, Version, Length). %% Generic code path -decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, undefined, _Version, _Length) -> +decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, undefined, _Version, _Length) -> if 5 =< Size -> {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0), - validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length); + validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length); 3 =< Size -> {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0), - validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); + validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); 1 =< Size -> {<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0), - validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, undefined, undefined); + validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, undefined, undefined); true -> - validate_tls_records_type(Versions, Q0, SslOpts, Acc, undefined, undefined, undefined) + validate_tls_records_type(Versions, Q0, MaxFragLen, SslOpts, Acc, undefined, undefined, undefined) end; -decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, undefined, _Length) -> +decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, undefined, _Length) -> if 4 =< Size -> {<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0), - validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length); + validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length); 2 =< Size -> {<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0), - validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); + validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); true -> - validate_tls_record_version(Versions, Q0, SslOpts, Acc, Type, undefined, undefined) + validate_tls_record_version(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, undefined, undefined) end; -decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, Version, undefined) -> +decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined) -> if 2 =< Size -> {<<?UINT16(Length)>>, Q} = binary_from_front(2, Q0), - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); true -> - validate_tls_record_length(Versions, Q0, SslOpts, Acc, Type, Version, undefined) + validate_tls_record_length(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined) end; -decode_tls_records(Versions, Q, SslOpts, Acc, Type, Version, Length) -> - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length). +decode_tls_records(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) -> + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length). -validate_tls_records_type(_Versions, Q, _SslOpts, Acc, undefined, _Version, _Length) -> +validate_tls_records_type(_Versions, Q, _MaxFragLen, _SslOpts, Acc, undefined, _Version, _Length) -> {lists:reverse(Acc), {undefined, Q}}; -validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, Version, Length) -> +validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) -> if ?KNOWN_RECORD_TYPE(Type) -> - validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); true -> %% Not ?KNOWN_RECORD_TYPE(Type) - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type}) end. -validate_tls_record_version(_Versions, Q, _SslOpts, Acc, Type, undefined, _Length) -> +validate_tls_record_version(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, undefined, _Length) -> {lists:reverse(Acc), {#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}}; -validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length) -> +validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) -> case Versions of _ when is_list(Versions) -> case is_acceptable_version(Version, Versions) of true -> - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version}) end; {3, 4} when Version =:= {3, 3} -> - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); Version -> %% Exact version match - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); _ -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version}) end. -validate_tls_record_length(_Versions, Q, _SslOpts, Acc, Type, Version, undefined) -> +validate_tls_record_length(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, Version, undefined) -> {lists:reverse(Acc), {#ssl_tls{type = Type, version = Version, fragment = undefined}, Q}}; -validate_tls_record_length(Versions, {_,Size0,_} = Q0, +validate_tls_record_length(Versions, {_,Size0,_} = Q0, MaxFragLen, #{log_level := LogLevel} = SslOpts, Acc, Type, Version, Length) -> - Max = max_len(Versions), + Max = if is_integer(MaxFragLen) -> + MaxFragLen + ?MAX_PADDING_LENGTH + ?MAX_MAC_LENGTH; + true -> + max_len(Versions) + end, if Length =< Max -> if @@ -513,7 +598,7 @@ validate_tls_record_length(Versions, {_,Size0,_} = Q0, {Fragment, Q} = binary_from_front(Length, Q0), Record = #ssl_tls{type = Type, version = Version, fragment = Fragment}, ssl_logger:debug(LogLevel, inbound, 'record', Record), - decode_tls_records(Versions, Q, SslOpts, [Record|Acc], undefined, undefined, undefined); + decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [Record|Acc], undefined, undefined, undefined); true -> {lists:reverse(Acc), {#ssl_tls{type = Type, version = Version, fragment = Length}, Q0}} @@ -620,22 +705,22 @@ encode_fragments(_Type, _Version, _Data, CS, _CompS, _CipherS, _Seq, _CipherFrag exit({cs, CS}). %%-------------------------------------------------------------------- -%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are +%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 ciphers are %% not vulnerable to this attack. -split_iovec(Data, Version, BCA, one_n_minus_one) +split_iovec(Data, Version, BCA, one_n_minus_one, MaxLength) when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse {3, 0} == Version) -> {Part, RestData} = split_iovec(Data, 1, []), - [Part|split_iovec(RestData)]; + [Part|split_iovec(RestData, MaxLength)]; %% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 %% splitting. -split_iovec(Data, Version, BCA, zero_n) +split_iovec(Data, Version, BCA, zero_n, MaxLength) when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse {3, 0} == Version) -> {Part, RestData} = split_iovec(Data, 0, []), - [Part|split_iovec(RestData)]; -split_iovec(Data, _Version, _BCA, _BeatMitigation) -> - split_iovec(Data). + [Part|split_iovec(RestData, MaxLength)]; +split_iovec(Data, _Version, _BCA, _BeatMitigation, MaxLength) -> + split_iovec(Data, MaxLength). split_iovec([Bin|Data] = Bin_Data, SplitSize, Acc) -> BinSize = byte_size(Bin), @@ -668,11 +753,12 @@ highest_protocol_version() -> lowest_protocol_version() -> lowest_protocol_version(supported_protocol_versions()). -sufficient_tlsv1_2_crypto_support() -> - CryptoSupport = crypto:supports(), - proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)). - max_len([{3,4}|_])-> ?TLS13_MAX_CIPHER_TEXT_LENGTH; max_len(_) -> ?MAX_CIPHER_TEXT_LENGTH. + +sufficient_support(Versions) -> + CryptoSupport = crypto:supports(), + [Ver || Ver <- Versions, sufficient_crypto_support(CryptoSupport, Ver)]. + diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index 89f2a484ff..3e42e3bf97 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -43,11 +43,17 @@ % %% Description: Encodes a handshake message to send on the tls-1.3-socket. %%-------------------------------------------------------------------- -encode_handshake(Frag, ConnectionStates) -> +encode_handshake(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} = + ConnectionStates) -> + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + %% TODO: Consider padding here + ?MAX_PLAIN_TEXT_LENGTH + end, case iolist_size(Frag) of - N when N > ?MAX_PLAIN_TEXT_LENGTH -> - %% TODO: Consider padding here - Data = tls_record:split_iovec(Frag), + N when N > MaxLength -> + Data = tls_record:split_iovec(erlang:iolist_to_iovec(Frag), MaxLength), encode_iolist(?HANDSHAKE, Data, ConnectionStates); _ -> encode_plain_text(?HANDSHAKE, Frag, ConnectionStates) @@ -69,8 +75,14 @@ encode_alert_record(#alert{level = Level, description = Description}, %% %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- -encode_data(Frag, ConnectionStates) -> - Data = tls_record:split_iovec(Frag), +encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} = + ConnectionStates) -> + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, + Data = tls_record:split_iovec(Frag, MaxLength), encode_iolist(?APPLICATION_DATA, Data, ConnectionStates). encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) -> @@ -95,7 +107,8 @@ encode_iolist(Type, Data, ConnectionStates0) -> %%-------------------------------------------------------------------- -spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states()) -> - {#ssl_tls{}, ssl_record:connection_states()}| #alert{}. + {#ssl_tls{} | trial_decryption_failed, + ssl_record:connection_states()}| #alert{}. %% %% Description: Decode cipher text, use legacy type ssl_tls instead of tls_cipher_text %% in decoding context so that we can reuse the code from erlier versions. @@ -112,12 +125,25 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE, #security_parameters{ cipher_type = ?AEAD, bulk_cipher_algorithm = - BulkCipherAlgo} + BulkCipherAlgo}, + max_early_data_size := MaxEarlyDataSize0, + trial_decryption := TrialDecryption, + early_data_limit := EarlyDataLimit } = ReadState0} = ConnectionStates0) -> case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of + #alert{} when TrialDecryption =:= true andalso + MaxEarlyDataSize0 > 0 -> %% Trial decryption + trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0, + BulkCipherAlgo, CipherFragment); #alert{} = Alert -> Alert; - PlainFragment -> + PlainFragment0 when EarlyDataLimit =:= true andalso + MaxEarlyDataSize0 > 0 -> + PlainFragment = remove_padding(PlainFragment0), + process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq, + BulkCipherAlgo, CipherFragment, PlainFragment); + PlainFragment0 -> + PlainFragment = remove_padding(PlainFragment0), ConnectionStates = ConnectionStates0#{current_read => ReadState0#{sequence_number => Seq + 1}}, @@ -177,9 +203,55 @@ decode_cipher_text(#ssl_tls{type = Type}, _) -> %% Version mismatch is already asserted ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_type_mismatch, Type}). + + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0, + BulkCipherAlgo, CipherFragment) -> + MaxEarlyDataSize = update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment), + ConnectionStates = + ConnectionStates0#{current_read => + ReadState0#{max_early_data_size => MaxEarlyDataSize}}, + if MaxEarlyDataSize < 0 -> + %% More early data is trial decrypted as the configured limit + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed); + true -> + {trial_decryption_failed, ConnectionStates} + end. + +process_early_data(ConnectionStates0, ReadState0, _MaxEarlyDataSize0, Seq, + _BulkCipherAlgo, _CipherFragment, PlainFragment) + when PlainFragment =:= <<5,0,0,0,22>> -> + %% struct { + %% opaque content[TLSPlaintext.length]; <<5,0,0,0>> - 5 = EndOfEarlyData + %% 0 = (uint24) size + %% ContentType type; <<22>> - Handshake + %% uint8 zeros[length_of_padding]; <<>> - no padding + %% } TLSInnerPlaintext; + %% EndOfEarlyData should not be counted into early data + ConnectionStates = + ConnectionStates0#{current_read => + ReadState0#{sequence_number => Seq + 1}}, + {decode_inner_plaintext(PlainFragment), ConnectionStates}; +process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq, + BulkCipherAlgo, CipherFragment, PlainFragment) -> + %% First packet is deciphered anyway so we must check if more early data is received + %% than the configured limit (max_early_data_size). + MaxEarlyDataSize = + update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment), + if MaxEarlyDataSize < 0 -> + %% Too much early data received, send alert unexpected_message + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, too_much_early_data); + true -> + ConnectionStates = + ConnectionStates0#{current_read => + ReadState0#{sequence_number => Seq + 1, + max_early_data_size => MaxEarlyDataSize}}, + {decode_inner_plaintext(PlainFragment), ConnectionStates} + end. + inner_plaintext(Type, Data, Length) -> #inner_plaintext{ content = Data, @@ -287,8 +359,6 @@ aead_ciphertext_split(CipherTextFragment, TagLen) decode_inner_plaintext(PlainText) -> case binary:last(PlainText) of - 0 -> - decode_inner_plaintext(init_binary(PlainText)); Type when Type =:= ?APPLICATION_DATA orelse Type =:= ?HANDSHAKE orelse Type =:= ?ALERT -> @@ -303,3 +373,30 @@ init_binary(B) -> {Init, _} = split_binary(B, byte_size(B) - 1), Init. + +remove_padding(InnerPlainText) -> + case binary:last(InnerPlainText) of + 0 -> + remove_padding(init_binary(InnerPlainText)); + _ -> + InnerPlainText + end. + +update_max_early_date_size(MaxEarlyDataSize, BulkCipherAlgo, CipherFragment) -> + %% CipherFragment is the binary encoded form of a TLSInnerPlaintext: + %% + %% struct { + %% opaque content[TLSPlaintext.length]; + %% ContentType type; + %% uint8 zeros[length_of_padding]; + %% } TLSInnerPlaintext; + %% + TypeLen = 1, + PaddingLen = 0, %% TODO Update formula when padding is implemented! + MaxEarlyDataSize - (byte_size(CipherFragment) - TypeLen - PaddingLen - + bca_tag_len(BulkCipherAlgo)). + +bca_tag_len(?AES_CCM_8) -> + 8; +bca_tag_len(_) -> + 16. diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 024c783b27..3d2cafa24c 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -272,12 +272,13 @@ connection({call, From}, dist_get_tls_socket, socket = Socket, connection_pid = Pid, trackers = Trackers}} = StateData) -> - TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Trackers), + TLSSocket = tls_gen_connection:socket([Pid, self()], Transport, Socket, Trackers), {next_state, ?FUNCTION_NAME, 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), ok = erlang:dist_ctrl_input_handler(DHandle, Pid), - ok = ssl_connection:dist_handshake_complete(Pid, DHandle), + ok = ssl_gen_statem:dist_handshake_complete(Pid, DHandle), %% From now on we execute on normal priority process_flag(priority, normal), {keep_state, StateData#data{static = Static#static{dist_handle = DHandle}}, @@ -287,7 +288,7 @@ connection({call, From}, {dist_handshake_complete, _Node, DHandle}, []; Data -> [{next_event, internal, - {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}] + {application_packets,{self(),undefined},Data}}] end]}; connection(internal, {application_packets, From, Data}, StateData) -> send_application_data(Data, From, ?FUNCTION_NAME, StateData); @@ -310,7 +311,7 @@ connection(info, dist_data, #data{static = #static{dist_handle = DHandle}}) -> []; Data -> [{next_event, internal, - {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}] + {application_packets,{self(),undefined},Data}}] end}; connection(info, tick, StateData) -> consume_ticks(), @@ -453,7 +454,7 @@ send_application_data(Data, From, StateName, {next_event, internal, {key_update, From}}, {next_event, internal, {application_packets, From, Data}}]}; renegotiate -> - ssl_connection:internal_renegotiation(Pid, ConnectionStates0), + tls_dtls_connection:internal_renegotiation(Pid, ConnectionStates0), {next_state, handshake, StateData0, [{next_event, internal, {application_packets, From, Data}}]}; chunk_and_key_update -> @@ -512,7 +513,7 @@ send_post_handshake_data(Handshake, From, StateName, maybe_update_cipher_key(#data{connection_states = ConnectionStates0, static = Static0} = StateData, #key_update{}) -> - ConnectionStates = tls_connection:update_cipher_key(current_write, ConnectionStates0), + ConnectionStates = tls_connection_1_3:update_cipher_key(current_write, ConnectionStates0), Static = Static0#static{bytes_sent = 0}, StateData#data{connection_states = ConnectionStates, static = Static}; @@ -542,6 +543,8 @@ key_update_at(Version, #{security_parameters := ?CHACHA20_POLY1305 -> seq_num_wrap; ?AES_CCM -> + KeyUpdateAt; + ?AES_CCM_8 -> KeyUpdateAt end; key_update_at(_, _, KeyUpdateAt) -> @@ -616,7 +619,15 @@ call(FsmPid, Event) -> %%-------------- Erlang distribution helpers ------------------------------ +%% To avoid livelock, dist_data/2 will check for more bytes coming from +%% distribution channel, if amount of already collected bytes greater +%% or equal than the limit defined below. +-define(TLS_BUNDLE_SOFT_LIMIT, 16 * 1024 * 1024). + dist_data(DHandle) -> + dist_data(DHandle, 0). + +dist_data(DHandle, CurBytes) -> case erlang:dist_ctrl_get_data(DHandle) of none -> erlang:dist_ctrl_get_data_notification(DHandle), @@ -625,15 +636,17 @@ dist_data(DHandle) -> %% since the emulator will always deliver a Data %% smaller than 4 GB, and the distribution will %% therefore always have to use {packet,4} - Data when is_binary(Data) -> - Len = byte_size(Data), - [[<<Len:32>>,Data]|dist_data(DHandle)]; - [BA,BB] = Data -> - Len = byte_size(BA) + byte_size(BB), - [[<<Len:32>>|Data]|dist_data(DHandle)]; - Data when is_list(Data) -> - Len = iolist_size(Data), - [[<<Len:32>>|Data]|dist_data(DHandle)] + {Len, Data} when Len + CurBytes >= ?TLS_BUNDLE_SOFT_LIMIT -> + %% Data is of type iovec(); lets keep it + %% as an iovec()... + erlang:dist_ctrl_get_data_notification(DHandle), + [<<Len:32>> | Data]; + {Len, Data} -> + Packet = [<<Len:32>> | Data], + case dist_data(DHandle, CurBytes + Len) of + [] -> Packet; + More -> Packet ++ More + end end. diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl index a804f81eaa..b9bafa6e36 100644 --- a/lib/ssl/src/tls_server_session_ticket.erl +++ b/lib/ssl/src/tls_server_session_ticket.erl @@ -31,7 +31,7 @@ -include("ssl_cipher.hrl"). %% API --export([start_link/4, +-export([start_link/5, new/3, use/4 ]). @@ -46,18 +46,19 @@ stateless, stateful, nonce, - lifetime + lifetime, + max_early_data_size }). %%%=================================================================== %%% API %%%=================================================================== --spec start_link(atom(), integer(), integer(), tuple()) -> {ok, Pid :: pid()} | +-spec start_link(atom(), integer(), integer(), integer(), tuple()) -> {ok, Pid :: pid()} | {error, Error :: {already_started, pid()}} | {error, Error :: term()} | ignore. -start_link(Mode, Lifetime, TicketStoreSize, AntiReplay) -> - gen_server:start_link(?MODULE, [Mode, Lifetime, TicketStoreSize, AntiReplay], []). +start_link(Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay) -> + gen_server:start_link(?MODULE, [Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay], []). new(Pid, Prf, MasterSecret) -> gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret}, infinity). @@ -81,18 +82,19 @@ init(Args) -> handle_call({new_session_ticket, Prf, MasterSecret}, _From, #state{nonce = Nonce, lifetime = LifeTime, - stateful = #{}} = State0) -> - Id = stateful_psk_id(), + max_early_data_size = MaxEarlyDataSize, + stateful = #{id_generator := IdGen}} = State0) -> + Id = stateful_psk_ticket_id(IdGen), PSK = tls_v1:pre_shared_key(MasterSecret, ticket_nonce(Nonce), Prf), - SessionTicket = new_session_ticket(Id, Nonce, LifeTime), + SessionTicket = new_session_ticket(Id, Nonce, LifeTime, MaxEarlyDataSize), State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, State0), {reply, SessionTicket, State}; handle_call({new_session_ticket, Prf, MasterSecret}, _From, #state{nonce = Nonce, stateless = #{}} = State) -> BaseSessionTicket = new_session_ticket_base(State), - SessionTicket = generate_statless_ticket(BaseSessionTicket, Prf, - MasterSecret, State), + SessionTicket = generate_stateless_ticket(BaseSessionTicket, Prf, + MasterSecret, State), {reply, SessionTicket, State#state{nonce = Nonce+1}}; handle_call({use_ticket, Identifiers, Prf, HandshakeHist}, _From, #state{stateful = #{}} = State0) -> @@ -142,31 +144,35 @@ format_status(_Opt, Status) -> %%% Internal functions %%%=================================================================== -inital_state([stateless, Lifetime, _, undefined]) -> +inital_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined]) -> #state{nonce = 0, stateless = #{seed => {crypto:strong_rand_bytes(16), crypto:strong_rand_bytes(32)}, window => undefined}, - lifetime = Lifetime + lifetime = Lifetime, + max_early_data_size = MaxEarlyDataSize }; -inital_state([stateless, Lifetime, _, {Window, K, M}]) -> +inital_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}]) -> erlang:send_after(Window * 1000, self(), rotate_bloom_filters), #state{nonce = 0, stateless = #{bloom_filter => tls_bloom_filter:new(K, M), seed => {crypto:strong_rand_bytes(16), crypto:strong_rand_bytes(32)}, window => Window}, - lifetime = Lifetime + lifetime = Lifetime, + max_early_data_size = MaxEarlyDataSize }; -inital_state([stateful, Lifetime, TicketStoreSize|_]) -> +inital_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) -> %% statfeful servers replay %% protection is that it saves %% all valid tickets #state{lifetime = Lifetime, + max_early_data_size = MaxEarlyDataSize, nonce = 0, stateful = #{db => stateful_store(), max => TicketStoreSize, - ref_index => #{} + ref_index => #{}, + id_generator => crypto:strong_rand_bytes(16) } }. @@ -186,17 +192,21 @@ ticket_nonce(I) -> <<?UINT64(I)>>. new_session_ticket_base(#state{nonce = Nonce, - lifetime = Lifetime}) -> - new_session_ticket(undefined, Nonce, Lifetime). + lifetime = Lifetime, + max_early_data_size = MaxEarlyDataSize}) -> + new_session_ticket(undefined, Nonce, Lifetime, MaxEarlyDataSize). -new_session_ticket(Id, Nonce, Lifetime) -> +new_session_ticket(Id, Nonce, Lifetime, MaxEarlyDataSize) -> TicketAgeAdd = ticket_age_add(), + Extensions = #{early_data => + #early_data_indication_nst{ + indication = MaxEarlyDataSize}}, #new_session_ticket{ ticket = Id, ticket_lifetime = Lifetime, ticket_age_add = TicketAgeAdd, ticket_nonce = ticket_nonce(Nonce), - extensions = #{} + extensions = Extensions }. @@ -295,15 +305,20 @@ stateful_living_ticket({TimeStamp,_}, Lived < LifeTime. -stateful_psk_id() -> - term_to_binary(make_ref()). +stateful_psk_ticket_id(Key) -> + Unique = erlang:unique_integer(), + %% Obfuscate to avoid DoS attack possiblities + %% that could invalidate tickets and render them + %% unusable. This id should be unpredictable + %% and unique but have no other cryptographic requirements. + crypto:crypto_one_time(aes_128_ecb, Key, <<Unique:128>>, true). %%%=================================================================== %%% Stateless ticket %%%=================================================================== -generate_statless_ticket(#new_session_ticket{ticket_nonce = Nonce, - ticket_age_add = TicketAgeAdd, - ticket_lifetime = Lifetime} +generate_stateless_ticket(#new_session_ticket{ticket_nonce = Nonce, + ticket_age_add = TicketAgeAdd, + ticket_lifetime = Lifetime} = Ticket, Prf, MasterSecret, #state{stateless = #{seed := {IV, Shard}}}) -> PSK = tls_v1:pre_shared_key(MasterSecret, Nonce, Prf), @@ -316,7 +331,7 @@ generate_statless_ticket(#new_session_ticket{ticket_nonce = Nonce, timestamp = Timestamp }, Shard, IV), Ticket#new_session_ticket{ticket = Encrypted}. - + stateless_use(#offered_psks{ identities = Identities, binders = Binders @@ -333,7 +348,7 @@ stateless_use([#psk_identity{identity = Encrypted, case ssl_cipher:decrypt_ticket(Encrypted, Shard, IV) of #stateless_ticket{hash = Prf, pre_shared_key = PSK} = Ticket -> - case statless_usable_ticket(Ticket, ObfAge, Binder, + case stateless_usable_ticket(Ticket, ObfAge, Binder, HandshakeHist, Window) of true -> stateless_anti_replay(Index, PSK, Binder, State); @@ -347,11 +362,11 @@ stateless_use([#psk_identity{identity = Encrypted, stateless_use(Ids, Binders, Prf, HandshakeHist, Index+1, State) end. -statless_usable_ticket(#stateless_ticket{hash = Prf, - ticket_age_add = TicketAgeAdd, - lifetime = Lifetime, - timestamp = Timestamp, - pre_shared_key = PSK}, ObfAge, +stateless_usable_ticket(#stateless_ticket{hash = Prf, + ticket_age_add = TicketAgeAdd, + lifetime = Lifetime, + timestamp = Timestamp, + pre_shared_key = PSK}, ObfAge, Binder, HandshakeHist, Window) -> case stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) of @@ -372,7 +387,7 @@ stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) -> in_window(_, undefined) -> true; -in_window(Age, {Window, _, _}) -> +in_window(Age, Window) when is_integer(Window) -> Age =< Window. stateless_anti_replay(Index, PSK, Binder, diff --git a/lib/ssl/src/tls_server_session_ticket_sup.erl b/lib/ssl/src/tls_server_session_ticket_sup.erl index 7ee4bb7b2c..bdde94ecea 100644 --- a/lib/ssl/src/tls_server_session_ticket_sup.erl +++ b/lib/ssl/src/tls_server_session_ticket_sup.erl @@ -27,26 +27,34 @@ -behaviour(supervisor). %% API --export([start_link/0, start_link_dist/0]). --export([start_child/1, start_child_dist/1]). +-export([start_link/0, + start_link_dist/0]). +-export([start_child/1, + start_child_dist/1]). %% Supervisor callback --export([init/1]). +-export([init/1, + sup_name/1]). %%%========================================================================= %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []). + supervisor:start_link({local, sup_name(normal)}, ?MODULE, []). start_link_dist() -> - supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []). + supervisor:start_link({local, sup_name(dist)}, ?MODULE, []). start_child(Args) -> - supervisor:start_child(tracker_name(normal), Args). + supervisor:start_child(sup_name(normal), Args). start_child_dist(Args) -> - supervisor:start_child(tracker_name(dist), Args). + supervisor:start_child(sup_name(dist), Args). + +sup_name(normal) -> + ?MODULE; +sup_name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). %%%========================================================================= %%% Supervisor callback @@ -66,7 +74,3 @@ init(_O) -> ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. -tracker_name(normal) -> - ?MODULE; -tracker_name(dist) -> - list_to_atom(atom_to_list(?MODULE) ++ "dist"). diff --git a/lib/ssl/src/tls_server_sup.erl b/lib/ssl/src/tls_server_sup.erl index 7834589206..b2f011f221 100644 --- a/lib/ssl/src/tls_server_sup.erl +++ b/lib/ssl/src/tls_server_sup.erl @@ -45,10 +45,14 @@ start_link() -> init([]) -> ListenTracker = listen_options_tracker_child_spec(), - SessionTracker = tls_server_session_child_spec(), - + SessionTracker = tls_server_session_child_spec(), + Pre_1_3SessionTracker = ssl_server_session_child_spec(), + Pre_1_3UpgradeSessionTracker = ssl_upgrade_server_session_child_spec(), + {ok, {{one_for_all, 10, 3600}, [ListenTracker, - SessionTracker + SessionTracker, + Pre_1_3SessionTracker, + Pre_1_3UpgradeSessionTracker ]}}. @@ -75,3 +79,21 @@ tls_server_session_child_spec() -> Modules = [tls_server_session_ticket_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +ssl_server_session_child_spec() -> + Name = ssl_server_session_cache_sup, + StartFunc = {ssl_server_session_cache_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_server_session_cache_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +ssl_upgrade_server_session_child_spec() -> + Name = ssl_upgrade_server_session_cache_sup, + StartFunc = {ssl_upgrade_server_session_cache_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_upgrade_server_session_cache_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index 89826e8250..91fdad4e44 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -49,6 +49,7 @@ start_link/3, terminate/2, inherit_tracker/3, + session_id_tracker/2, emulated_socket_options/2, get_emulated_opts/1, set_emulated_opts/2, @@ -62,7 +63,7 @@ -record(state, { emulated_opts, - port, + listen_monitor, ssl_opts }). @@ -78,10 +79,16 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _}, case Transport:listen(Port, Options ++ internal_inet_values()) of {ok, ListenSocket} -> {ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts), - LifeTime = get_ticket_lifetime(), - TicketStoreSize = get_ticket_store_size(), - {ok, SessionHandler} = session_tickets_tracker(LifeTime, TicketStoreSize, SslOpts), - Trackers = [{option_tracker, Tracker}, {session_tickets_tracker, SessionHandler}], + LifeTime = ssl_config:get_ticket_lifetime(), + TicketStoreSize = ssl_config:get_ticket_store_size(), + MaxEarlyDataSize = ssl_config:get_max_early_data_size(), + %% TLS-1.3 session handling + {ok, SessionHandler} = + session_tickets_tracker(LifeTime, TicketStoreSize, MaxEarlyDataSize, SslOpts), + %% PRE TLS-1.3 session handling + {ok, SessionIdHandle} = session_id_tracker(ListenSocket, SslOpts), + Trackers = [{option_tracker, Tracker}, {session_tickets_tracker, SessionHandler}, + {session_id_tracker, SessionIdHandle}], Socket = #sslsocket{pid = {ListenSocket, Config#config{trackers = Trackers}}}, check_active_n(EmOpts, Socket), {ok, Socket}; @@ -103,7 +110,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo], case tls_connection_sup:start_child(ConnArgs) of {ok, Pid} -> - ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers); + ssl_gen_statem:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers); {error, Reason} -> {error, Reason} end; @@ -117,7 +124,7 @@ upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo, ok = setopts(Transport, Socket, tls_socket:internal_inet_values()), case peername(Transport, Socket) of {ok, {Address, Port}} -> - ssl_connection:connect(ConnectionCb, Address, Port, Socket, + ssl_gen_statem:connect(ConnectionCb, Address, Port, Socket, {SslOptions, emulated_socket_options(EmOpts, #socket_options{}), undefined}, self(), CbInfo, Timeout); @@ -132,7 +139,7 @@ connect(Address, Port, {Transport, _, _, _, _} = CbInfo, try Transport:connect(Address, Port, SocketOpts, Timeout) of {ok, Socket} -> - ssl_connection:connect(ConnetionCb, Address, Port, Socket, + ssl_gen_statem:connect(ConnetionCb, Address, Port, Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, self(), CbInfo, Timeout); @@ -256,18 +263,40 @@ inherit_tracker(ListenSocket, EmOpts, #{erl_dist := false} = SslOpts) -> inherit_tracker(ListenSocket, EmOpts, #{erl_dist := true} = SslOpts) -> ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]). -session_tickets_tracker(_, _, #{erl_dist := false, - session_tickets := disabled}) -> +session_tickets_tracker(_, _, _, #{erl_dist := false, + session_tickets := disabled}) -> {ok, disabled}; -session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := false, - session_tickets := Mode, - anti_replay := AntiReplay}) -> - tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, AntiReplay]); -session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := true, - session_tickets := Mode}) -> - tls_server_session_ticket_sup:start_child_dist([Mode, Lifetime, TicketStoreSize]). - - +session_tickets_tracker(Lifetime, TicketStoreSize, MaxEarlyDataSize, + #{erl_dist := false, + session_tickets := Mode, + anti_replay := AntiReplay}) -> + tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay]); +session_tickets_tracker(Lifetime, TicketStoreSize, MaxEarlyDataSize, + #{erl_dist := true, + session_tickets := Mode, + anti_replay := AntiReplay}) -> + SupName = tls_server_session_ticket_sup:sup_name(dist), + Children = supervisor:count_children(SupName), + Workers = proplists:get_value(workers, Children), + case Workers of + 0 -> + tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay]); + 1 -> + [{_,Child,_, _}] = supervisor:which_children(SupName), + {ok, Child} + end. +session_id_tracker(_, #{versions := [{3,4}]}) -> + {ok, not_relevant}; +%% Regardless of the option reuse_sessions we need the session_id_tracker +%% to generate session ids, but no sessions will be stored unless +%% reuse_sessions = true. +session_id_tracker(ssl_unknown_listener, #{erl_dist := false}) -> + ssl_upgrade_server_session_cache_sup:start_child(normal); +session_id_tracker(ListenSocket, #{erl_dist := false}) -> + ssl_server_session_cache_sup:start_child(ListenSocket); +session_id_tracker(_, #{erl_dist := true}) -> + ssl_upgrade_server_session_cache_sup:start_child(dist). + get_emulated_opts(TrackerPid) -> call(TrackerPid, get_emulated_opts). set_emulated_opts(TrackerPid, InetValues) -> @@ -289,10 +318,12 @@ start_link(Port, SockOpts, SslOpts) -> %% %% Description: Initiates the server %%-------------------------------------------------------------------- -init([Port, Opts, SslOpts]) -> +init([Listen, Opts, SslOpts]) -> process_flag(trap_exit, true), - true = link(Port), - {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}. + Monitor = monitor_listen(Listen), + {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), + listen_monitor = Monitor, + ssl_opts = SslOpts}}. %%-------------------------------------------------------------------- -spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. @@ -337,7 +368,7 @@ handle_cast(_, State)-> %% %% Description: Handling all non call/cast messages %%------------------------------------------------------------------- -handle_info({'EXIT', Port, _}, #state{port = Port} = State) -> +handle_info({'DOWN', Monitor, _, _, _}, #state{listen_monitor = Monitor} = State) -> {stop, normal, State}. @@ -366,6 +397,9 @@ code_change(_OldVsn, State, _Extra) -> call(Pid, Msg) -> gen_server:call(Pid, Msg, infinity). +monitor_listen(Listen) when is_port(Listen) -> + erlang:monitor(port, Listen). + split_options(Opts) -> split_options(Opts, emulated_options(), [], []). split_options([], _, SocketOpts, EmuOpts) -> @@ -472,19 +506,3 @@ validate_inet_option(active, Value) validate_inet_option(_, _) -> ok. -get_ticket_lifetime() -> - case application:get_env(ssl, server_session_ticket_lifetime) of - {ok, Seconds} when is_integer(Seconds) andalso - Seconds =< 604800 -> %% MUST be less than 7 days - Seconds; - _ -> - 7200 %% Default 2 hours - end. - -get_ticket_store_size() -> - case application:get_env(ssl, server_session_ticket_store_size) of - {ok, Size} when is_integer(Size) -> - Size; - _ -> - 1000 - end. diff --git a/lib/ssl/src/tls_sup.erl b/lib/ssl/src/tls_sup.erl index 25c1db0272..a425ae31e2 100644 --- a/lib/ssl/src/tls_sup.erl +++ b/lib/ssl/src/tls_sup.erl @@ -45,10 +45,10 @@ start_link() -> init([]) -> - TLSConnetionManager = tls_connection_manager_child_spec(), + TLSConnetionSup = tls_connection_child_spec(), ServerInstanceSup = server_instance_child_spec(), - {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager, + {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup, ServerInstanceSup ]}}. @@ -57,7 +57,7 @@ init([]) -> %%% Internal functions %%-------------------------------------------------------------------- -tls_connection_manager_child_spec() -> +tls_connection_child_spec() -> Name = tls_connection, StartFunc = {tls_connection_sup, start_link, []}, Restart = permanent, diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index bab5ad9592..59c425ecbe 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -29,22 +29,53 @@ -include("ssl_internal.hrl"). -include("ssl_record.hrl"). --export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3, - setup_keys/8, suites/1, prf/5, - ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1, - default_signature_algs/1, signature_algs/2, - default_signature_schemes/1, signature_schemes/2, - groups/1, groups/2, group_to_enum/1, enum_to_group/1, default_groups/1]). - --export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4, - key_schedule/3, key_schedule/4, create_info/3, - external_binder_key/2, resumption_binder_key/2, - client_early_traffic_secret/3, early_exporter_master_secret/3, - client_handshake_traffic_secret/3, server_handshake_traffic_secret/3, - client_application_traffic_secret_0/3, server_application_traffic_secret_0/3, - exporter_master_secret/3, resumption_master_secret/3, - update_traffic_secret/2, calculate_traffic_keys/3, - transcript_hash/2, finished_key/2, finished_verify_data/3, pre_shared_key/3]). +-export([master_secret/4, + finished/5, + certificate_verify/3, + mac_hash/7, + hmac_hash/3, + setup_keys/8, + suites/1, + exclusive_suites/1, + prf/5, + ecc_curves/1, + ecc_curves/2, + oid_to_enum/1, + enum_to_oid/1, + default_signature_algs/1, + signature_algs/2, + default_signature_schemes/1, + signature_schemes/2, + groups/1, + groups/2, + group_to_enum/1, + enum_to_group/1, + default_groups/1]). + +-export([derive_secret/4, + hkdf_expand_label/5, + hkdf_extract/3, + hkdf_expand/4, + key_length/1, + key_schedule/3, + key_schedule/4, + create_info/3, + external_binder_key/2, + resumption_binder_key/2, + client_early_traffic_secret/3, + early_exporter_master_secret/3, + client_handshake_traffic_secret/3, + server_handshake_traffic_secret/3, + client_application_traffic_secret_0/3, + server_application_traffic_secret_0/3, + exporter_master_secret/3, + resumption_master_secret/3, + update_traffic_secret/2, + calculate_traffic_keys/3, + transcript_hash/2, + finished_key/2, + finished_verify_data/3, + pre_shared_key/3]). -type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 | sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 | @@ -425,13 +456,20 @@ update_traffic_secret(Algo, Secret) -> %% %% [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length) %% [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) --spec calculate_traffic_keys(atom(), atom(), binary()) -> {binary(), binary()}. -calculate_traffic_keys(HKDFAlgo, Cipher, Secret) -> - Key = hkdf_expand_label(Secret, <<"key">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo), +-spec calculate_traffic_keys(atom(), integer(), binary()) -> {binary(), binary()}. +calculate_traffic_keys(HKDFAlgo, KeyLength, Secret) -> + Key = hkdf_expand_label(Secret, <<"key">>, <<>>, KeyLength, HKDFAlgo), %% TODO: remove hard coded IV size IV = hkdf_expand_label(Secret, <<"iv">>, <<>>, 12, HKDFAlgo), {Key, IV}. +-spec key_length(CipherSuite) -> KeyLength when + CipherSuite :: binary(), + KeyLength :: 0 | 8 | 16 | 24 | 32. +key_length(CipherSuite) -> + #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + ssl_cipher:key_material(Cipher). + %% TLS v1.3 --------------------------------------------------- %% TLS 1.0 -1.2 --------------------------------------------------- @@ -453,50 +491,68 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, %% TODO 1.3 same as above? --spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()]. +-spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()]. suites(Minor) when Minor == 1; Minor == 2 -> - [ - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA - ]; + exclusive_suites(2); suites(3) -> + exclusive_suites(3) ++ suites(2); + +suites(4) -> + exclusive_suites(4) ++ suites(3). + +exclusive_suites(4) -> + [?TLS_AES_256_GCM_SHA384, + ?TLS_AES_128_GCM_SHA256, + ?TLS_CHACHA20_POLY1305_SHA256, + ?TLS_AES_128_CCM_SHA256, + ?TLS_AES_128_CCM_8_SHA256 + ]; +exclusive_suites(3) -> [?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM, + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM, + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, - ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, - ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 @@ -505,27 +561,24 @@ suites(3) -> %% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384, %% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256, %% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256 - ] ++ suites(2); - -suites(4) -> - [?TLS_AES_256_GCM_SHA384, - ?TLS_AES_128_GCM_SHA256, - ?TLS_CHACHA20_POLY1305_SHA256, - ?TLS_AES_128_CCM_SHA256 - %% Not supported - %% ?TLS_AES_128_CCM_8_SHA256 - ] ++ suites(3); - -suites('TLS_v1.3') -> - [?TLS_AES_256_GCM_SHA384, - ?TLS_AES_128_GCM_SHA256, - ?TLS_CHACHA20_POLY1305_SHA256, - ?TLS_AES_128_CCM_SHA256 - %% Not supported - %% ?TLS_AES_128_CCM_8_SHA256 + ]; +exclusive_suites(Minor) when Minor == 1; Minor == 2 -> + [ + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA ]. - signature_algs({3, 4}, HashSigns) -> signature_algs({3, 3}, HashSigns); signature_algs({3, 3}, HashSigns) -> @@ -684,7 +737,7 @@ hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M, N, Prev, Acc) -> hmac_hash(?NULL, _, _) -> <<>>; hmac_hash(Alg, Key, Value) -> - crypto:hmac(mac_algo(Alg), Key, Value). + crypto:mac(hmac, mac_algo(Alg), Key, Value). mac_algo(Alg) when is_atom(Alg) -> Alg; |