summaryrefslogtreecommitdiff
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/Makefile31
-rw-r--r--lib/ssl/src/dtls_connection.erl1125
-rw-r--r--lib/ssl/src/dtls_connection_sup.erl4
-rw-r--r--lib/ssl/src/dtls_gen_connection.erl685
-rw-r--r--lib/ssl/src/dtls_handshake.erl49
-rw-r--r--lib/ssl/src/dtls_listener_sup.erl32
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl54
-rw-r--r--lib/ssl/src/dtls_record.erl25
-rw-r--r--lib/ssl/src/dtls_server_session_cache_sup.erl63
-rw-r--r--lib/ssl/src/dtls_server_sup.erl75
-rw-r--r--lib/ssl/src/dtls_socket.erl121
-rw-r--r--lib/ssl/src/dtls_sup.erl24
-rw-r--r--lib/ssl/src/inet6_tls_dist.erl13
-rw-r--r--lib/ssl/src/inet_tls_dist.erl39
-rw-r--r--lib/ssl/src/ssl.app.src18
-rw-r--r--lib/ssl/src/ssl.appup.src2
-rw-r--r--lib/ssl/src/ssl.erl1233
-rw-r--r--lib/ssl/src/ssl_alert.erl2
-rw-r--r--lib/ssl/src/ssl_certificate.erl346
-rw-r--r--lib/ssl/src/ssl_cipher.erl251
-rw-r--r--lib/ssl/src/ssl_cipher.hrl12
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl376
-rw-r--r--lib/ssl/src/ssl_client_session_cache_db.erl (renamed from lib/ssl/src/ssl_session_cache.erl)47
-rw-r--r--lib/ssl/src/ssl_config.erl145
-rw-r--r--lib/ssl/src/ssl_connection.erl3117
-rw-r--r--lib/ssl/src/ssl_connection.hrl11
-rw-r--r--lib/ssl/src/ssl_crl.erl68
-rw-r--r--lib/ssl/src/ssl_dist_connection_sup.erl30
-rw-r--r--lib/ssl/src/ssl_dist_sup.erl8
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl2055
-rw-r--r--lib/ssl/src/ssl_handshake.erl710
-rw-r--r--lib/ssl/src/ssl_handshake.hrl63
-rw-r--r--lib/ssl/src/ssl_internal.hrl50
-rw-r--r--lib/ssl/src/ssl_listen_tracker_sup.erl2
-rw-r--r--lib/ssl/src/ssl_logger.erl11
-rw-r--r--lib/ssl/src/ssl_manager.erl237
-rw-r--r--lib/ssl/src/ssl_record.erl73
-rw-r--r--lib/ssl/src/ssl_record.hrl3
-rw-r--r--lib/ssl/src/ssl_server_session_cache.erl259
-rw-r--r--lib/ssl/src/ssl_server_session_cache_db.erl80
-rw-r--r--lib/ssl/src/ssl_server_session_cache_sup.erl65
-rw-r--r--lib/ssl/src/ssl_session.erl71
-rw-r--r--lib/ssl/src/ssl_session_cache_api.erl2
-rw-r--r--lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl90
-rw-r--r--lib/ssl/src/ssl_v3.erl200
-rw-r--r--lib/ssl/src/tls_client_ticket_store.erl118
-rw-r--r--lib/ssl/src/tls_connection.erl1479
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl474
-rw-r--r--lib/ssl/src/tls_connection_sup.erl8
-rw-r--r--lib/ssl/src/tls_dist_server_sup.erl89
-rw-r--r--lib/ssl/src/tls_dist_sup.erl75
-rw-r--r--lib/ssl/src/tls_dtls_connection.erl1687
-rw-r--r--lib/ssl/src/tls_gen_connection.erl778
-rw-r--r--lib/ssl/src/tls_handshake.erl105
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl1299
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl8
-rw-r--r--lib/ssl/src/tls_record.erl252
-rw-r--r--lib/ssl/src/tls_record_1_3.erl119
-rw-r--r--lib/ssl/src/tls_sender.erl43
-rw-r--r--lib/ssl/src/tls_server_session_ticket.erl81
-rw-r--r--lib/ssl/src/tls_server_session_ticket_sup.erl26
-rw-r--r--lib/ssl/src/tls_server_sup.erl28
-rw-r--r--lib/ssl/src/tls_socket.erl96
-rw-r--r--lib/ssl/src/tls_sup.erl6
-rw-r--r--lib/ssl/src/tls_v1.erl179
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;