aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/Makefile144
-rw-r--r--lib/ssl/src/dtls.erl113
-rw-r--r--lib/ssl/src/dtls_connection.erl1516
-rw-r--r--lib/ssl/src/dtls_handshake.erl373
-rw-r--r--lib/ssl/src/dtls_handshake.hrl23
-rw-r--r--lib/ssl/src/dtls_listener_sup.erl (renamed from lib/ssl/src/dtls_udp_sup.erl)8
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl (renamed from lib/ssl/src/dtls_udp_listener.erl)153
-rw-r--r--lib/ssl/src/dtls_record.erl371
-rw-r--r--lib/ssl/src/dtls_socket.erl141
-rw-r--r--lib/ssl/src/dtls_v1.erl33
-rw-r--r--lib/ssl/src/inet6_tls_dist.erl7
-rw-r--r--lib/ssl/src/inet_tls_dist.erl938
-rw-r--r--lib/ssl/src/ssl.app.src22
-rw-r--r--lib/ssl/src/ssl.appup.src6
-rw-r--r--lib/ssl/src/ssl.erl1677
-rw-r--r--lib/ssl/src/ssl_alert.erl200
-rw-r--r--lib/ssl/src/ssl_alert.hrl12
-rw-r--r--lib/ssl/src/ssl_api.hrl43
-rw-r--r--lib/ssl/src/ssl_app.erl17
-rw-r--r--lib/ssl/src/ssl_certificate.erl138
-rw-r--r--lib/ssl/src/ssl_cipher.erl2077
-rw-r--r--lib/ssl/src/ssl_cipher.hrl154
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl1919
-rw-r--r--lib/ssl/src/ssl_config.erl20
-rw-r--r--lib/ssl/src/ssl_connection.erl2717
-rw-r--r--lib/ssl/src/ssl_connection.hrl203
-rw-r--r--lib/ssl/src/ssl_connection_sup.erl12
-rw-r--r--lib/ssl/src/ssl_crl_cache.erl22
-rw-r--r--lib/ssl/src/ssl_crl_cache_api.erl15
-rw-r--r--lib/ssl/src/ssl_crl_hash_dir.erl5
-rw-r--r--lib/ssl/src/ssl_dh_groups.erl467
-rw-r--r--lib/ssl/src/ssl_dist_sup.erl77
-rw-r--r--lib/ssl/src/ssl_handshake.erl3210
-rw-r--r--lib/ssl/src/ssl_handshake.hrl101
-rw-r--r--lib/ssl/src/ssl_internal.hrl117
-rw-r--r--lib/ssl/src/ssl_logger.erl451
-rw-r--r--lib/ssl/src/ssl_manager.erl68
-rw-r--r--lib/ssl/src/ssl_pem_cache.erl39
-rw-r--r--lib/ssl/src/ssl_pkix_db.erl37
-rw-r--r--lib/ssl/src/ssl_record.erl209
-rw-r--r--lib/ssl/src/ssl_record.hrl12
-rw-r--r--lib/ssl/src/ssl_session.erl17
-rw-r--r--lib/ssl/src/ssl_session_cache_api.erl24
-rw-r--r--lib/ssl/src/ssl_tls_dist_proxy.erl479
-rw-r--r--lib/ssl/src/ssl_v2.erl38
-rw-r--r--lib/ssl/src/ssl_v3.erl4
-rw-r--r--lib/ssl/src/tls.erl112
-rw-r--r--lib/ssl/src/tls_connection.erl1463
-rw-r--r--lib/ssl/src/tls_connection.hrl1
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl244
-rw-r--r--lib/ssl/src/tls_handshake.erl438
-rw-r--r--lib/ssl/src/tls_handshake.hrl1
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl1898
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl261
-rw-r--r--lib/ssl/src/tls_record.erl534
-rw-r--r--lib/ssl/src/tls_record_1_3.erl320
-rw-r--r--lib/ssl/src/tls_record_1_3.hrl58
-rw-r--r--lib/ssl/src/tls_sender.erl524
-rw-r--r--lib/ssl/src/tls_socket.erl118
-rw-r--r--lib/ssl/src/tls_v1.erl492
60 files changed, 17753 insertions, 7140 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 2e7df9792e..8dc76f2638 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2016. All Rights Reserved.
+# Copyright Ericsson AB 1999-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.
@@ -39,62 +39,80 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN)
# ----------------------------------------------------
BEHAVIOUR_MODULES= \
- ssl_session_cache_api \
- ssl_crl_cache_api
+ ssl_crl_cache_api \
+ ssl_session_cache_api
+
MODULES= \
- ssl \
- tls \
- dtls \
- ssl_alert \
- ssl_app \
- ssl_sup \
- ssl_admin_sup\
- tls_connection_sup \
- ssl_connection_sup \
- ssl_listen_tracker_sup\
+ dtls_connection \
dtls_connection_sup \
- dtls_udp_listener\
- dtls_udp_sup \
- ssl_dist_sup\
- ssl_dist_admin_sup\
- ssl_dist_connection_sup\
+ dtls_handshake \
+ dtls_listener_sup \
+ dtls_packet_demux \
+ dtls_record \
+ dtls_socket \
+ dtls_v1 \
inet_tls_dist \
inet6_tls_dist \
- ssl_certificate\
- ssl_pkix_db\
+ ssl \
+ ssl_admin_sup \
+ ssl_alert \
+ ssl_app \
+ ssl_certificate \
ssl_cipher \
- ssl_srp_primes \
- tls_connection \
- dtls_connection \
+ ssl_cipher_format \
ssl_config \
ssl_connection \
- tls_handshake \
- dtls_handshake\
- ssl_handshake\
- ssl_manager \
- ssl_session \
- ssl_session_cache \
- ssl_pem_cache \
- ssl_crl\
+ ssl_connection_sup \
+ ssl_crl \
ssl_crl_cache \
ssl_crl_hash_dir \
- tls_socket \
- dtls_socket \
- tls_record \
- dtls_record \
+ ssl_dh_groups \
+ ssl_dist_admin_sup \
+ ssl_dist_connection_sup \
+ ssl_dist_sup \
+ ssl_handshake \
+ ssl_listen_tracker_sup \
+ ssl_logger \
+ ssl_manager \
+ ssl_pem_cache \
+ ssl_pkix_db \
ssl_record \
- ssl_v2 \
+ ssl_session \
+ ssl_session_cache \
+ ssl_srp_primes \
+ ssl_sup \
ssl_v3 \
- tls_v1 \
- dtls_v1 \
- ssl_tls_dist_proxy
+ tls_connection \
+ tls_connection_sup \
+ tls_connection_1_3 \
+ tls_handshake \
+ tls_handshake_1_3 \
+ tls_record \
+ tls_record_1_3 \
+ tls_sender \
+ tls_socket \
+ tls_v1
+
INTERNAL_HRL_FILES = \
- ssl_alert.hrl ssl_cipher.hrl \
- tls_connection.hrl dtls_connection.hrl ssl_connection.hrl \
- ssl_handshake.hrl tls_handshake.hrl dtls_handshake.hrl ssl_api.hrl ssl_internal.hrl \
- ssl_record.hrl tls_record.hrl dtls_record.hrl ssl_srp.hrl
+ dtls_connection.hrl \
+ dtls_handshake.hrl \
+ dtls_record.hrl \
+ ssl_alert.hrl \
+ ssl_api.hrl \
+ ssl_cipher.hrl \
+ ssl_connection.hrl \
+ ssl_handshake.hrl \
+ ssl_internal.hrl \
+ ssl_record.hrl \
+ ssl_srp.hrl \
+ tls_connection.hrl \
+ tls_handshake.hrl \
+ tls_handshake_1_3.hrl \
+ tls_record.hrl \
+ tls_record_1_3.hrl
+
ERL_FILES= \
$(MODULES:%=%.erl) \
@@ -113,6 +131,10 @@ APP_TARGET= $(EBIN)/$(APP_FILE)
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
+DEPDIR=$(ERL_TOP)/lib/ssl/src/deps
+DEP_FILE=$(DEPDIR)/ssl.d
+$(shell mkdir -p $(dir $(DEP_FILE)) >/dev/null)
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
@@ -120,7 +142,7 @@ EXTRA_ERLC_FLAGS = +warn_unused_vars
ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
-pz $(EBIN) \
-pz $(ERL_TOP)/lib/public_key/ebin \
- $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\"
+ $(EXTRA_ERLC_FLAGS)
# ----------------------------------------------------
@@ -129,11 +151,22 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
$(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES)
-debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
+$(DEP_FILE): $(ERL_FILES)
+ $(gen_verbose)erlc -M $(ERL_FILES) \
+ | sed "s@$(ERL_TOP)@../../..@g" \
+ | sed "s/\.$(EMULATOR)/\.$$\(EMULATOR\)/" \
+ | sed 's@^dtls_@$$(EBIN)/dtls_@' \
+ | sed 's@^inet_@$$(EBIN)/inet_@' \
+ | sed 's@^ssl_@$$(EBIN)/ssl_@' \
+ | sed 's@^tls_@$$(EBIN)/tls_@' \
+ > $(DEP_FILE)
+
+debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE)
clean:
rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES)
rm -f errs core *~
+ rm -rf $(DEPDIR)
$(APP_TARGET): $(APP_SRC) ../vsn.mk
$(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
@@ -143,7 +176,6 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
docs:
-
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -161,22 +193,4 @@ release_docs_spec:
# ----------------------------------------------------
# Dependencies
# ----------------------------------------------------
-$(EBIN)/inet_tls_dist.$(EMULATOR): ../../kernel/include/net_address.hrl ../../kernel/include/dist.hrl ../../kernel/include/dist_util.hrl
-$(EBIN)/tls.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/ssl_alert.$(EMULATOR): ssl_alert.hrl ssl_record.hrl
-$(EBIN)/ssl_certificate.$(EMULATOR): ssl_internal.hrl ssl_alert.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/ssl_certificate_db.$(EMULATOR): ssl_internal.hrl ../../public_key/include/public_key.hrl ../../kernel/include/file.hrl
-$(EBIN)/ssl_cipher.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/tls_connection.$(EMULATOR): ssl_internal.hrl tls_connection.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/dtls_connection.$(EMULATOR): ssl_internal.hrl dtls_connection.hrl dtls_record.hrl ssl_cipher.hrl dtls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl ssl_connection.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/ssl_manager.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl ../../kernel/include/file.hrl
-$(EBIN)/ssl_record.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl
-$(EBIN)/ssl_session.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
-$(EBIN)/ssl_session_cache.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
-$(EBIN)/ssl_session_cache_api.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
-$(EBIN)/ssl_ssl3.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl
-$(EBIN)/ssl_tls1.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl
-$(EBIN)/ssl_cache.$(EMULATOR): ssl_cache.erl ssl_internal.hrl ../../public_key/include/public_key.hrl
-
+-include $(DEP_FILE)
diff --git a/lib/ssl/src/dtls.erl b/lib/ssl/src/dtls.erl
deleted file mode 100644
index cd705152a8..0000000000
--- a/lib/ssl/src/dtls.erl
+++ /dev/null
@@ -1,113 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2016. 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 : Reflect DTLS specific API options (fairly simple wrapper at the moment)
-%% First implementation will support DTLS connections only in a "TLS/TCP like way"
-
--module(dtls).
-
--include("ssl_api.hrl").
--include("ssl_internal.hrl").
-
--export([connect/2, connect/3, listen/2, accept/1, accept/2,
- handshake/1, handshake/2, handshake/3]).
-
-%%--------------------------------------------------------------------
-%%
-%% Description: Connect to a DTLS server.
-%%--------------------------------------------------------------------
-
--spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
- {error, reason()}.
-
-connect(Socket, Options) when is_port(Socket) ->
- connect(Socket, Options, infinity).
-
--spec connect(host() | port(), [connect_option()] | inet:port_number(),
- timeout() | list()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
-connect(Socket, SslOptions, Timeout) when is_port(Socket) ->
- DTLSOpts = [{protocol, dtls} | SslOptions],
- ssl:connect(Socket, DTLSOpts, Timeout);
-connect(Host, Port, Options) ->
- connect(Host, Port, Options, infinity).
-
--spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
-connect(Host, Port, Options, Timeout) ->
- DTLSOpts = [{protocol, dtls} | Options],
- ssl:connect(Host, Port, DTLSOpts, Timeout).
-
-%%--------------------------------------------------------------------
--spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
-
-%%
-%% Description: Creates an ssl listen socket.
-%%--------------------------------------------------------------------
-listen(Port, Options) ->
- DTLSOpts = [{protocol, dtls} | Options],
- ssl:listen(Port, DTLSOpts).
-
-%%--------------------------------------------------------------------
-%%
-%% Description: Performs transport accept on an ssl listen socket
-%%--------------------------------------------------------------------
--spec accept(#sslsocket{}) -> {ok, #sslsocket{}} |
- {error, reason()}.
-accept(ListenSocket) ->
- accept(ListenSocket, infinity).
-
--spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {error, reason()}.
-accept(Socket, Timeout) ->
- ssl:transport_accept(Socket, Timeout).
-
-%%--------------------------------------------------------------------
-%%
-%% Description: Performs accept on an ssl listen socket. e.i. performs
-%% ssl handshake.
-%%--------------------------------------------------------------------
-
--spec handshake(#sslsocket{}) -> ok | {error, reason()}.
-
-handshake(ListenSocket) ->
- handshake(ListenSocket, infinity).
-
-
--spec handshake(#sslsocket{} | port(), timeout()| [ssl_option()
- | transport_option()]) ->
- ok | {ok, #sslsocket{}} | {error, reason()}.
-
-handshake(#sslsocket{} = Socket, Timeout) ->
- ssl:ssl_accept(Socket, Timeout);
-
-handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
- handshake(ListenSocket, SslOptions, infinity).
-
-
--spec handshake(port(), [ssl_option()| transport_option()], timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
-handshake(Socket, SslOptions, Timeout) when is_port(Socket) ->
- ssl:ssl_accept(Socket, SslOptions, Timeout).
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 745db788e7..b220691e79 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. 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.
@@ -32,250 +32,455 @@
-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]).
+-export([start_fsm/8, start_link/7, init/1, pids/1]).
%% State transition handling
--export([next_record/1, next_event/3, next_event/4]).
+-export([next_event/3, next_event/4, handle_protocol_record/3]).
%% Handshake handling
--export([renegotiate/2,
- reinit_handshake_data/1,
- send_handshake/2, queue_handshake/2, queue_change_cipher/2,
- select_sni_extension/1]).
+-export([renegotiate/2, send_handshake/2,
+ queue_handshake/2, queue_change_cipher/2,
+ reinit/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]).
%% Alert and close handling
--export([encode_alert/3,send_alert/2, close/5]).
+-export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
%% Data handling
-
--export([encode_data/3, passive_receive/2, next_record_if_active/1, handle_common_event/4,
- send/3, socket/5]).
+-export([socket/4, setopts/3, getopts/3]).
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+ hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
connection/3]).
%% gen_statem callbacks
-export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
%%====================================================================
%% Internal application API
+%%====================================================================
+%%====================================================================
+%% Setup
%%====================================================================
start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts,
- User, {CbModule, _,_, _} = CbInfo,
+ 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),
- ok = ssl_connection:handshake(SslSocket, Timeout),
- {ok, SslSocket}
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
+ ssl_connection:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
Error
end.
-send_handshake(Handshake, #state{connection_states = ConnectionStates} = States) ->
+%%--------------------------------------------------------------------
+-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),
+ try
+ State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
+ gen_statem:enter_loop(?MODULE, [], init, 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)
+ 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
+%%====================================================================
+
+renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) ->
+ %% Handle same way as if server requested
+ %% the renegotiation
+ State = 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, States), Epoch).
+ send_handshake_flight(queue_handshake(Handshake, State), Epoch).
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
- negotiated_version = Version,
+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} = State) ->
+ next_sequence := Seq} = Flight0,
+ ssl_options = SslOpts} = State) ->
Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Handshake0),
+
State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist};
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
- negotiated_version = Version,
+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} = State) ->
+ next_sequence := Seq} = Flight0,
+ ssl_options = SslOpts} = State) ->
Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Handshake0),
+
State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist}.
-
-
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
- flight_buffer = #{handshakes := Flight,
- change_cipher_spec := undefined},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = State0, Epoch) ->
- %% TODO remove hardcoded Max size
- {Encoded, ConnectionStates} =
- encode_handshake_flight(lists:reverse(Flight), Version, 1400, Epoch, ConnectionStates0),
- send(Transport, Socket, Encoded),
- start_flight(State0#state{connection_states = ConnectionStates});
-
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
- flight_buffer = #{handshakes := [_|_] = Flight0,
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := []},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = 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]),
- start_flight(State0#state{connection_states = ConnectionStates});
-
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
- flight_buffer = #{handshakes := [_|_] = Flight0,
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := Flight1},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = 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]),
- start_flight(State0#state{connection_states = ConnectionStates});
-
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
- flight_buffer = #{handshakes := [],
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := Flight1},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = 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]),
- start_flight(State0#state{connection_states = ConnectionStates}).
+ 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),
+ dtls_record:next_epoch(ConnectionStates0, write),
State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher},
connection_states = ConnectionStates}.
-send_alert(Alert, #state{negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
- connection_states = ConnectionStates0} = State0) ->
+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 = SslOpts} = State0) ->
{BinMsg, ConnectionStates} =
encode_alert(Alert, Version, ConnectionStates0),
send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, 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).
-reinit_handshake_data(#state{protocol_buffers = Buffers} = State) ->
- State#state{premaster_secret = undefined,
- public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history(),
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
- protocol_buffers =
- Buffers#protocol_buffers{
- dtls_handshake_next_seq = 0,
- dtls_handshake_next_fragments = [],
- dtls_handshake_later_fragments = []
- }}.
-
-select_sni_extension(#client_hello{extensions = HelloExtensions}) ->
- HelloExtensions#hello_extensions.sni;
-select_sni_extension(_) ->
- undefined.
-
-socket(Pid, Transport, Socket, Connection, _) ->
- dtls_socket:socket(Pid, Transport, Socket, Connection).
-
-%%====================================================================
-%% tls_connection_sup API
+protocol_name() ->
+ "DTLS".
+
%%====================================================================
+%% Data handling
+%%====================================================================
-%%--------------------------------------------------------------------
--spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) ->
- {ok, pid()} | ignore | {error, reason()}.
-%%
-%% Description: Creates a gen_fsm process which calls Module:init/1 to
-%% initialize. To ensure a synchronized start-up procedure, this function
-%% does not return until Module:init/1 has returned.
-%%--------------------------------------------------------------------
-start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
+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).
-init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
- process_flag(trap_exit, true),
- State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
- try
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- gen_statem:enter_loop(?MODULE, [], init, State)
- catch
- throw:Error ->
- gen_statem:enter_loop(?MODULE, [], error, {Error,State0})
- end.
+socket(Pid, Transport, Socket, _Tracker) ->
+ dtls_socket:socket(Pid, Transport, Socket, ?MODULE).
-callback_mode() ->
- state_functions.
+setopts(Transport, Socket, Other) ->
+ dtls_socket:setopts(Transport, Socket, Other).
+
+getopts(Transport, Socket, Tag) ->
+ dtls_socket:getopts(Transport, Socket, Tag).
%%--------------------------------------------------------------------
%% State functions
%%--------------------------------------------------------------------
-
+%%--------------------------------------------------------------------
+-spec init(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+init(enter, _, State) ->
+ {keep_state, State};
init({call, From}, {start, Timeout},
- #state{host = Host, port = Port, role = client,
+ #state{static_env = #static_env{host = Host,
+ port = Port,
+ role = client,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ connection_env = CEnv,
ssl_options = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
- Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
- HelloVersion = dtls_record:lowest_protocol_version(SslOpts#ssl_options.versions),
- State1 = prepare_flight(State0#state{negotiated_version = Version}),
- {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
- State3 = State2#state{negotiated_version = Version, %% Requested version
+ HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.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}}),
+ State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
session =
Session0#session{session_id = Hello#client_hello.session_id},
- start_or_recv_from = From,
- timer = Timer,
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
- },
- {Record, State} = next_record(State3),
- next_event(hello, Record, State, Actions);
-init({call, _} = Type, Event, #state{role = server, transport_cb = gen_udp} = State) ->
- ssl_connection:init(Type, Event,
- State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}},
- ?MODULE);
-init({call, _} = Type, Event, #state{role = server} = State) ->
- %% I.E. DTLS over sctp
- ssl_connection:init(Type, Event, State#state{flight_state = reliable}, ?MODULE);
+ 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(),
+ 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) ->
- ssl_connection:init(Type, Event, State, ?MODULE).
-
-error({call, From}, {start, _Timeout}, {Error, State}) ->
- {stop_and_reply, normal, {reply, From, {error, Error}}, State};
-error({call, From}, Msg, State) ->
- handle_call(Msg, From, error, State);
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+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]}.
@@ -285,264 +490,281 @@ error(_, _, _) ->
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+hello(enter, _, #state{static_env = #static_env{role = server}} = State) ->
+ {keep_state, State};
+hello(enter, _, #state{static_env = #static_env{role = client}} = State0) ->
+ {State, Actions} = handle_flight_timer(State0),
+ {keep_state, State, Actions};
hello(internal, #client_hello{cookie = <<>>,
- client_version = Version} = Hello, #state{role = server,
- transport_cb = Transport,
- socket = Socket} = State0) ->
- %% TODO: not hard code key
- {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
- Cookie = dtls_handshake:cookie(<<"secret">>, IP, Port, Hello),
- VerifyRequest = dtls_handshake:hello_verify_request(Cookie, Version),
- State1 = prepare_flight(State0#state{negotiated_version = Version}),
- {State2, Actions} = send_handshake(VerifyRequest, State1),
- {Record, State} = next_record(State2),
- next_event(hello, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);
-hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server,
- transport_cb = Transport,
- socket = Socket} = State0) ->
+ client_version = Version} = Hello,
+ #state{static_env = #static_env{role = server,
+ transport_cb = Transport,
+ socket = Socket},
+ handshake_env = HsEnv,
+ connection_env = CEnv,
+ protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
- %% TODO: not hard code key
- case dtls_handshake:cookie(<<"secret">>, IP, Port, Hello) of
- Cookie ->
- handle_client_hello(Hello, State0);
- _ ->
- %% Handle bad cookie as new cookie request RFC 6347 4.1.2
- hello(internal, Hello#client_hello{cookie = <<>>}, State0)
- end;
-hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,
- host = Host, port = Port,
+ 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,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ connection_env = CEnv,
ssl_options = SslOpts,
session = #session{own_certificate = OwnCert}
= Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
- State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}),
+
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
SslOpts,
Cache, CacheCb, Renegotiation, OwnCert),
Version = Hello#client_hello.client_version,
- HelloVersion = dtls_record:lowest_protocol_version(SslOpts#ssl_options.versions),
- {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
- State3 = State2#state{negotiated_version = Version, %% Requested version
- session =
- Session0#session{session_id =
- Hello#client_hello.session_id}},
- {Record, State} = next_record(State3),
- next_event(hello, Record, State, Actions);
+ State1 = prepare_flight(State0#state{handshake_env =
+ HsEnv#handshake_env{tls_handshake_history
+ = ssl_handshake:init_handshake_history()}}),
+
+ {State2, Actions} = send_handshake(Hello, State1),
+ State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% Requested version
+ session =
+ Session0#session{session_id =
+ Hello#client_hello.session_id}},
+ next_event(?FUNCTION_NAME, no_record, State, Actions);
+hello(internal, #client_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #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, #server_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #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{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) ->
+ {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
+ case dtls_handshake:cookie(Secret, IP, Port, Hello) of
+ Cookie ->
+ handle_client_hello(Hello, State0);
+ _ ->
+ case dtls_handshake:cookie(PSecret, IP, Port, Hello) of
+ Cookie ->
+ handle_client_hello(Hello, State0);
+ _ ->
+ %% Handle bad cookie as new cookie request RFC 6347 4.1.2
+ hello(internal, Hello#client_hello{cookie = <<>>}, State0)
+ end
+ end;
hello(internal, #server_hello{} = Hello,
- #state{connection_states = ConnectionStates0,
- negotiated_version = ReqVersion,
- role = client,
- renegotiation = {Renegotiation, _},
- ssl_options = SslOptions} = State) ->
+ #state{
+ static_env = #static_env{role = client},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ connection_env = #connection_env{negotiated_version = ReqVersion},
+ connection_states = ConnectionStates0,
+ ssl_options = SslOptions} = State) ->
case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, ReqVersion, hello, State);
+ handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
{Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
ssl_connection:handle_session(Hello,
Version, NewId, ConnectionStates, ProtoExt, Protocol, State)
end;
hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) ->
%% Initial hello should not be in handshake history
- {next_state, hello, State, [{next_event, internal, Handshake}]};
+ {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]};
hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) ->
%% hello_verify should not be in handshake history
- {next_state, hello, State, [{next_event, internal, Handshake}]};
+ {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),
+ %% This will reset the retransmission timer by repeating the enter state event
+ {repeat_state, State, Actions};
hello(info, Event, State) ->
- handle_info(Event, hello, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
hello(state_timeout, Event, State) ->
- handle_state_timeout(Event, hello, State);
+ handle_state_timeout(Event, ?FUNCTION_NAME, State);
hello(Type, Event, State) ->
- ssl_connection:hello(Type, Event, State, ?MODULE).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
+user_hello(enter, _, State) ->
+ {keep_state, State};
+user_hello(Type, Event, State) ->
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+%%--------------------------------------------------------------------
+-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+abbreviated(enter, _, State0) ->
+ {State, Actions} = handle_flight_timer(State0),
+ {keep_state, State, Actions};
abbreviated(info, Event, State) ->
- handle_info(Event, abbreviated, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
abbreviated(internal = Type,
#change_cipher_spec{type = <<1>>} = Event,
#state{connection_states = ConnectionStates0} = State) ->
ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read),
ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read),
- ssl_connection:abbreviated(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE);
-abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) ->
- ssl_connection:abbreviated(Type, Event,
- prepare_flight(State#state{connection_states = ConnectionStates,
- flight_state = connection}), ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates});
+abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates,
+ protocol_specific = PS} = State) ->
+ gen_handshake(?FUNCTION_NAME, Type, Event,
+ prepare_flight(State#state{connection_states = ConnectionStates,
+ protocol_specific = PS#{flight_state => connection}}));
abbreviated(state_timeout, Event, State) ->
- handle_state_timeout(Event, abbreviated, State);
+ handle_state_timeout(Event, ?FUNCTION_NAME, State);
abbreviated(Type, Event, State) ->
- ssl_connection:abbreviated(Type, Event, State, ?MODULE).
-
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+%%--------------------------------------------------------------------
+-spec certify(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+certify(enter, _, State0) ->
+ {State, Actions} = handle_flight_timer(State0),
+ {keep_state, State, Actions};
certify(info, Event, State) ->
- handle_info(Event, certify, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
certify(internal = Type, #server_hello_done{} = Event, State) ->
ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE);
+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),
+ %% This will reset the retransmission timer by repeating the enter state event
+ {repeat_state, State, Actions};
certify(state_timeout, Event, State) ->
- handle_state_timeout(Event, certify, State);
+ handle_state_timeout(Event, ?FUNCTION_NAME, State);
certify(Type, Event, State) ->
- ssl_connection:certify(Type, Event, State, ?MODULE).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+%%--------------------------------------------------------------------
+-spec cipher(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+cipher(enter, _, State0) ->
+ {State, Actions} = handle_flight_timer(State0),
+ {keep_state, State, Actions};
cipher(info, Event, State) ->
- handle_info(Event, cipher, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event,
#state{connection_states = ConnectionStates0} = State) ->
ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read),
ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read),
- ssl_connection:cipher(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE);
-cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) ->
- ssl_connection:cipher(Type, Event,
- prepare_flight(State#state{connection_states = ConnectionStates,
- flight_state = connection}),
- ?MODULE);
+ ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE);
+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);
cipher(state_timeout, Event, State) ->
- handle_state_timeout(Event, cipher, State);
+ handle_state_timeout(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- ssl_connection:cipher(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(),
+ #hello_request{} | #client_hello{}| term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection(enter, _, State) ->
+ {keep_state, State};
connection(info, Event, State) ->
- handle_info(Event, connection, State);
-connection(internal, #hello_request{}, #state{host = Host, port = Port,
- session = #session{own_certificate = Cert} = Session0,
- session_cache = Cache, session_cache_cb = CacheCb,
- ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}} = State0) ->
+ gen_info(Event, ?FUNCTION_NAME, State);
+connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host,
+ port = Port,
+ data_tag = DataTag,
+ session_cache = Cache,
+ session_cache_cb = CacheCb
+ },
+ handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}},
+ connection_env = CEnv,
+ session = #session{own_certificate = Cert} = Session0,
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates0,
+ protocol_specific = PS
+ } = State0) ->
+
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
- {State1, Actions} = send_handshake(Hello, State0),
- {Record, State} =
- next_record(
- State1#state{session = Session0#session{session_id
- = Hello#client_hello.session_id}}),
- next_event(hello, Record, State, Actions);
-connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) ->
+ Version = Hello#client_hello.client_version,
+ HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.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)},
+ session = Session0#session{session_id
+ = Hello#client_hello.session_id}},
+ next_event(hello, no_record, State, Actions);
+connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {next_state, hello, State#state{allow_renegotiate = false}, [{next_event, internal, Hello}]};
-connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false} = State0) ->
+ {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},
+ 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(connection, Record, State);
+ 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}])
+ end;
connection(Type, Event, State) ->
- ssl_connection:connection(Type, Event, State, ?MODULE).
-
-downgrade(Type, Event, State) ->
- ssl_connection:downgrade(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+%%TODO does this make sense for DTLS ?
%%--------------------------------------------------------------------
-%% Description: This function is called by a gen_fsm when it receives any
-%% other message than a synchronous or asynchronous event
-%% (or a system message).
+-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
+downgrade(enter, _, State) ->
+ {keep_state, State};
+downgrade(Type, Event, State) ->
+ ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
-%% raw data from socket, unpack records
-handle_info({Protocol, _, _, _, Data}, StateName,
- #state{data_tag = Protocol} = State0) ->
- case next_dtls_record(Data, State0) of
- {Record, State} ->
- next_event(StateName, Record, State);
- #alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- {stop, {shutdown, own_alert}}
- end;
-handle_info({CloseTag, Socket}, StateName,
- #state{socket = Socket, close_tag = CloseTag,
- negotiated_version = Version} = 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 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,
- ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- {stop, {shutdown, transport_closed}};
-handle_info(Msg, StateName, State) ->
- ssl_connection:handle_info(Msg, StateName, State).
-
-handle_call(Event, From, StateName, State) ->
- ssl_connection:handle_call(Event, From, StateName, State, ?MODULE).
-
-handle_common_event(internal, #alert{} = Alert, StateName,
- #state{negotiated_version = Version} = State) ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State);
-%%% DTLS record protocol level handshake messages
-handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE,
- fragment = Data},
- StateName,
- #state{protocol_buffers = Buffers0,
- negotiated_version = Version} = State0) ->
- try
- case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of
- {[], Buffers} ->
- {Record, State} = next_record(State0#state{protocol_buffers = Buffers}),
- next_event(StateName, Record, State);
- {Packets, Buffers} ->
- State = State0#state{protocol_buffers = Buffers},
- Events = dtls_handshake_events(Packets),
- {next_state, StateName,
- State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
- end
- catch throw:#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
- end;
-%%% DTLS record protocol level application data messages
-handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]};
-%%% DTLS record protocol level change cipher messages
-handle_common_event(internal, #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_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{negotiated_version = Version} = State) ->
- case decode_alerts(EncAlerts) of
- Alerts = [_|_] ->
- handle_alerts(Alerts, {next_state, StateName, State});
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
- end;
-%% Ignore unknown TLS record level protocol messages
-handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State}.
-
-handle_state_timeout(flight_retransmission_timeout, StateName,
- #state{flight_state = {retransmit, NextTimeout}} = State0) ->
- {State1, Actions} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}},
- retransmit_epoch(StateName, State0)),
- {Record, State} = next_record(State1),
- next_event(StateName, Record, State, Actions).
-
-send(Transport, {_, {{_,_}, _} = Socket}, Data) ->
- send(Transport, Socket, Data);
-send(Transport, Socket, Data) ->
- dtls_socket:send(Transport, Socket, Data).
%%--------------------------------------------------------------------
-%% Description:This function is called by a gen_fsm when it is about
-%% to terminate. It should be the opposite of Module:init/1 and do any
-%% necessary cleaning up. When it returns, the gen_fsm terminates with
-%% Reason. The return value is ignored.
+%% gen_statem callbacks
%%--------------------------------------------------------------------
+callback_mode() ->
+ [state_functions, state_enter].
+
terminate(Reason, StateName, State) ->
ssl_connection:terminate(Reason, StateName, State).
-%%--------------------------------------------------------------------
-%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
@@ -552,57 +774,8 @@ format_status(Type, Data) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
- #state{connection_states = ConnectionStates0,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
- negotiated_protocol = CurrentProtocol,
- key_algorithm = KeyExAlg,
- ssl_options = SslOpts} = State0) ->
-
- case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
- ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
- #alert{} = Alert ->
- ssl_connection: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,
- negotiated_version = Version,
- hashsign_algorithm = HashSign,
- session = Session,
- negotiated_protocol = Protocol}),
-
- ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt},
- State, ?MODULE)
- end.
-
-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).
-
-encode_data(Data, Version, ConnectionStates0)->
- dtls_record:encode_data(Data, Version, ConnectionStates0).
-
-encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
- dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
-
-decode_alerts(Bin) ->
- ssl_alert:decode(Bin).
-
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
- {CbModule, DataTag, CloseTag, ErrorTag}) ->
+ {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
#ssl_options{beast_mitigation = BeastMitigation} = SSLOptions,
ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation),
@@ -612,39 +785,64 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
_ ->
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,
Monitor = erlang:monitor(process, User),
-
- #state{socket_options = SocketOptions,
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = ?MODULE,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ passive_tag = PassiveTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ session_cache_cb = SessionCacheCb
+ },
+
+ #state{static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first},
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation
+ },
+ connection_env = #connection_env{user_application = {Monitor, User}},
+ socket_options = SocketOptions,
%% 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#ssl_options{password = undefined},
session = #session{is_resumable = new},
- transport_cb = CbModule,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
- user_application = {Monitor, User},
- user_data_buffer = <<>>,
- session_cache_cb = SessionCacheCb,
- renegotiation = {false, first},
- allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
+ user_data_buffer = {[],0,[]},
start_or_recv_from = undefined,
- protocol_cb = ?MODULE,
flight_buffer = new_flight(),
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
+ protocol_specific = #{active_n => InternalActiveN,
+ active_n_toggle => true,
+ flight_state => initial_flight_state(DataTag)}
}.
-next_dtls_record(Data, #state{protocol_buffers = #protocol_buffers{
+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} = State0) ->
- case dtls_record:get_dtls_records(Data, Buf0) of
+ 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 =
@@ -654,13 +852,14 @@ next_dtls_record(Data, #state{protocol_buffers = #protocol_buffers{
Alert
end.
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
-next_record(#state{protocol_buffers =
- #protocol_buffers{dtls_cipher_texts = [CT | Rest]}
- = Buffers,
- connection_states = ConnStates0} = State) ->
+
+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 =
@@ -668,83 +867,198 @@ next_record(#state{protocol_buffers =
connection_states = ConnStates}};
#alert{} = Alert ->
{Alert, State}
- end;
-next_record(#state{role = server,
- socket = {Listener, {Client, _}},
- transport_cb = gen_udp} = State) ->
- dtls_udp_listener:active_once(Listener, Client, self()),
- {no_record, State};
-next_record(#state{role = client,
- socket = {_Server, Socket},
- transport_cb = Transport} = State) ->
- dtls_socket:setopts(Transport, Socket, [{active,once}]),
- {no_record, State};
-next_record(State) ->
- {no_record, State}.
+ end.
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
- {no_record ,State};
+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.
-next_record_if_active(State) ->
- next_record(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,
-passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
- case Buffer of
- <<>> ->
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State);
- _ ->
- {Record, State} = ssl_connection:read_application_data(<<>>, State0),
- next_event(StateName, Record, State)
+ 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.
-next_event(StateName, Record, State) ->
- next_event(StateName, Record, State, []).
-next_event(connection = StateName, no_record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case next_record_if_active(State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, 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, Epoch),
- {next_state, StateName, State, Actions ++ MoreActions};
- {#ssl_tls{epoch = _Epoch,
- version = _Version}, State} ->
- %% TODO maybe buffer later epoch
- {next_state, StateName, State, Actions};
- {#alert{} = Alert, State} ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
- end;
-next_event(StateName, Record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State, Actions) ->
- case Record of
- no_record ->
- {next_state, StateName, State, Actions};
- #ssl_tls{epoch = CurrentEpoch,
- version = Version} = Record ->
- {next_state, StateName,
- dtls_version(StateName, Version, State),
- [{next_event, internal, {protocol_record, Record}} | Actions]};
- #ssl_tls{epoch = _Epoch,
- version = _Version} = _Record ->
- %% TODO maybe buffer later epoch
- {next_state, StateName, State, Actions};
+%% raw data from socket, unpack records
+handle_info({Protocol, _, _, _, Data}, StateName,
+ #state{static_env = #static_env{data_tag = Protocol}} = State0) ->
+ case next_dtls_record(Data, StateName, State0) of
+ {Record, State} ->
+ next_event(StateName, Record, State);
#alert{} = Alert ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+ ssl_connection:handle_normal_shutdown(Alert, 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{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,
+ ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), 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),
+ %% 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 = Options} = State0) ->
+ case ignore_alert(Alert, State0) of
+ {true, State} ->
+ log_ignore_alert(Options#ssl_options.log_level, 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
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
end.
-dtls_version(hello, Version, #state{role = server} = State) ->
- State#state{negotiated_version = Version}; %%Inital version
-dtls_version(_,_, State) ->
- State.
+gen_info(Event, connection = 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(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.
+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 =
@@ -765,77 +1079,179 @@ next_flight(Flight) ->
Flight#{handshakes => [],
change_cipher_spec => undefined,
handshakes_after_change_cipher_spec => []}.
-
-start_flight(#state{transport_cb = gen_udp,
- flight_state = {retransmit, Timeout}} = State) ->
+
+handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
+ protocol_specific = #{flight_state := {retransmit, Timeout}}} = State) ->
start_retransmision_timer(Timeout, State);
-start_flight(#state{transport_cb = gen_udp,
- flight_state = connection} = State) ->
+handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
+ protocol_specific = #{flight_state := connection}} = State) ->
{State, []};
-start_flight(State) ->
+handle_flight_timer(#state{protocol_specific = #{flight_state := reliable}} = State) ->
%% No retransmision needed i.e DTLS over SCTP
- {State#state{flight_state = reliable}, []}.
+ {State, []}.
-start_retransmision_timer(Timeout, State) ->
- {State#state{flight_state = {retransmit, new_timeout(Timeout)}},
+start_retransmision_timer(Timeout, #state{protocol_specific = PS} = State) ->
+ {State#state{protocol_specific = PS#{flight_state => {retransmit, new_timeout(Timeout)}}},
[{state_timeout, Timeout, flight_retransmission_timeout}]}.
-new_timeout(N) when N =< 30 ->
+new_timeout(N) when N =< 30000 ->
N * 2;
new_timeout(_) ->
- 60.
+ 60000.
-dtls_handshake_events(Packets) ->
- lists:map(fun(Packet) ->
- {next_event, internal, {handshake, Packet}}
- end, Packets).
+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 = #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}, []};
-renegotiate(#state{role = client} = State, Actions) ->
- %% Handle same way as if server requested
- %% the renegotiation
- Hs0 = ssl_handshake:init_handshake_history(),
- {next_state, connection, State#state{tls_handshake_history = Hs0,
- protocol_buffers = #protocol_buffers{}},
- [{next_event, internal, #hello_request{}} | Actions]};
+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 = #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),
-renegotiate(#state{role = server,
- connection_states = CS0} = State0, Actions) ->
- HelloRequest = ssl_handshake:hello_request(),
- CS = CS0#{write_msg_seq => 0},
- {State1, MoreActions} = send_handshake(HelloRequest,
- State0#state{connection_states =
- CS}),
- Hs0 = ssl_handshake:init_handshake_history(),
- {Record, State} = next_record(State1#state{tls_handshake_history = Hs0,
- protocol_buffers = #protocol_buffers{}}),
- next_event(hello, Record, State, Actions ++ MoreActions).
+ send(Transport, Socket, [HsBefore, EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ {State0#state{connection_states = ConnectionStates}, []};
-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)).
+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 = #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 = #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.
-
-update_handshake_history(#hello_verify_request{}, _, Hist) ->
- Hist;
-update_handshake_history(_, Handshake, Hist) ->
- %% DTLS never needs option "v2_hello_compatible" to be true
- ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake), false).
+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(debug, StateName, Alert, Role) ->
+ Txt = ssl_alert:alert_txt(Alert),
+ ?LOG_ERROR("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n",
+ [Role, StateName, Txt]);
+log_ignore_alert(_, _, _, _) ->
+ ok.
+
+send_application_data(Data, From, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = HsEnv,
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{renegotiate_at = RenegotiateAt,
+ log_level = LogLevel}} = State0) ->
+
+ case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
+ true ->
+ renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}},
+ [{next_event, {call, From}, {application_data, Data}}]);
+ false ->
+ {Msgs, ConnectionStates} =
+ dtls_record:encode_data(Data, Version, ConnectionStates0),
+ State = State0#state{connection_states = ConnectionStates},
+ case send(Transport, Socket, Msgs) of
+ ok ->
+ ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
+ ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]);
+ Result ->
+ ssl_connection:hibernate_after(connection, State, [{reply, From, Result}])
+ end
+ 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.
+time_to_renegotiate(_Data,
+ #{current_write := #{sequence_number := Num}},
+ RenegotiateAt) ->
+
+ %% We could do test:
+ %% is_time_to_renegotiate((erlang:byte_size(_Data) div
+ %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to
+ %% have a some what lower renegotiateAt and a much cheaper test
+ is_time_to_renegotiate(Num, RenegotiateAt).
+
+is_time_to_renegotiate(N, M) when N < M->
+ false;
+is_time_to_renegotiate(_,_) ->
+ true.
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index a94954d8f2..4a381745d4 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-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.
@@ -16,6 +16,11 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
+
+%%----------------------------------------------------------------------
+%% Purpose: Help funtions for handling the DTLS (specific parts of)
+%%% SSL/TLS/DTLS handshake protocol
+%%----------------------------------------------------------------------
-module(dtls_handshake).
-include("dtls_connection.hrl").
@@ -24,18 +29,24 @@
-include("ssl_internal.hrl").
-include("ssl_alert.hrl").
+%% Handshake handling
-export([client_hello/8, client_hello/9, cookie/4, hello/4,
- hello_verify_request/2, get_dtls_handshake/3, fragment_handshake/2,
- handshake_bin/2, encode_handshake/3]).
+ 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{} |
ssl_handshake:ssl_handshake().
%%====================================================================
-%% Internal application API
+%% Handshake handling
%%====================================================================
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), ssl_record:connection_states(),
+-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
#ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
#client_hello{}.
%%
@@ -48,7 +59,7 @@ client_hello(Host, Port, ConnectionStates, SslOpts,
Cache, CacheCb, Renegotiation, OwnCert).
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), term(), ssl_record:connection_states(),
+-spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(),
#ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
#client_hello{}.
%%
@@ -56,7 +67,8 @@ client_hello(Host, Port, ConnectionStates, SslOpts,
%%--------------------------------------------------------------------
client_hello(Host, Port, Cookie, ConnectionStates,
#ssl_options{versions = Versions,
- ciphers = UserSuites
+ ciphers = UserSuites,
+ fallback = Fallback
} = SslOpts,
Cache, CacheCb, Renegotiation, OwnCert) ->
Version = dtls_record:highest_protocol_version(Versions),
@@ -65,14 +77,16 @@ client_hello(Host, Port, Cookie, ConnectionStates,
TLSVersion = dtls_v1:corresponding_tls_version(Version),
CipherSuites = ssl_handshake:available_suites(UserSuites, TLSVersion),
- Extensions = ssl_handshake:client_hello_extensions(Host, TLSVersion, CipherSuites,
- SslOpts, ConnectionStates, Renegotiation),
-
+ Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites,
+ SslOpts, ConnectionStates,
+ Renegotiation, undefined),
Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
#client_hello{session_id = Id,
client_version = Version,
- cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation),
+ cipher_suites =
+ ssl_handshake:cipher_suites(CipherSuites,
+ Renegotiation, Fallback),
compression_methods = ssl_record:compressions(),
random = SecParams#security_parameters.client_random,
cookie = Cookie,
@@ -88,11 +102,11 @@ hello(#server_hello{server_version = Version, random = Random,
case dtls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation);
+ Compression, HelloExt, SslOpt,
+ ConnectionStates0, Renegotiation);
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
end;
-
hello(#client_hello{client_version = ClientVersion} = Hello,
#ssl_options{versions = Versions} = SslOpts,
Info, Renegotiation) ->
@@ -108,8 +122,8 @@ cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor},
<<?BYTE(Major), ?BYTE(Minor)>>,
Random, SessionId, CipherSuites, CompressionMethods],
crypto:hmac(sha, Key, CookieData).
-
--spec hello_verify_request(binary(), dtls_record:dtls_version()) -> #hello_verify_request{}.
+%%--------------------------------------------------------------------
+-spec hello_verify_request(binary(), ssl_record:ssl_version()) -> #hello_verify_request{}.
%%
%% Description: Creates a hello verify request message sent by server to
%% verify client
@@ -118,11 +132,8 @@ hello_verify_request(Cookie, Version) ->
#hello_verify_request{protocol_version = Version, cookie = Cookie}.
%%--------------------------------------------------------------------
-
-encode_handshake(Handshake, Version, Seq) ->
- {MsgType, Bin} = enc_handshake(Handshake, Version),
- Len = byte_size(Bin),
- [MsgType, ?uint24(Len), ?uint16(Seq), ?uint24(0), ?uint24(Len), Bin].
+%%% Handshake encoding
+%%--------------------------------------------------------------------
fragment_handshake(Bin, _) when is_binary(Bin)->
%% This is the change_cipher_spec not a "real handshake" but part of the flight
@@ -130,50 +141,60 @@ fragment_handshake(Bin, _) when is_binary(Bin)->
fragment_handshake([MsgType, Len, Seq, _, Len, Bin], Size) ->
Bins = bin_fragments(Bin, Size),
handshake_fragments(MsgType, Seq, Len, Bins, []).
+encode_handshake(Handshake, Version, Seq) ->
+ {MsgType, Bin} = enc_handshake(Handshake, Version),
+ Len = byte_size(Bin),
+ [MsgType, ?uint24(Len), ?uint16(Seq), ?uint24(0), ?uint24(Len), Bin].
+
+%%--------------------------------------------------------------------
+%%% Handshake decodeing
+%%--------------------------------------------------------------------
-handshake_bin([Type, Length, Data], Seq) ->
- handshake_bin(Type, Length, Seq, Data).
-
%%--------------------------------------------------------------------
--spec get_dtls_handshake(dtls_record:dtls_version(), binary(), #protocol_buffers{}) ->
+-spec get_dtls_handshake(ssl_record:ssl_version(), binary(), #protocol_buffers{}, #ssl_options{}) ->
{[dtls_handshake()], #protocol_buffers{}}.
%%
%% Description: Given buffered and new data from dtls_record, collects
%% and returns it as a list of handshake messages, also returns
%% possible leftover data in the new "protocol_buffers".
%%--------------------------------------------------------------------
-get_dtls_handshake(Version, Fragment, ProtocolBuffers) ->
- handle_fragments(Version, Fragment, ProtocolBuffers, []).
+get_dtls_handshake(Version, Fragment, ProtocolBuffers, Options) ->
+ handle_fragments(Version, Fragment, ProtocolBuffers, Options, []).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-handle_client_hello(Version, #client_hello{session_id = SugesstedId,
- cipher_suites = CipherSuites,
- compression_methods = Compressions,
- random = Random,
- extensions =
- #hello_extensions{elliptic_curves = Curves,
- signature_algs = ClientHashSigns} = HelloExt},
+handle_client_hello(Version,
+ #client_hello{session_id = SugesstedId,
+ cipher_suites = CipherSuites,
+ compression_methods = Compressions,
+ random = Random,
+ extensions = HelloExt},
#ssl_options{versions = Versions,
- signature_algs = SupportedHashSigns} = SslOpts,
- {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) ->
+ signature_algs = SupportedHashSigns,
+ eccs = SupportedECCs,
+ honor_ecc_order = ECCOrder} = SslOpts,
+ {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _},
+ Renegotiation) ->
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),
- ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(TLSVersion)),
+ ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite} = Session1}
- = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions,
+ = ssl_handshake:select_session(SugesstedId, CipherSuites,
+ AvailableHashSigns, Compressions,
Port, Session0#session{ecc = ECCCurve}, TLSVersion,
SslOpts, Cache, CacheCb, Cert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
- {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
- case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg,
+ #{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, Cert, KeyExAlg,
SupportedHashSigns, TLSVersion) of
#alert{} = Alert ->
Alert;
@@ -191,9 +212,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) ->
try ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites,
HelloExt, dtls_v1:corresponding_tls_version(Version),
- SslOpts, Session0, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ SslOpts, Session0,
+ ConnectionStates0, Renegotiation) of
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
catch throw:Alert ->
@@ -202,18 +222,17 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
- Compression, HelloExt,
- dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ 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}
+ catch throw:Alert ->
+ Alert
end.
-
-%%%%%%% Encodeing %%%%%%%%%%%%%
+%%--------------------------------------------------------------------
enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
cookie = Cookie}, _Version) ->
@@ -221,7 +240,6 @@ enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
{?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor),
?BYTE(CookieLength),
Cookie:CookieLength/binary>>};
-
enc_handshake(#hello_request{}, _Version) ->
{?HELLO_REQUEST, <<>>};
enc_handshake(#client_hello{client_version = {Major, Minor},
@@ -237,26 +255,37 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions,
+ dtls_v1:corresponding_tls_version({Major, Minor})),
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
?BYTE(CookieLength), Cookie/binary,
?UINT16(CsLength), BinCipherSuites/binary,
?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
-
enc_handshake(#server_hello{} = HandshakeMsg, Version) ->
{Type, <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>} =
ssl_handshake:encode_handshake(HandshakeMsg, Version),
{DTLSMajor, DTLSMinor} = dtls_v1:corresponding_dtls_version({Major, Minor}),
{Type, <<?BYTE(DTLSMajor), ?BYTE(DTLSMinor), Rest/binary>>};
-
enc_handshake(HandshakeMsg, Version) ->
- ssl_handshake:encode_handshake(HandshakeMsg, Version).
+ ssl_handshake:encode_handshake(HandshakeMsg, dtls_v1:corresponding_tls_version(Version)).
+handshake_bin(#handshake_fragment{
+ type = Type,
+ length = Len,
+ message_seq = Seq,
+ fragment_length = Len,
+ fragment_offset = 0,
+ fragment = Fragment}) ->
+ handshake_bin(Type, Len, Seq, Fragment).
+handshake_bin(Type, Length, Seq, FragmentData) ->
+ <<?BYTE(Type), ?UINT24(Length),
+ ?UINT16(Seq), ?UINT24(0), ?UINT24(Length),
+ FragmentData:Length/binary>>.
+
bin_fragments(Bin, Size) ->
bin_fragments(Bin, size(Bin), Size, 0, []).
-
bin_fragments(Bin, BinSize, FragSize, Offset, Fragments) ->
case (BinSize - Offset - FragSize) > 0 of
true ->
@@ -280,22 +309,23 @@ address_to_bin({A,B,C,D}, Port) ->
address_to_bin({A,B,C,D,E,F,G,H}, Port) ->
<<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>.
-%%%%%%% Decodeing %%%%%%%%%%%%%
+%%--------------------------------------------------------------------
-handle_fragments(Version, FragmentData, Buffers0, Acc) ->
+handle_fragments(Version, FragmentData, Buffers0, Options, Acc) ->
Fragments = decode_handshake_fragments(FragmentData),
- do_handle_fragments(Version, Fragments, Buffers0, Acc).
+ do_handle_fragments(Version, Fragments, Buffers0, Options, Acc).
-do_handle_fragments(_, [], Buffers, Acc) ->
+do_handle_fragments(_, [], Buffers, _Options, Acc) ->
{lists:reverse(Acc), Buffers};
-do_handle_fragments(Version, [Fragment | Fragments], Buffers0, Acc) ->
+do_handle_fragments(Version, [Fragment | Fragments], Buffers0, Options, Acc) ->
case reassemble(Version, Fragment, Buffers0) of
{more_data, Buffers} when Fragments == [] ->
{lists:reverse(Acc), Buffers};
{more_data, Buffers} ->
- do_handle_fragments(Version, Fragments, Buffers, Acc);
- {HsPacket, Buffers} ->
- do_handle_fragments(Version, Fragments, Buffers, [HsPacket | Acc])
+ do_handle_fragments(Version, Fragments, Buffers, Options, Acc);
+ {{Handshake, _} = HsPacket, Buffers} ->
+ ssl_logger:debug(Options#ssl_options.log_level, inbound, 'handshake', Handshake),
+ do_handle_fragments(Version, Fragments, Buffers, Options, [HsPacket | Acc])
end.
decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) ->
@@ -303,7 +333,7 @@ decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) ->
decode_handshake(_, ?HELLO_REQUEST, <<>>) ->
#hello_request{};
-decode_handshake(_Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
+decode_handshake(Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
?UINT24(_), ?UINT24(_),
?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
@@ -311,8 +341,10 @@ decode_handshake(_Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
?UINT16(Cs_length), CipherSuites:Cs_length/binary,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
Extensions/binary>>) ->
-
- DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}),
+ TLSVersion = dtls_v1:corresponding_tls_version(Version),
+ LegacyVersion = dtls_v1:corresponding_tls_version({Major, Minor}),
+ Exts = ssl_handshake:decode_vector(Extensions),
+ DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, TLSVersion, LegacyVersion, client),
#client_hello{
client_version = {Major,Minor},
@@ -323,7 +355,6 @@ decode_handshake(_Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
compression_methods = Comp_methods,
extensions = DecodedExtensions
};
-
decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?UINT24(_), ?UINT16(_),
?UINT24(_), ?UINT24(_),
?BYTE(Major), ?BYTE(Minor),
@@ -331,13 +362,12 @@ decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?UINT24(_), ?UINT16(_),
Cookie:CookieLength/binary>>) ->
#hello_verify_request{protocol_version = {Major, Minor},
cookie = Cookie};
-
decode_handshake(Version, Tag, <<?UINT24(_), ?UINT16(_),
?UINT24(_), ?UINT24(_), Msg/binary>>) ->
%% DTLS specifics stripped
- decode_tls_thandshake(Version, Tag, Msg).
+ decode_tls_handshake(Version, Tag, Msg).
-decode_tls_thandshake(Version, Tag, Msg) ->
+decode_tls_handshake(Version, Tag, Msg) ->
TLSVersion = dtls_v1:corresponding_tls_version(Version),
ssl_handshake:decode_handshake(TLSVersion, Tag, Msg).
@@ -371,9 +401,10 @@ reassemble(Version, #handshake_fragment{message_seq = Seq} = Fragment,
end;
reassemble(_, #handshake_fragment{message_seq = FragSeq} = Fragment,
#protocol_buffers{dtls_handshake_next_seq = Seq,
- dtls_handshake_later_fragments = LaterFragments} = Buffers0) when FragSeq > Seq->
- {more_data,
- Buffers0#protocol_buffers{dtls_handshake_later_fragments = [Fragment | LaterFragments]}};
+ dtls_handshake_later_fragments = LaterFragments}
+ = Buffers0) when FragSeq > Seq->
+ {more_data,
+ Buffers0#protocol_buffers{dtls_handshake_later_fragments = [Fragment | LaterFragments]}};
reassemble(_, _, Buffers) ->
%% Disregard fragments FragSeq < Seq
{more_data, Buffers}.
@@ -398,90 +429,156 @@ merge_fragment(Frag0, [Frag1 | Rest]) ->
merge_fragment(Frag, Rest)
end.
-is_complete_handshake(#handshake_fragment{length = Length, fragment_length = Length}) ->
- true;
-is_complete_handshake(_) ->
- false.
-
-next_fragments(LaterFragments) ->
- case lists:keysort(#handshake_fragment.message_seq, LaterFragments) of
- [] ->
- {[], []};
- [#handshake_fragment{message_seq = Seq} | _] = Fragments ->
- split_frags(Fragments, Seq, [])
- end.
-
-split_frags([#handshake_fragment{message_seq = Seq} = Frag | Rest], Seq, Acc) ->
- split_frags(Rest, Seq, [Frag | Acc]);
-split_frags(Frags, _, Acc) ->
- {lists:reverse(Acc), Frags}.
-
-%% Duplicate
+%% Duplicate (fully contained fragment)
+%% 2,5 _ _ P P P P P
+%% 2,5 _ _ C C C C C
merge_fragments(#handshake_fragment{
- fragment_offset = PreviousOffSet,
+ fragment_offset = PreviousOffSet,
fragment_length = PreviousLen,
fragment = PreviousData
- } = Previous,
+ } = Previous,
#handshake_fragment{
fragment_offset = PreviousOffSet,
fragment_length = PreviousLen,
fragment = PreviousData}) ->
Previous;
-%% Lager fragment save new data
+%% Duplicate (fully contained fragment)
+%% 2,5 _ _ P P P P P
+%% 2,2 _ _ C C
+%% 0,3 X X X
+%% 5,3 _ _ _ _ _ X X X
merge_fragments(#handshake_fragment{
- fragment_offset = PreviousOffSet,
- fragment_length = PreviousLen,
+ fragment_offset = PreviousOffset,
+ fragment_length = PreviousLen
+ } = Previous,
+ #handshake_fragment{
+ fragment_offset = CurrentOffset,
+ fragment_length = CurrentLen})
+ when PreviousOffset =< CurrentOffset andalso
+ CurrentOffset =< PreviousOffset + PreviousLen andalso
+ CurrentOffset + CurrentLen =< PreviousOffset + PreviousLen ->
+ Previous;
+
+%% Fully overlapping fragments
+%% 2,5 _ _ P P P P P
+%% 0,8 C C C C C C C C
+merge_fragments(#handshake_fragment{
+ fragment_offset = PreviousOffset,
+ fragment_length = PreviousLen
+ },
+ #handshake_fragment{
+ fragment_offset = CurrentOffset,
+ fragment_length = CurrentLen} = Current)
+ when CurrentOffset =< PreviousOffset andalso
+ CurrentOffset + CurrentLen >= PreviousOffset + PreviousLen ->
+ Current;
+
+%% Overlapping fragments
+%% 2,5 _ _ P P P P P
+%% 0,3 C C C
+merge_fragments(#handshake_fragment{
+ fragment_offset = PreviousOffset,
+ fragment_length = PreviousLen,
fragment = PreviousData
- } = Previous,
- #handshake_fragment{
- fragment_offset = PreviousOffSet,
- fragment_length = CurrentLen,
- fragment = CurrentData}) when CurrentLen > PreviousLen ->
- NewLength = CurrentLen - PreviousLen,
- <<_:PreviousLen/binary, NewData/binary>> = CurrentData,
+ } = Previous,
+ #handshake_fragment{
+ fragment_offset = CurrentOffset,
+ fragment_length = CurrentLen,
+ fragment = CurrentData})
+ when CurrentOffset < PreviousOffset andalso
+ CurrentOffset + CurrentLen < PreviousOffset + PreviousLen ->
+ NewDataLen = PreviousOffset - CurrentOffset,
+ <<NewData:NewDataLen/binary, _/binary>> = CurrentData,
Previous#handshake_fragment{
- fragment_length = PreviousLen + NewLength,
+ fragment_length = PreviousLen + NewDataLen,
+ fragment = <<NewData/binary, PreviousData/binary>>
+ };
+
+%% Overlapping fragments
+%% 2,5 _ _ P P P P P
+%% 5,3 _ _ _ _ _ C C C
+merge_fragments(#handshake_fragment{
+ fragment_offset = PreviousOffset,
+ fragment_length = PreviousLen,
+ fragment = PreviousData
+ } = Previous,
+ #handshake_fragment{
+ fragment_offset = CurrentOffset,
+ fragment_length = CurrentLen,
+ fragment = CurrentData})
+ when CurrentOffset > PreviousOffset andalso
+ CurrentOffset < PreviousOffset + PreviousLen ->
+ NewDataLen = CurrentOffset + CurrentLen - (PreviousOffset + PreviousLen),
+ DropLen = CurrentLen - NewDataLen,
+ <<_:DropLen/binary, NewData/binary>> = CurrentData,
+ Previous#handshake_fragment{
+ fragment_length = PreviousLen + NewDataLen,
fragment = <<PreviousData/binary, NewData/binary>>
};
-%% Smaller fragment
+%% Adjacent fragments
+%% 2,5 _ _ P P P P P
+%% 7,3 _ _ _ _ _ _ _ C C C
merge_fragments(#handshake_fragment{
- fragment_offset = PreviousOffSet,
- fragment_length = PreviousLen
- } = Previous,
- #handshake_fragment{
- fragment_offset = PreviousOffSet,
- fragment_length = CurrentLen}) when CurrentLen < PreviousLen ->
- Previous;
-%% Next fragment
+ fragment_offset = PreviousOffset,
+ fragment_length = PreviousLen,
+ fragment = PreviousData
+ } = Previous,
+ #handshake_fragment{
+ fragment_offset = CurrentOffset,
+ fragment_length = CurrentLen,
+ fragment = CurrentData})
+ when CurrentOffset =:= PreviousOffset + PreviousLen ->
+ Previous#handshake_fragment{
+ fragment_length = PreviousLen + CurrentLen,
+ fragment = <<PreviousData/binary, CurrentData/binary>>
+ };
+
+%% Adjacent fragments
+%% 2,5 _ _ P P P P P
+%% 0,2 C C
merge_fragments(#handshake_fragment{
- fragment_offset = PreviousOffSet,
- fragment_length = PreviousLen,
+ fragment_offset = PreviousOffset,
+ fragment_length = PreviousLen,
fragment = PreviousData
- } = Previous,
- #handshake_fragment{
- fragment_offset = CurrentOffSet,
- fragment_length = CurrentLen,
- fragment = CurrentData}) when PreviousOffSet + PreviousLen == CurrentOffSet->
- Previous#handshake_fragment{
- fragment_length = PreviousLen + CurrentLen,
- fragment = <<PreviousData/binary, CurrentData/binary>>};
+ } = Previous,
+ #handshake_fragment{
+ fragment_offset = CurrentOffset,
+ fragment_length = CurrentLen,
+ fragment = CurrentData})
+ when PreviousOffset =:= CurrentOffset + CurrentLen ->
+ Previous#handshake_fragment{
+ fragment_length = PreviousLen + CurrentLen,
+ fragment = <<CurrentData/binary, PreviousData/binary>>
+ };
+
%% No merge there is a gap
+%% 3,5 _ _ _ P P P P
+%% 0,2 C C
merge_fragments(Previous, Current) ->
[Previous, Current].
-
-handshake_bin(#handshake_fragment{
- type = Type,
- length = Len,
- message_seq = Seq,
- fragment_length = Len,
- fragment_offset = 0,
- fragment = Fragment}) ->
- handshake_bin(Type, Len, Seq, Fragment).
-handshake_bin(Type, Length, Seq, FragmentData) ->
- <<?BYTE(Type), ?UINT24(Length),
- ?UINT16(Seq), ?UINT24(0), ?UINT24(Length),
- FragmentData:Length/binary>>.
+next_fragments(LaterFragments) ->
+ case lists:keysort(#handshake_fragment.message_seq, LaterFragments) of
+ [] ->
+ {[], []};
+ [#handshake_fragment{message_seq = Seq} | _] = Fragments ->
+ split_frags(Fragments, Seq, [])
+ end.
+
+split_frags([#handshake_fragment{message_seq = Seq} = Frag | Rest], Seq, Acc) ->
+ split_frags(Rest, Seq, [Frag | Acc]);
+split_frags(Frags, _, Acc) ->
+ {lists:reverse(Acc), Frags}.
+
+is_complete_handshake(#handshake_fragment{length = Length, fragment_length = Length}) ->
+ true;
+is_complete_handshake(_) ->
+ false.
+
+
+
+
+
diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl
index 0a980c5f31..de2be1daeb 100644
--- a/lib/ssl/src/dtls_handshake.hrl
+++ b/lib/ssl/src/dtls_handshake.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2017. 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.
@@ -26,20 +26,12 @@
-ifndef(dtls_handshake).
-define(dtls_handshake, true).
+-include("tls_handshake.hrl"). %% Common TLS and DTLS records and Constantes
-include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes
+-include("ssl_api.hrl").
-define(HELLO_VERIFY_REQUEST, 3).
-
--record(client_hello, {
- client_version,
- random,
- session_id, % opaque SessionID<0..32>
- cookie, % opaque<2..2^16-1>
- cipher_suites, % cipher_suites<2..2^16-1>
- compression_methods, % compression_methods<1..2^8-1>,
- %% Extensions
- extensions
- }).
+-define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}).
-record(hello_verify_request, {
protocol_version,
@@ -55,4 +47,11 @@
fragment
}).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 7764 Datagram Transport Layer Security (DTLS) Extension to Establish Keys
+%% for the Secure Real-time Transport Protocol (SRTP)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(USE_SRTP, 14).
+
-endif. % -ifdef(dtls_handshake).
diff --git a/lib/ssl/src/dtls_udp_sup.erl b/lib/ssl/src/dtls_listener_sup.erl
index 197882e92f..dc30696a2c 100644
--- a/lib/ssl/src/dtls_udp_sup.erl
+++ b/lib/ssl/src/dtls_listener_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2016-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.
@@ -23,7 +23,7 @@
%% Purpose: Supervisor for a procsses dispatching upd datagrams to
%% correct DTLS handler
%%----------------------------------------------------------------------
--module(dtls_udp_sup).
+-module(dtls_listener_sup).
-behaviour(supervisor).
@@ -52,10 +52,10 @@ init(_O) ->
MaxT = 3600,
Name = undefined, % As simple_one_for_one is used.
- StartFunc = {dtls_udp_listener, start_link, []},
+ StartFunc = {dtls_packet_demux, start_link, []},
Restart = temporary, % E.g. should not be restarted
Shutdown = 4000,
- Modules = [dtls_udp_listener],
+ Modules = [dtls_packet_demux],
Type = worker,
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
diff --git a/lib/ssl/src/dtls_udp_listener.erl b/lib/ssl/src/dtls_packet_demux.erl
index f0ace2d887..94b350eaa5 100644
--- a/lib/ssl/src/dtls_udp_listener.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2018. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -19,21 +19,26 @@
%%
--module(dtls_udp_listener).
+-module(dtls_packet_demux).
-behaviour(gen_server).
+-include("ssl_internal.hrl").
+-include_lib("kernel/include/logger.hrl").
+
%% API
--export([start_link/4, active_once/3, accept/2, sockname/1, close/1,
- get_all_opts/1]).
+-export([start_link/5, active_once/3, accept/2, sockname/1, close/1,
+ get_all_opts/1, get_sock_opts/2, set_sock_opts/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state,
- {port,
- listner,
+ {active_n,
+ port,
+ listener,
+ transport,
dtls_options,
emulated_options,
dtls_msq_queues = kv_new(),
@@ -48,45 +53,59 @@
%%% API
%%%===================================================================
-start_link(Port, EmOpts, InetOptions, DTLSOptions) ->
- gen_server:start_link(?MODULE, [Port, EmOpts, InetOptions, DTLSOptions], []).
+start_link(Port, TransportInfo, EmOpts, InetOptions, DTLSOptions) ->
+ gen_server:start_link(?MODULE, [Port, TransportInfo, EmOpts, InetOptions, DTLSOptions], []).
-active_once(UDPConnection, Client, Pid) ->
- gen_server:cast(UDPConnection, {active_once, Client, Pid}).
+active_once(PacketSocket, Client, Pid) ->
+ gen_server:cast(PacketSocket, {active_once, Client, Pid}).
-accept(UDPConnection, Accepter) ->
- call(UDPConnection, {accept, Accepter}).
+accept(PacketSocket, Accepter) ->
+ call(PacketSocket, {accept, Accepter}).
-sockname(UDPConnection) ->
- call(UDPConnection, sockname).
-close(UDPConnection) ->
- call(UDPConnection, close).
-get_all_opts(UDPConnection) ->
- call(UDPConnection, get_all_opts).
+sockname(PacketSocket) ->
+ call(PacketSocket, sockname).
+close(PacketSocket) ->
+ call(PacketSocket, close).
+get_sock_opts(PacketSocket, SplitSockOpts) ->
+ call(PacketSocket, {get_sock_opts, SplitSockOpts}).
+get_all_opts(PacketSocket) ->
+ call(PacketSocket, get_all_opts).
+set_sock_opts(PacketSocket, Opts) ->
+ call(PacketSocket, {set_sock_opts, Opts}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
-init([Port, EmOpts, InetOptions, DTLSOptions]) ->
+init([Port, {TransportModule, _,_,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) ->
try
- {ok, Socket} = gen_udp:open(Port, InetOptions),
- {ok, #state{port = Port,
+ {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,
- listner = Socket,
+ listener = Socket,
close = false}}
catch _:_ ->
- {error, closed}
+ {stop, {shutdown, {error, closed}}}
end.
handle_call({accept, _}, _, #state{close = true} = State) ->
{reply, {error, closed}, State};
-handle_call({accept, Accepter}, From, #state{first = true,
+handle_call({accept, Accepter}, From, #state{active_n = N,
+ first = true,
accepters = Accepters,
- listner = Socket} = State0) ->
- next_datagram(Socket),
+ listener = Socket} = State0) ->
+ next_datagram(Socket, N),
State = State0#state{first = false,
accepters = queue:in({Accepter, From}, Accepters)},
{noreply, State};
@@ -94,7 +113,7 @@ handle_call({accept, Accepter}, From, #state{first = true,
handle_call({accept, Accepter}, From, #state{accepters = Accepters} = State0) ->
State = State0#state{accepters = queue:in({Accepter, From}, Accepters)},
{noreply, State};
-handle_call(sockname, _, #state{listner = Socket} = State) ->
+handle_call(sockname, _, #state{listener = Socket} = State) ->
Reply = inet:sockname(Socket),
{reply, Reply, State};
handle_call(close, _, #state{dtls_processes = Processes,
@@ -108,30 +127,62 @@ handle_call(close, _, #state{dtls_processes = Processes,
end, queue:to_list(Accepters)),
{reply, ok, State#state{close = true, accepters = queue:new()}}
end;
+handle_call({get_sock_opts, {SocketOptNames, EmOptNames}}, _, #state{listener = Socket,
+ emulated_options = EmOpts} = State) ->
+ case get_socket_opts(Socket, SocketOptNames) of
+ {ok, Opts} ->
+ {reply, {ok, emulated_opts_list(EmOpts, EmOptNames, []) ++ Opts}, State};
+ {error, Reason} ->
+ {reply, {error, Reason}, State}
+ end;
handle_call(get_all_opts, _, #state{dtls_options = DTLSOptions,
emulated_options = EmOpts} = State) ->
- {reply, {ok, EmOpts, DTLSOptions}, State}.
+ {reply, {ok, EmOpts, DTLSOptions}, State};
+handle_call({set_sock_opts, {SocketOpts, NewEmOpts}}, _, #state{listener = Socket, emulated_options = EmOpts0} = State) ->
+ set_socket_opts(Socket, SocketOpts),
+ EmOpts = do_set_emulated_opts(NewEmOpts, EmOpts0),
+ {reply, ok, State#state{emulated_options = EmOpts}}.
handle_cast({active_once, Client, Pid}, State0) ->
State = handle_active_once(Client, Pid, State0),
{noreply, State}.
-handle_info({udp, Socket, IP, InPortNo, _} = Msg, #state{listner = Socket} = State0) ->
+handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket, transport = {_,Transport,_,_,_}} = State0) ->
State = handle_datagram({IP, InPortNo}, Msg, State0),
- next_datagram(Socket),
{noreply, State};
+handle_info({PassiveTag, Socket},
+ #state{active_n = N,
+ listener = Socket,
+ transport = {_, _, _, _, PassiveTag}} = State) ->
+ next_datagram(Socket, N),
+ {noreply, State};
+%% UDP socket does not have a connection and should not receive an econnreset
+%% This does however happens on some windows versions. Just ignoring it
+%% appears to make things work as expected!
+handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error,_}} = State) ->
+ Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]),
+ ?LOG_NOTICE(Report),
+ {noreply, State};
+handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag,_}} = State) ->
+ Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]),
+ ?LOG_NOTICE(Report),
+ {noreply, State#state{close=true}};
+
handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients,
dtls_processes = Processes0,
+ dtls_msq_queues = MsgQueues0,
close = ListenClosed} = State) ->
Client = kv_get(Pid, Processes0),
Processes = kv_delete(Pid, Processes0),
+ MsgQueues = kv_delete(Client, MsgQueues0),
case ListenClosed andalso kv_empty(Processes) of
true ->
{stop, normal, State};
false ->
{noreply, State#state{clients = set_delete(Client, Clients),
- dtls_processes = Processes}}
+ dtls_processes = Processes,
+ dtls_msq_queues = MsgQueues}}
end.
terminate(_Reason, _State) ->
@@ -167,16 +218,16 @@ dispatch(Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) ->
Pid ! Msg,
State#state{dtls_msq_queues =
kv_update(Client, Queue, MsgQueues)};
- {{value, _}, Queue} ->
+ {{value, _UDP}, _Queue} ->
State#state{dtls_msq_queues =
- kv_update(Client, queue:in(Msg, Queue), MsgQueues)};
+ kv_update(Client, queue:in(Msg, Queue0), MsgQueues)};
{empty, Queue} ->
State#state{dtls_msq_queues =
kv_update(Client, queue:in(Msg, Queue), MsgQueues)}
end
end.
-next_datagram(Socket) ->
- inet:setopts(Socket, [{active, once}]).
+next_datagram(Socket, N) ->
+ inet:setopts(Socket, [{active, N}]).
handle_active_once(Client, Pid, #state{dtls_msq_queues = MsgQueues} = State0) ->
Queue0 = kv_get(Client, MsgQueues),
@@ -195,10 +246,10 @@ setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes,
dtls_msq_queues = MsgQueues,
dtls_options = DTLSOpts,
port = Port,
- listner = Socket,
+ listener = Socket,
emulated_options = EmOpts} = State) ->
ConnArgs = [server, "localhost", Port, {self(), {Client, Socket}},
- {DTLSOpts, EmOpts, udp_listner}, User, dtls_socket:default_cb_info()],
+ {DTLSOpts, EmOpts, dtls_listener}, User, dtls_socket:default_cb_info()],
case dtls_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
erlang:monitor(process, Pid),
@@ -247,3 +298,31 @@ call(Server, Msg) ->
exit:{{shutdown, _},_} ->
{error, closed}
end.
+
+set_socket_opts(_, []) ->
+ ok;
+set_socket_opts(Socket, SocketOpts) ->
+ inet:setopts(Socket, SocketOpts).
+
+get_socket_opts(_, []) ->
+ {ok, []};
+get_socket_opts(Socket, SocketOpts) ->
+ inet:getopts(Socket, SocketOpts).
+
+do_set_emulated_opts([], Opts) ->
+ Opts;
+do_set_emulated_opts([{mode, Value} | Rest], Opts) ->
+ do_set_emulated_opts(Rest, Opts#socket_options{mode = Value});
+do_set_emulated_opts([{active, N0} | Rest], Opts=#socket_options{active = Active}) when is_integer(N0) ->
+ N = tls_socket:update_active_n(N0, Active),
+ do_set_emulated_opts(Rest, Opts#socket_options{active = N});
+do_set_emulated_opts([{active, Value} | Rest], Opts) ->
+ do_set_emulated_opts(Rest, Opts#socket_options{active = Value}).
+
+emulated_opts_list(_,[], Acc) ->
+ Acc;
+emulated_opts_list( Opts, [mode | Rest], Acc) ->
+ emulated_opts_list(Opts, Rest, [{mode, Opts#socket_options.mode} | Acc]);
+emulated_opts_list(Opts, [active | Rest], Acc) ->
+ emulated_opts_list(Opts, Rest, [{active, Opts#socket_options.active} | Acc]).
+
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index f447897d59..8b8db7b2de 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. 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.
@@ -30,35 +30,35 @@
-include("ssl_cipher.hrl").
%% Handling of incoming data
--export([get_dtls_records/2, init_connection_states/2]).
+-export([get_dtls_records/4, init_connection_states/2, empty_connection_state/1]).
-%% Decoding
--export([decode_cipher_text/2]).
+-export([save_current_connection_state/2, next_epoch/2, get_connection_state_by_epoch/3, replay_detect/2,
+ init_connection_state_seq/2, current_connection_state_epoch/2]).
%% Encoding
-export([encode_handshake/4, encode_alert_record/3,
- encode_change_cipher_spec/3, encode_data/3]).
--export([encode_plain_text/5]).
+ encode_change_cipher_spec/3, encode_data/3, encode_plain_text/5]).
+
+%% Decoding
+-export([decode_cipher_text/2]).
%% 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_acceptable_version/2]).
+ is_acceptable_version/2, hello_version/2]).
--export([save_current_connection_state/2, next_epoch/2]).
--export([init_connection_state_seq/2, current_connection_state_epoch/2]).
+-export_type([dtls_atom_version/0]).
--export_type([dtls_version/0, dtls_atom_version/0]).
-
--type dtls_version() :: ssl_record:ssl_version().
-type dtls_atom_version() :: dtlsv1 | 'dtlsv1.2'.
+-define(REPLAY_WINDOW_SIZE, 64).
+
-compile(inline).
%%====================================================================
-%% Internal application API
+%% Handling of incoming data
%%====================================================================
%%--------------------------------------------------------------------
-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) ->
@@ -73,7 +73,7 @@ init_connection_states(Role, BeastMitigation) ->
Initial = initial_connection_state(ConnectionEnd, BeastMitigation),
Current = Initial#{epoch := 0},
InitialPending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation),
- Pending = InitialPending#{epoch => undefined},
+ Pending = empty_connection_state(InitialPending),
#{saved_read => Current,
current_read => Current,
pending_read => Pending,
@@ -81,6 +81,9 @@ init_connection_states(Role, BeastMitigation) ->
current_write => Current,
pending_write => Pending}.
+empty_connection_state(Empty) ->
+ Empty#{epoch => undefined, replay_window => init_replay_window(?REPLAY_WINDOW_SIZE)}.
+
%%--------------------------------------------------------------------
-spec save_current_connection_state(ssl_record:connection_states(), read | write) ->
ssl_record:connection_states().
@@ -96,11 +99,13 @@ save_current_connection_state(#{current_write := Current} = States, write) ->
next_epoch(#{pending_read := Pending,
current_read := #{epoch := Epoch}} = States, read) ->
- States#{pending_read := Pending#{epoch := Epoch + 1}};
+ States#{pending_read := Pending#{epoch := Epoch + 1,
+ replay_window := init_replay_window(?REPLAY_WINDOW_SIZE)}};
next_epoch(#{pending_write := Pending,
current_write := #{epoch := Epoch}} = States, write) ->
- States#{pending_write := Pending#{epoch := Epoch + 1}}.
+ States#{pending_write := Pending#{epoch := Epoch + 1,
+ replay_window := init_replay_window(?REPLAY_WINDOW_SIZE)}}.
get_connection_state_by_epoch(Epoch, #{current_write := #{epoch := Epoch} = Current},
write) ->
@@ -129,69 +134,51 @@ set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch
States#{saved_read := ReadState}.
%%--------------------------------------------------------------------
--spec get_dtls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}.
+-spec init_connection_state_seq(ssl_record:ssl_version(), ssl_record:connection_states()) ->
+ ssl_record:connection_state().
+%%
+%% Description: Copy the read sequence number to the write sequence number
+%% This is only valid for DTLS in the first client_hello
+%%--------------------------------------------------------------------
+init_connection_state_seq({254, _},
+ #{current_read := #{epoch := 0, sequence_number := Seq},
+ current_write := #{epoch := 0} = Write} = ConnnectionStates0) ->
+ ConnnectionStates0#{current_write => Write#{sequence_number => Seq}};
+init_connection_state_seq(_, ConnnectionStates) ->
+ ConnnectionStates.
+
+%%--------------------------------------------------------
+-spec current_connection_state_epoch(ssl_record:connection_states(), read | write) ->
+ integer().
+%%
+%% Description: Returns the epoch the connection_state record
+%% that is currently defined as the current connection state.
+%%--------------------------------------------------------------------
+current_connection_state_epoch(#{current_read := #{epoch := Epoch}},
+ read) ->
+ Epoch;
+current_connection_state_epoch(#{current_write := #{epoch := Epoch}},
+ write) ->
+ Epoch.
+
+%%--------------------------------------------------------------------
+-spec get_dtls_records(binary(), {atom(), atom(), ssl_record:ssl_version(), [ssl_record:ssl_version()]}, binary(),
+ #ssl_options{}) -> {[binary()], binary()} | #alert{}.
%%
%% Description: Given old buffer and new data from UDP/SCTP, packs up a records
%% and returns it as a list of tls_compressed binaries also returns leftover
%% data
%%--------------------------------------------------------------------
-get_dtls_records(Data, <<>>) ->
- get_dtls_records_aux(Data, []);
-get_dtls_records(Data, Buffer) ->
- get_dtls_records_aux(list_to_binary([Buffer, Data]), []).
+get_dtls_records(Data, Vinfo, Buffer, SslOpts) ->
+ BinData = list_to_binary([Buffer, Data]),
+ get_dtls_records_aux(Vinfo, BinData, [], SslOpts).
-get_dtls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
-get_dtls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length),
- Data:Length/binary, Rest/binary>>, Acc) when MajVer >= 128 ->
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
-get_dtls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary,
- Rest/binary>>, Acc) ->
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
-get_dtls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
-
-get_dtls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
- ?UINT16(Length), _/binary>>,
- _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
- ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-
-get_dtls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc)
- when Length0 > ?MAX_CIPHER_TEXT_LENGTH ->
- ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-
-get_dtls_records_aux(Data, Acc) ->
- case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
- true ->
- {lists:reverse(Acc), Data};
- false ->
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
- end.
+%%====================================================================
+%% Encoding DTLS records
+%%====================================================================
%%--------------------------------------------------------------------
--spec encode_handshake(iolist(), dtls_version(), integer(), ssl_record:connection_states()) ->
+-spec encode_handshake(iolist(), ssl_record:ssl_version(), integer(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%
%% Description: Encodes a handshake message to send on the ssl-socket.
@@ -201,7 +188,7 @@ encode_handshake(Frag, Version, Epoch, ConnectionStates) ->
%%--------------------------------------------------------------------
--spec encode_alert_record(#alert{}, dtls_version(), ssl_record:connection_states()) ->
+-spec encode_alert_record(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%%
%% Description: Encodes an alert message to send on the ssl-socket.
@@ -213,7 +200,7 @@ encode_alert_record(#alert{level = Level, description = Description},
ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_change_cipher_spec(dtls_version(), integer(), ssl_record:connection_states()) ->
+-spec encode_change_cipher_spec(ssl_record:ssl_version(), integer(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%%
%% Description: Encodes a change_cipher_spec-message to send on the ssl socket.
@@ -222,7 +209,7 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) ->
encode_plain_text(?CHANGE_CIPHER_SPEC, Version, Epoch, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_data(binary(), dtls_version(), ssl_record:connection_states()) ->
+-spec encode_data(binary(), ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(),ssl_record:connection_states()}.
%%
%% Description: Encodes data to send on the ssl-socket.
@@ -237,14 +224,22 @@ encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) ->
{CipherText, Write} = encode_dtls_cipher_text(Type, Version, CipherFragment, Write1),
{CipherText, set_connection_state_by_epoch(Write, Epoch, ConnectionStates, write)}.
+%%====================================================================
+%% Decoding
+%%====================================================================
decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) ->
ReadState = get_connection_state_by_epoch(Epoch, ConnnectionStates0, read),
decode_cipher_text(CipherText, ReadState, ConnnectionStates0).
+
+%%====================================================================
+%% Protocol version handling
+%%====================================================================
+
%%--------------------------------------------------------------------
--spec protocol_version(dtls_atom_version() | dtls_version()) ->
- dtls_version() | dtls_atom_version().
+-spec protocol_version(dtls_atom_version() | ssl_record:ssl_version()) ->
+ ssl_record:ssl_version() | dtls_atom_version().
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
@@ -258,7 +253,7 @@ protocol_version({254, 253}) ->
protocol_version({254, 255}) ->
dtlsv1.
%%--------------------------------------------------------------------
--spec lowest_protocol_version(dtls_version(), dtls_version()) -> dtls_version().
+-spec lowest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
@@ -272,7 +267,7 @@ lowest_protocol_version(_,Version) ->
Version.
%%--------------------------------------------------------------------
--spec lowest_protocol_version([dtls_version()]) -> dtls_version().
+-spec lowest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Lowest protocol version present in a list
%%--------------------------------------------------------------------
@@ -283,7 +278,7 @@ lowest_protocol_version(Versions) ->
lowest_list_protocol_version(Ver, Vers).
%%--------------------------------------------------------------------
--spec highest_protocol_version([dtls_version()]) -> dtls_version().
+-spec highest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
@@ -294,7 +289,7 @@ highest_protocol_version(Versions) ->
highest_list_protocol_version(Ver, Vers).
%%--------------------------------------------------------------------
--spec highest_protocol_version(dtls_version(), dtls_version()) -> dtls_version().
+-spec highest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version of two given versions
%%--------------------------------------------------------------------
@@ -310,7 +305,7 @@ highest_protocol_version(_,Version) ->
Version.
%%--------------------------------------------------------------------
--spec is_higher(V1 :: dtls_version(), V2::dtls_version()) -> boolean().
+-spec is_higher(V1 :: ssl_record:ssl_version(), V2::ssl_record:ssl_version()) -> boolean().
%%
%% Description: Is V1 > V2
%%--------------------------------------------------------------------
@@ -322,7 +317,7 @@ is_higher(_, _) ->
false.
%%--------------------------------------------------------------------
--spec supported_protocol_versions() -> [dtls_version()].
+-spec supported_protocol_versions() -> [ssl_record:ssl_version()].
%%
%% Description: Protocol versions supported
%%--------------------------------------------------------------------
@@ -365,7 +360,7 @@ supported_protocol_versions([_|_] = Vsns) ->
end.
%%--------------------------------------------------------------------
--spec is_acceptable_version(dtls_version(), Supported :: [dtls_version()]) -> boolean().
+-spec is_acceptable_version(ssl_record:ssl_version(), Supported :: [ssl_record:ssl_version()]) -> boolean().
%%
%% Description: ssl version 2 is not acceptable security risks are too big.
%%
@@ -373,34 +368,15 @@ supported_protocol_versions([_|_] = Vsns) ->
is_acceptable_version(Version, Versions) ->
lists:member(Version, Versions).
+-spec hello_version(ssl_record:ssl_version(), [ssl_record:ssl_version()]) -> ssl_record:ssl_version().
+hello_version(Version, Versions) ->
+ case dtls_v1:corresponding_tls_version(Version) of
+ TLSVersion when TLSVersion >= {3, 3} ->
+ Version;
+ _ ->
+ lowest_protocol_version(Versions)
+ end.
-%%--------------------------------------------------------------------
--spec init_connection_state_seq(dtls_version(), ssl_record:connection_states()) ->
- ssl_record:connection_state().
-%%
-%% Description: Copy the read sequence number to the write sequence number
-%% This is only valid for DTLS in the first client_hello
-%%--------------------------------------------------------------------
-init_connection_state_seq({254, _},
- #{current_read := #{epoch := 0, sequence_number := Seq},
- current_write := #{epoch := 0} = Write} = ConnnectionStates0) ->
- ConnnectionStates0#{current_write => Write#{sequence_number => Seq}};
-init_connection_state_seq(_, ConnnectionStates) ->
- ConnnectionStates.
-
-%%--------------------------------------------------------
--spec current_connection_state_epoch(ssl_record:connection_states(), read | write) ->
- integer().
-%%
-%% Description: Returns the epoch the connection_state record
-%% that is currently defined as the current conection state.
-%%--------------------------------------------------------------------
-current_connection_state_epoch(#{current_read := #{epoch := Epoch}},
- read) ->
- Epoch;
-current_connection_state_epoch(#{current_write := #{epoch := Epoch}},
- write) ->
- Epoch.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -410,6 +386,7 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
ssl_record:initial_security_params(ConnectionEnd),
epoch => undefined,
sequence_number => 0,
+ replay_window => init_replay_window(?REPLAY_WINDOW_SIZE),
beast_mitigation => BeastMitigation,
compression_state => undefined,
cipher_state => undefined,
@@ -419,15 +396,92 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
server_verify_data => undefined
}.
-lowest_list_protocol_version(Ver, []) ->
- Ver;
-lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
+get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Epoch), ?UINT48(SequenceNumber),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
+ Acc, SslOpts) when ((StateName == hello) orelse
+ ((StateName == certify) andalso (DataTag == udp)) orelse
+ ((StateName == abbreviated) andalso(DataTag == udp)))
+ andalso
+ ((Type == ?HANDSHAKE) orelse
+ (Type == ?ALERT)) ->
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
+ case is_acceptable_version({MajVer, MinVer}, Versions) of
+ true ->
+ get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type,
+ version = {MajVer, MinVer},
+ epoch = Epoch, sequence_number = SequenceNumber,
+ fragment = Data} | Acc], SslOpts);
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+get_dtls_records_aux({_, _, Version, _} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Epoch), ?UINT48(SequenceNumber),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
+ Acc, SslOpts) when (Type == ?APPLICATION_DATA) orelse
+ (Type == ?HANDSHAKE) orelse
+ (Type == ?ALERT) orelse
+ (Type == ?CHANGE_CIPHER_SPEC) ->
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
+ case {MajVer, MinVer} of
+ Version ->
+ get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type,
+ version = {MajVer, MinVer},
+ epoch = Epoch, sequence_number = SequenceNumber,
+ fragment = Data} | Acc], SslOpts);
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+get_dtls_records_aux(_, <<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer),
+ ?UINT16(Length), _/binary>>,
+ _Acc, _) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
+ ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-highest_list_protocol_version(Ver, []) ->
- Ver;
-highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
+get_dtls_records_aux(_, Data, Acc, _) ->
+ case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
+ true ->
+ {lists:reverse(Acc), Data};
+ false ->
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
+ end.
+%%--------------------------------------------------------------------
+
+init_replay_window(Size) ->
+ #{size => Size,
+ top => Size,
+ bottom => 0,
+ mask => 0 bsl 64
+ }.
+
+replay_detect(#ssl_tls{sequence_number = SequenceNumber}, #{replay_window := Window}) ->
+ is_replay(SequenceNumber, Window).
+
+
+is_replay(SequenceNumber, #{bottom := Bottom}) when SequenceNumber < Bottom ->
+ true;
+is_replay(SequenceNumber, #{size := Size,
+ top := Top,
+ bottom := Bottom,
+ mask := Mask}) when (SequenceNumber >= Bottom) andalso (SequenceNumber =< Top) ->
+ Index = (SequenceNumber rem Size),
+ (Index band Mask) == 1;
+
+is_replay(_, _) ->
+ false.
+
+update_replay_window(SequenceNumber, #{replay_window := #{size := Size,
+ top := Top,
+ bottom := Bottom,
+ mask := Mask0} = Window0} = ConnectionStates) ->
+ NoNewBits = SequenceNumber - Top,
+ Index = SequenceNumber rem Size,
+ Mask = (Mask0 bsl NoNewBits) bor Index,
+ Window = Window0#{top => SequenceNumber,
+ bottom => Bottom + NoNewBits,
+ mask => Mask},
+ ConnectionStates#{replay_window := Window}.
+
+%%--------------------------------------------------------------------
encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment,
#{epoch := Epoch, sequence_number := Seq} = WriteState) ->
@@ -437,45 +491,63 @@ encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment,
WriteState#{sequence_number => Seq + 1}}.
encode_plain_text(Type, Version, Data, #{compression_state := CompS0,
+ cipher_state := CipherS0,
epoch := Epoch,
sequence_number := Seq,
security_parameters :=
#security_parameters{
cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BCAlg,
compression_algorithm = CompAlg}
} = WriteState0) ->
{Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
- WriteState1 = WriteState0#{compression_state => CompS1},
- AAD = calc_aad(Type, Version, Epoch, Seq),
- ssl_record:cipher_aead(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, AAD);
-encode_plain_text(Type, Version, Data, #{compression_state := CompS0,
+ AAD = start_additional_data(Type, Version, Epoch, Seq),
+ CipherS = ssl_record:nonce_seed(BCAlg, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0),
+ WriteState = WriteState0#{compression_state => CompS1,
+ cipher_state => CipherS},
+ TLSVersion = dtls_v1:corresponding_tls_version(Version),
+ ssl_record:cipher_aead(TLSVersion, Comp, WriteState, AAD);
+encode_plain_text(Type, Version, Fragment, #{compression_state := CompS0,
epoch := Epoch,
sequence_number := Seq,
+ cipher_state := CipherS0,
security_parameters :=
- #security_parameters{compression_algorithm = CompAlg}
+ #security_parameters{compression_algorithm = CompAlg,
+ bulk_cipher_algorithm =
+ BulkCipherAlgo}
}= WriteState0) ->
- {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
+ {Comp, CompS1} = ssl_record:compress(CompAlg, Fragment, CompS0),
WriteState1 = WriteState0#{compression_state => CompS1},
- MacHash = calc_mac_hash(Type, Version, WriteState1, Epoch, Seq, Comp),
- ssl_record:cipher(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, MacHash).
+ MAC = calc_mac_hash(Type, Version, WriteState1, Epoch, Seq, Comp),
+ TLSVersion = dtls_v1:corresponding_tls_version(Version),
+ {CipherFragment, CipherS1} =
+ ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MAC, Fragment, TLSVersion),
+ {CipherFragment, WriteState0#{cipher_state => CipherS1}}.
+%%--------------------------------------------------------------------
decode_cipher_text(#ssl_tls{type = Type, version = Version,
epoch = Epoch,
sequence_number = Seq,
fragment = CipherFragment} = CipherText,
#{compression_state := CompressionS0,
+ cipher_state := CipherS0,
security_parameters :=
#security_parameters{
cipher_type = ?AEAD,
+ bulk_cipher_algorithm =
+ BulkCipherAlgo,
compression_algorithm = CompAlg}} = ReadState0,
ConnnectionStates0) ->
- AAD = calc_aad(Type, Version, Epoch, Seq),
- case ssl_record:decipher_aead(dtls_v1:corresponding_tls_version(Version),
- CipherFragment, ReadState0, AAD) of
- {PlainFragment, ReadState1} ->
- {Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
+ AAD = start_additional_data(Type, Version, Epoch, Seq),
+ CipherS = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0),
+ TLSVersion = dtls_v1:corresponding_tls_version(Version),
+ case ssl_record:decipher_aead(BulkCipherAlgo, CipherS, AAD, CipherFragment, TLSVersion) of
+ PlainFragment when is_binary(PlainFragment) ->
+ {Plain, CompressionS} = ssl_record:uncompress(CompAlg,
PlainFragment, CompressionS0),
- ReadState = ReadState1#{compression_state => CompressionS1},
+ ReadState1 = ReadState0#{compression_state := CompressionS,
+ cipher_state := CipherS},
+ ReadState = update_replay_window(Seq, ReadState1),
ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read),
{CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
#alert{} = Alert ->
@@ -498,21 +570,43 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version,
{Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
PlainFragment, CompressionS0),
- ReadState = ReadState1#{compression_state => CompressionS1},
+ ReadState2 = ReadState1#{compression_state => CompressionS1},
+ ReadState = update_replay_window(Seq, ReadState2),
ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read),
{CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end.
+%%--------------------------------------------------------------------
calc_mac_hash(Type, Version, #{mac_secret := MacSecret,
security_parameters := #security_parameters{mac_algorithm = MacAlg}},
Epoch, SeqNo, Fragment) ->
Length = erlang:iolist_size(Fragment),
- NewSeq = (Epoch bsl 48) + SeqNo,
- mac_hash(Version, MacAlg, MacSecret, NewSeq, Type,
+ mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type,
Length, Fragment).
+mac_hash({Major, Minor}, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) ->
+ Value = [<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type),
+ ?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
+ Fragment],
+ dtls_v1:hmac_hash(MacAlg, MacSecret, Value).
+
+start_additional_data(Type, {MajVer, MinVer}, Epoch, SeqNo) ->
+ <<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
+
+%%--------------------------------------------------------------------
+
+lowest_list_protocol_version(Ver, []) ->
+ Ver;
+lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
+ lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
+
+highest_list_protocol_version(Ver, []) ->
+ Ver;
+highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
+ highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
+
highest_protocol_version() ->
highest_protocol_version(supported_protocol_versions()).
@@ -523,10 +617,3 @@ sufficient_dtlsv1_2_crypto_support() ->
CryptoSupport = crypto:supports(),
proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)).
-mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
- dtls_v1:mac_hash(Version, MacAlg, MacSecret, SeqNo, Type,
- Length, Fragment).
-
-calc_aad(Type, {MajVer, MinVer}, Epoch, SeqNo) ->
- NewSeq = (Epoch bsl 48) + SeqNo,
- <<NewSeq:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index 2a746d97f0..b305d08f70 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2016-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.
@@ -22,38 +22,40 @@
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
--export([send/3, listen/3, accept/3, connect/4, socket/4, setopts/3, getopts/3, getstat/3,
+-export([send/3, listen/2, accept/3, connect/4, socket/4, setopts/3, getopts/3, getstat/3,
peername/2, sockname/2, port/2, close/2]).
--export([emulated_options/0, internal_inet_values/0, default_inet_values/0, default_cb_info/0]).
+-export([emulated_options/0, emulated_options/1, internal_inet_values/0, default_inet_values/0, default_cb_info/0]).
send(Transport, {{IP,Port},Socket}, Data) ->
Transport:send(Socket, IP, Port, Data).
-listen(gen_udp = Transport, Port, #config{transport_info = {Transport, _, _, _},
- ssl = SslOpts,
- emulated = EmOpts,
- inet_user = Options} = Config) ->
+listen(Port, #config{transport_info = TransportInfo,
+ ssl = SslOpts,
+ emulated = EmOpts,
+ inet_user = Options} = Config) ->
- case dtls_udp_sup:start_child([Port, emulated_socket_options(EmOpts, #socket_options{}),
+ case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}),
Options ++ internal_inet_values(), SslOpts]) of
{ok, Pid} ->
- {ok, #sslsocket{pid = {udp, Config#config{udp_handler = {Pid, Port}}}}};
+ Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}},
+ check_active_n(EmOpts, Socket),
+ {ok, Socket};
Err = {error, _} ->
Err
end.
-accept(udp, #config{transport_info = {Transport = gen_udp,_,_,_},
+accept(dtls, #config{transport_info = {Transport,_,_,_,_},
connection_cb = ConnectionCb,
- udp_handler = {Listner, _}}, _Timeout) ->
- case dtls_udp_listener:accept(Listner, self()) of
+ dtls_handler = {Listner, _}}, _Timeout) ->
+ case dtls_packet_demux:accept(Listner, self()) of
{ok, Pid, Socket} ->
- {ok, socket(Pid, Transport, {Listner, Socket}, ConnectionCb)};
+ {ok, socket([Pid], Transport, {Listner, Socket}, ConnectionCb)};
{error, Reason} ->
{error, Reason}
end.
-connect(Address, Port, #config{transport_info = {Transport, _, _, _} = CbInfo,
+connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo,
connection_cb = ConnectionCb,
ssl = SslOpts,
emulated = EmOpts,
@@ -69,40 +71,70 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _} = CbInfo,
end.
close(gen_udp, {_Client, _Socket}) ->
- ok.
+ ok;
+close(Transport, {_Client, Socket}) ->
+ Transport:close(Socket).
-socket(Pid, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) ->
- #sslsocket{pid = Pid,
+socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) ->
+ #sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
fd = {Transport, Socket, ConnectionCb}};
-socket(Pid, Transport, Socket, ConnectionCb) ->
- #sslsocket{pid = Pid,
+socket(Pids, Transport, Socket, ConnectionCb) ->
+ #sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
fd = {Transport, Socket, ConnectionCb}}.
-%% Vad göra med emulerade
-setopts(gen_udp, #sslsocket{pid = {Socket, _}}, Options) ->
- {SockOpts, _} = tls_socket:split_options(Options),
- inet:setopts(Socket, SockOpts);
-setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}, Options) ->
- {SockOpts, _} = tls_socket:split_options(Options),
- Transport:setopts(ListenSocket, SockOpts);
+setopts(_, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) ->
+ SplitOpts = {_, EmOpts} = tls_socket:split_options(Options),
+ check_active_n(EmOpts, Socket),
+ dtls_packet_demux:set_sock_opts(ListenPid, SplitOpts);
%%% Following clauses will not be called for emulated options, they are handled in the connection process
setopts(gen_udp, Socket, Options) ->
inet:setopts(Socket, Options);
setopts(Transport, Socket, Options) ->
Transport:setopts(Socket, Options).
+check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}) ->
+ %% We check the resulting options to send an ssl_passive message if necessary.
+ case proplists:lookup(active, EmulatedOpts) of
+ %% The provided value is out of bound.
+ {_, N} when is_integer(N), N < -32768 ->
+ throw(einval);
+ {_, N} when is_integer(N), N > 32767 ->
+ throw(einval);
+ {_, N} when is_integer(N) ->
+ {ok, #socket_options{active = Active}, _} = dtls_packet_demux:get_all_opts(ListenPid),
+ case Active of
+ Atom when is_atom(Atom), N =< 0 ->
+ self() ! {ssl_passive, Socket};
+ %% The result of the addition is out of bound.
+ %% We do not need to check < -32768 because Active can't be below 1.
+ A when is_integer(A), A + N > 32767 ->
+ throw(einval);
+ A when is_integer(A), A + N =< 0 ->
+ self() ! {ssl_passive, Socket};
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+getopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) ->
+ SplitOpts = tls_socket:split_options(Options),
+ dtls_packet_demux:get_sock_opts(ListenPid, SplitOpts);
getopts(gen_udp, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) ->
{SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options),
EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames),
SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet),
{ok, EmulatedOpts ++ SocketOpts};
-getopts(Transport, #sslsocket{pid = {ListenSocket, #config{emulated = EmOpts}}}, Options) ->
+getopts(_Transport, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) ->
{SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options),
EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames),
- SocketOpts = tls_socket:get_socket_opts(ListenSocket, SockOptNames, Transport),
+ SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet),
{ok, EmulatedOpts ++ SocketOpts};
%%% Following clauses will not be called for emulated options, they are handled in the connection process
+getopts(gen_udp, {_,{{_, _},Socket}}, Options) ->
+ inet:getopts(Socket, Options);
getopts(gen_udp, {_,Socket}, Options) ->
inet:getopts(Socket, Options);
getopts(Transport, Socket, Options) ->
@@ -111,7 +143,7 @@ getstat(gen_udp, {_,Socket}, Options) ->
inet:getstat(Socket, Options);
getstat(Transport, Socket, Options) ->
Transport:getstat(Socket, Options).
-peername(udp, _) ->
+peername(_, undefined) ->
{error, enotconn};
peername(gen_udp, {_, {Client, _Socket}}) ->
{ok, Client};
@@ -132,14 +164,17 @@ port(Transport, Socket) ->
emulated_options() ->
[mode, active, packet, packet_size].
+emulated_options(Opts) ->
+ emulated_options(Opts, internal_inet_values(), default_inet_values()).
+
internal_inet_values() ->
[{active, false}, {mode,binary}].
default_inet_values() ->
- [{active, true}, {mode, list}].
+ [{active, true}, {mode, list}, {packet, 0}, {packet_size, 0}].
default_cb_info() ->
- {gen_udp, udp, udp_closed, udp_error}.
+ {gen_udp, udp, udp_closed, udp_error, udp_passive}.
get_emulated_opts(EmOpts, EmOptNames) ->
lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts),
@@ -148,8 +183,50 @@ get_emulated_opts(EmOpts, EmOptNames) ->
emulated_socket_options(InetValues, #socket_options{
mode = Mode,
+ packet = Packet,
+ packet_size = PacketSize,
active = Active}) ->
#socket_options{
mode = proplists:get_value(mode, InetValues, Mode),
- active = proplists:get_value(active, InetValues, Active)
+ packet = proplists:get_value(packet, InetValues, Packet),
+ packet_size = proplists:get_value(packet_size, InetValues, PacketSize),
+ active = emulated_active_option(InetValues, Active)
}.
+
+emulated_active_option([], Active) ->
+ Active;
+emulated_active_option([{active, Active} | _], _) when Active =< 0 ->
+ false;
+emulated_active_option([{active, Active} | _], _) ->
+ Active;
+emulated_active_option([_|Tail], Active) ->
+ emulated_active_option(Tail, Active).
+
+emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) ->
+ validate_inet_option(mode, Value),
+ emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]);
+emulated_options([{header, _} = Opt | _], _, _) ->
+ throw({error, {options, {not_supported, Opt}}});
+emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) ->
+ validate_inet_option(active, Value),
+ emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]);
+emulated_options([{packet, _} = Opt | _], _, _) ->
+ throw({error, {options, {not_supported, Opt}}});
+emulated_options([{packet_size, _} = Opt | _], _, _) ->
+ throw({error, {options, {not_supported, Opt}}});
+emulated_options([Opt|Opts], Inet, Emulated) ->
+ emulated_options(Opts, [Opt|Inet], Emulated);
+emulated_options([], Inet,Emulated) ->
+ {Inet, Emulated}.
+
+validate_inet_option(mode, Value)
+ when Value =/= list, Value =/= binary ->
+ throw({error, {options, {mode,Value}}});
+validate_inet_option(active, Value)
+ when Value >= -32768, Value =< 32767 ->
+ ok;
+validate_inet_option(active, Value)
+ when Value =/= true, Value =/= false, Value =/= once ->
+ throw({error, {options, {active,Value}}});
+validate_inet_option(_, _) ->
+ ok.
diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl
index 7169477a82..fc9dce02ce 100644
--- a/lib/ssl/src/dtls_v1.erl
+++ b/lib/ssl/src/dtls_v1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-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.
@@ -21,25 +21,33 @@
-include("ssl_cipher.hrl").
--export([suites/1, all_suites/1, mac_hash/7, ecc_curves/1,
- corresponding_tls_version/1, corresponding_dtls_version/1]).
+-export([suites/1, all_suites/1, anonymous_suites/1,hmac_hash/3, ecc_curves/1,
+ corresponding_tls_version/1, corresponding_dtls_version/1,
+ cookie_secret/0, cookie_timeout/0]).
--spec suites(Minor:: 253|255) -> [ssl_cipher:cipher_suite()].
+-define(COOKIE_BASE_TIMEOUT, 30000).
+
+-spec suites(Minor:: 253|255) -> [ssl_cipher_format:cipher_suite()].
suites(Minor) ->
lists:filter(fun(Cipher) ->
- is_acceptable_cipher(ssl_cipher:suite_definition(Cipher))
+ is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
tls_v1:suites(corresponding_minor_tls_version(Minor))).
all_suites(Version) ->
lists:filter(fun(Cipher) ->
- is_acceptable_cipher(ssl_cipher:suite_definition(Cipher))
+ is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
ssl_cipher:all_suites(corresponding_tls_version(Version))).
-mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
- tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
- Length, Fragment).
+anonymous_suites(Version) ->
+ lists:filter(fun(Cipher) ->
+ is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
+ end,
+ ssl_cipher:anonymous_suites(corresponding_tls_version(Version))).
+
+hmac_hash(MacAlg, MacSecret, Value) ->
+ tls_v1:hmac_hash(MacAlg, MacSecret, Value).
ecc_curves({_Major, Minor}) ->
tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)).
@@ -47,6 +55,13 @@ ecc_curves({_Major, Minor}) ->
corresponding_tls_version({254, Minor}) ->
{3, corresponding_minor_tls_version(Minor)}.
+cookie_secret() ->
+ crypto:strong_rand_bytes(32).
+
+cookie_timeout() ->
+ %% Cookie will live for two timeouts periods
+ round(rand:uniform() * ?COOKIE_BASE_TIMEOUT/2).
+
corresponding_minor_tls_version(255) ->
2;
corresponding_minor_tls_version(253) ->
diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl
index ffd7296f93..96ce4d493a 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. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,8 @@
%%
-module(inet6_tls_dist).
--export([childspecs/0, listen/1, accept/1, accept_connection/5,
+-export([childspecs/0]).
+-export([listen/1, accept/1, accept_connection/5,
setup/5, close/1, select/1]).
childspecs() ->
@@ -43,4 +44,4 @@ setup(Node, Type, MyNode, LongOrShortNames,SetupTime) ->
inet_tls_dist:gen_setup(inet6_tcp, Node, Type, MyNode, LongOrShortNames,SetupTime).
close(Socket) ->
- inet_tls_dist:close(Socket).
+ inet_tls_dist:gen_close(inet6_tcp, Socket).
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 0da4b3587f..8d9b92361b 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-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,16 +21,29 @@
%%
-module(inet_tls_dist).
--export([childspecs/0, listen/1, accept/1, accept_connection/5,
+-export([childspecs/0]).
+-export([listen/1, accept/1, accept_connection/5,
setup/5, close/1, select/1, is_node_name/1]).
%% Generalized dist API
-export([gen_listen/2, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_select/2]).
+ gen_setup/6, gen_close/2, gen_select/2]).
+
+-export([nodelay/0]).
+
+-export([verify_client/3, cert_nodes/1]).
+
+-export([dbg/0]). % Debug
-include_lib("kernel/include/net_address.hrl").
-include_lib("kernel/include/dist.hrl").
-include_lib("kernel/include/dist_util.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-include("ssl_api.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+%% -------------------------------------------------------------------------
childspecs() ->
{ok, [{ssl_dist_sup,{ssl_dist_sup, start_link, []},
@@ -40,103 +53,529 @@ select(Node) ->
gen_select(inet_tcp, Node).
gen_select(Driver, Node) ->
- case split_node(atom_to_list(Node), $@, []) of
- [_, Host] ->
- case inet:getaddr(Host, Driver:family()) of
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
+ case Driver:getaddr(Host) of
{ok, _} -> true;
_ -> false
end;
- _ ->
- false
+ _ ->
+ false
+ end.
+
+%% -------------------------------------------------------------------------
+
+is_node_name(Node) ->
+ dist_util:is_node_name(Node).
+
+%% -------------------------------------------------------------------------
+
+hs_data_common(#sslsocket{pid = [_, DistCtrl|_]} = SslSocket) ->
+ #hs_data{
+ f_send =
+ fun (_Ctrl, Packet) ->
+ f_send(SslSocket, Packet)
+ end,
+ f_recv =
+ fun (_, Length, Timeout) ->
+ f_recv(SslSocket, Length, Timeout)
+ end,
+ f_setopts_pre_nodeup =
+ fun (Ctrl) when Ctrl == DistCtrl ->
+ f_setopts_pre_nodeup(SslSocket)
+ end,
+ f_setopts_post_nodeup =
+ fun (Ctrl) when Ctrl == DistCtrl ->
+%%% sys:trace(Ctrl, true),
+ f_setopts_post_nodeup(SslSocket)
+ end,
+ f_getll =
+ fun (Ctrl) when Ctrl == DistCtrl ->
+ f_getll(DistCtrl)
+ end,
+ f_address =
+ fun (Ctrl, Node) when Ctrl == DistCtrl ->
+ f_address(SslSocket, Node)
+ end,
+ mf_tick =
+ fun (Ctrl) when Ctrl == DistCtrl ->
+ mf_tick(DistCtrl)
+ end,
+ mf_getstat =
+ fun (Ctrl) when Ctrl == DistCtrl ->
+ mf_getstat(SslSocket)
+ end,
+ mf_setopts =
+ fun (Ctrl, Opts) when Ctrl == DistCtrl ->
+ mf_setopts(SslSocket, Opts)
+ end,
+ mf_getopts =
+ fun (Ctrl, Opts) when Ctrl == DistCtrl ->
+ mf_getopts(SslSocket, Opts)
+ end,
+ f_handshake_complete =
+ fun (Ctrl, Node, DHandle) when Ctrl == DistCtrl ->
+ f_handshake_complete(DistCtrl, Node, DHandle)
+ end}.
+
+f_send(SslSocket, Packet) ->
+ ssl:send(SslSocket, Packet).
+
+f_recv(SslSocket, Length, Timeout) ->
+ case ssl:recv(SslSocket, Length, Timeout) of
+ {ok, Bin} when is_binary(Bin) ->
+ {ok, binary_to_list(Bin)};
+ Other ->
+ Other
end.
-is_node_name(Node) when is_atom(Node) ->
- select(Node);
-is_node_name(_) ->
- false.
+f_setopts_pre_nodeup(_SslSocket) ->
+ ok.
+
+f_setopts_post_nodeup(SslSocket) ->
+ ssl:setopts(SslSocket, [nodelay()]).
+
+f_getll(DistCtrl) ->
+ {ok, DistCtrl}.
+
+f_address(SslSocket, Node) ->
+ case ssl:peername(SslSocket) of
+ {ok, Address} ->
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
+ #net_address{
+ address=Address, host=Host,
+ protocol=tls, family=inet};
+ _ ->
+ {error, no_node}
+ end
+ end.
+
+mf_tick(DistCtrl) ->
+ DistCtrl ! tick,
+ ok.
+
+mf_getstat(SslSocket) ->
+ case ssl:getstat(
+ SslSocket, [recv_cnt, send_cnt, send_pend]) of
+ {ok, Stat} ->
+ split_stat(Stat,0,0,0);
+ Error ->
+ Error
+ end.
+
+mf_setopts(SslSocket, Opts) ->
+ case setopts_filter(Opts) of
+ [] ->
+ ssl:setopts(SslSocket, Opts);
+ Opts1 ->
+ {error, {badopts,Opts1}}
+ end.
+
+mf_getopts(SslSocket, Opts) ->
+ ssl:getopts(SslSocket, Opts).
+
+f_handshake_complete(DistCtrl, Node, DHandle) ->
+ tls_sender:dist_handshake_complete(DistCtrl, Node, DHandle).
+
+setopts_filter(Opts) ->
+ [Opt || {K,_} = Opt <- Opts,
+ K =:= active orelse K =:= deliver orelse K =:= packet].
+
+split_stat([{recv_cnt, R}|Stat], _, W, P) ->
+ split_stat(Stat, R, W, P);
+split_stat([{send_cnt, W}|Stat], R, _, P) ->
+ split_stat(Stat, R, W, P);
+split_stat([{send_pend, P}|Stat], R, W, _) ->
+ split_stat(Stat, R, W, P);
+split_stat([], R, W, P) ->
+ {ok, R, W, P}.
+
+%% -------------------------------------------------------------------------
listen(Name) ->
gen_listen(inet_tcp, Name).
gen_listen(Driver, Name) ->
- ssl_tls_dist_proxy:listen(Driver, Name).
+ case inet_tcp_dist:gen_listen(Driver, Name) of
+ {ok, {Socket, Address, Creation}} ->
+ inet:setopts(Socket, [{packet, 4}, {nodelay, true}]),
+ {ok, {Socket, Address#net_address{protocol=tls}, Creation}};
+ Other ->
+ Other
+ end.
+
+%% -------------------------------------------------------------------------
accept(Listen) ->
gen_accept(inet_tcp, Listen).
gen_accept(Driver, Listen) ->
- ssl_tls_dist_proxy:accept(Driver, Listen).
+ Kernel = self(),
+ monitor_pid(
+ spawn_opt(
+ fun () ->
+ accept_loop(Driver, Listen, Kernel)
+ end,
+ [link, {priority, max}])).
+
+accept_loop(Driver, Listen, Kernel) ->
+ case Driver:accept(Listen) of
+ {ok, Socket} ->
+ case check_ip(Driver, Socket) of
+ true ->
+ accept_loop(Driver, Listen, Kernel, Socket);
+ {false,IP} ->
+ ?LOG_ERROR(
+ "** Connection attempt from "
+ "disallowed IP ~w ** ~n", [IP]),
+ ?shutdown2(no_node, trace({disallowed, IP}))
+ end;
+ Error ->
+ exit(trace(Error))
+ end.
+
+accept_loop(Driver, Listen, Kernel, Socket) ->
+ Opts = setup_verify_client(Socket, get_ssl_options(server)),
+ wait_for_code_server(),
+ case
+ ssl:handshake(
+ Socket,
+ trace([{active, false},{packet, 4}|Opts]),
+ net_kernel:connecttime())
+ of
+ {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} ->
+ trace(
+ Kernel !
+ {accept, self(), DistCtrl,
+ Driver:family(), tls}),
+ receive
+ {Kernel, controller, Pid} ->
+ ok = ssl:controlling_process(SslSocket, Pid),
+ trace(
+ Pid ! {self(), controller});
+ {Kernel, unsupported_protocol} ->
+ exit(trace(unsupported_protocol))
+ end,
+ accept_loop(Driver, Listen, Kernel);
+ {error, {options, _}} = Error ->
+ %% Bad options: that's probably our fault.
+ %% Let's log that.
+ ?LOG_ERROR(
+ "Cannot accept TLS distribution connection: ~s~n",
+ [ssl:format_error(Error)]),
+ gen_tcp:close(Socket),
+ exit(trace(Error));
+ Other ->
+ gen_tcp:close(Socket),
+ exit(trace(Other))
+ end.
+
+
+%% {verify_fun,{fun ?MODULE:verify_client/3,_}} is used
+%% as a configuration marker that verify_client/3 shall be used.
+%%
+%% Replace the State in the first occurence of
+%% {verify_fun,{fun ?MODULE:verify_client/3,State}}
+%% and remove the rest.
+%% The inserted state is not accesible from a configuration file
+%% since it is dynamic and connection dependent.
+%%
+setup_verify_client(Socket, Opts) ->
+ setup_verify_client(Socket, Opts, true, []).
+%%
+setup_verify_client(_Socket, [], _, OptsR) ->
+ lists:reverse(OptsR);
+setup_verify_client(Socket, [Opt|Opts], First, OptsR) ->
+ case Opt of
+ {verify_fun,{Fun,_}} ->
+ case Fun =:= fun ?MODULE:verify_client/3 of
+ true ->
+ if
+ First ->
+ case inet:peername(Socket) of
+ {ok,{PeerIP,_Port}} ->
+ {ok,Allowed} = net_kernel:allowed(),
+ AllowedHosts = allowed_hosts(Allowed),
+ setup_verify_client(
+ Socket, Opts, false,
+ [{verify_fun,
+ {Fun, {AllowedHosts,PeerIP}}}
+ |OptsR]);
+ {error,Reason} ->
+ exit(trace({no_peername,Reason}))
+ end;
+ true ->
+ setup_verify_client(
+ Socket, Opts, First, OptsR)
+ end;
+ false ->
+ setup_verify_client(
+ Socket, Opts, First, [Opt|OptsR])
+ end;
+ _ ->
+ setup_verify_client(Socket, Opts, First, [Opt|OptsR])
+ end.
+
+allowed_hosts(Allowed) ->
+ lists:usort(allowed_node_hosts(Allowed)).
+%%
+allowed_node_hosts([]) -> [];
+allowed_node_hosts([Node|Allowed]) ->
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
+ [Host|allowed_node_hosts(Allowed)];
+ {host,Host} ->
+ [Host|allowed_node_hosts(Allowed)];
+ _ ->
+ allowed_node_hosts(Allowed)
+ end.
+
+%% Same as verify_peer but check cert host names for
+%% peer IP address
+verify_client(_, {bad_cert,_} = Reason, _) ->
+ {fail,Reason};
+verify_client(_, {extension,_}, S) ->
+ {unknown,S};
+verify_client(_, valid, S) ->
+ {valid,S};
+verify_client(_, valid_peer, {[],_} = S) ->
+ %% Allow all hosts
+ {valid,S};
+verify_client(PeerCert, valid_peer, {AllowedHosts,PeerIP} = S) ->
+ case
+ public_key:pkix_verify_hostname(
+ PeerCert,
+ [{ip,PeerIP}|[{dns_id,Host} || Host <- AllowedHosts]])
+ of
+ true ->
+ {valid,S};
+ false ->
+ {fail,cert_no_hostname_nor_ip_match}
+ end.
+
+
+wait_for_code_server() ->
+ %% This is an ugly hack. Upgrading a socket to TLS requires the
+ %% crypto module to be loaded. Loading the crypto module triggers
+ %% its on_load function, which calls code:priv_dir/1 to find the
+ %% directory where its NIF library is. However, distribution is
+ %% started earlier than the code server, so the code server is not
+ %% necessarily started yet, and code:priv_dir/1 might fail because
+ %% of that, if we receive an incoming connection on the
+ %% distribution port early enough.
+ %%
+ %% If the on_load function of a module fails, the module is
+ %% unloaded, and the function call that triggered loading it fails
+ %% with 'undef', which is rather confusing.
+ %%
+ %% Thus, the accept process will terminate, and be
+ %% restarted by ssl_dist_sup. However, it won't have any memory
+ %% of being asked by net_kernel to listen for incoming
+ %% connections. Hence, the node will believe that it's open for
+ %% distribution, but it actually isn't.
+ %%
+ %% So let's avoid that by waiting for the code server to start.
+ case whereis(code_server) of
+ undefined ->
+ timer:sleep(10),
+ wait_for_code_server();
+ Pid when is_pid(Pid) ->
+ ok
+ end.
-accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
- gen_accept_connection(inet_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime).
+%% -------------------------------------------------------------------------
-gen_accept_connection(Driver, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
+accept_connection(AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
+ gen_accept_connection(
+ inet_tcp, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime).
+
+gen_accept_connection(
+ Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
Kernel = self(),
- spawn_link(fun() -> do_accept(Driver, Kernel, AcceptPid, Socket,
- MyNode, Allowed, SetupTime) end).
+ monitor_pid(
+ spawn_opt(
+ fun() ->
+ do_accept(
+ Driver, AcceptPid, DistCtrl,
+ MyNode, Allowed, SetupTime, Kernel)
+ end,
+ [link, {priority, max}])).
-setup(Node, Type, MyNode, LongOrShortNames,SetupTime) ->
- gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames,SetupTime).
+do_accept(
+ _Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) ->
+ {ok, SslSocket} = tls_sender:dist_tls_socket(DistCtrl),
+ receive
+ {AcceptPid, controller} ->
+ Timer = dist_util:start_timer(SetupTime),
+ NewAllowed = allowed_nodes(SslSocket, Allowed),
+ HSData0 = hs_data_common(SslSocket),
+ HSData =
+ HSData0#hs_data{
+ kernel_pid = Kernel,
+ this_node = MyNode,
+ socket = DistCtrl,
+ timer = Timer,
+ this_flags = 0,
+ allowed = NewAllowed},
+ link(DistCtrl),
+ dist_util:handshake_other_started(trace(HSData))
+ end.
-gen_setup(Driver, Node, Type, MyNode, LongOrShortNames,SetupTime) ->
+allowed_nodes(_SslSocket, []) ->
+ %% Allow all
+ [];
+allowed_nodes(SslSocket, Allowed) ->
+ case ssl:peercert(SslSocket) of
+ {ok,PeerCertDER} ->
+ case ssl:peername(SslSocket) of
+ {ok,{PeerIP,_Port}} ->
+ PeerCert =
+ public_key:pkix_decode_cert(PeerCertDER, otp),
+ case
+ allowed_nodes(
+ PeerCert, allowed_hosts(Allowed), PeerIP)
+ of
+ [] ->
+ ?LOG_ERROR(
+ "** Connection attempt from "
+ "disallowed node(s) ~p ** ~n", [PeerIP]),
+ ?shutdown2(
+ PeerIP, trace({is_allowed, not_allowed}));
+ AllowedNodes ->
+ AllowedNodes
+ end;
+ Error1 ->
+ ?shutdown2(no_peer_ip, trace(Error1))
+ end;
+ {error,no_peercert} ->
+ Allowed;
+ Error2 ->
+ ?shutdown2(no_peer_cert, trace(Error2))
+ end.
+
+allowed_nodes(PeerCert, [], PeerIP) ->
+ case public_key:pkix_verify_hostname(PeerCert, [{ip,PeerIP}]) of
+ true ->
+ Host = inet:ntoa(PeerIP),
+ true = is_list(Host),
+ [Host];
+ false ->
+ []
+ end;
+allowed_nodes(PeerCert, [Node|Allowed], PeerIP) ->
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
+ allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host);
+ {host,Host} ->
+ allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host);
+ _ ->
+ allowed_nodes(PeerCert, Allowed, PeerIP)
+ end.
+
+allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host) ->
+ case public_key:pkix_verify_hostname(PeerCert, [{dns_id,Host}]) of
+ true ->
+ [Node|allowed_nodes(PeerCert, Allowed, PeerIP)];
+ false ->
+ allowed_nodes(PeerCert, Allowed, PeerIP)
+ end.
+
+setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+ gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime).
+
+gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
Kernel = self(),
- spawn_opt(fun() -> do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) end, [link, {priority, max}]).
-
+ monitor_pid(
+ spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime),
+ [link, {priority, max}])).
+
+-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
+setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+ fun() ->
+ do_setup(
+ Driver, Kernel, Node, Type,
+ MyNode, LongOrShortNames, SetupTime)
+ end.
+
+
+-spec do_setup(_,_,_,_,_,_,_) -> no_return().
do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- [Name, Address] = splitnode(Driver, Node, LongOrShortNames),
- case inet:getaddr(Address, Driver:family()) of
+ {Name, Address} = split_node(Driver, Node, LongOrShortNames),
+ ErlEpmd = net_kernel:epmd_module(),
+ {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver),
+ Timer = trace(dist_util:start_timer(SetupTime)),
+ case ARMod:ARFun(Name,Address,Driver:family()) of
+ {ok, Ip, TcpPort, Version} ->
+ do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer);
{ok, Ip} ->
- Timer = dist_util:start_timer(SetupTime),
- ErlEpmd = net_kernel:epmd_module(),
case ErlEpmd:port_please(Name, Ip) of
{port, TcpPort, Version} ->
- ?trace("port_please(~p) -> version ~p~n",
- [Node,Version]),
- dist_util:reset_timer(Timer),
- case ssl_tls_dist_proxy:connect(Driver, Ip, TcpPort) of
- {ok, Socket} ->
- HSData = connect_hs_data(Kernel, Node, MyNode, Socket,
- Timer, Version, Ip, TcpPort, Address,
- Type),
- dist_util:handshake_we_started(HSData);
- Other ->
- %% Other Node may have closed since
- %% port_please !
- ?trace("other node (~p) "
- "closed since port_please.~n",
- [Node]),
- ?shutdown2(Node, {shutdown, {connect_failed, Other}})
- end;
+ do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer);
Other ->
- ?trace("port_please (~p) "
- "failed.~n", [Node]),
- ?shutdown2(Node, {shutdown, {port_please_failed, Other}})
+ ?shutdown2(
+ Node,
+ trace(
+ {port_please_failed, ErlEpmd, Name, Ip, Other}))
end;
Other ->
- ?trace("inet_getaddr(~p) "
- "failed (~p).~n", [Node,Other]),
- ?shutdown2(Node, {shutdown, {inet_getaddr_failed, Other}})
+ ?shutdown2(
+ Node,
+ trace({getaddr_failed, Driver, Address, Other}))
+ end.
+
+-spec do_setup_connect(_,_,_,_,_,_,_,_,_,_) -> no_return().
+
+do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer) ->
+ Opts = trace(connect_options(get_ssl_options(client))),
+ dist_util:reset_timer(Timer),
+ case ssl:connect(
+ Address, TcpPort,
+ [binary, {active, false}, {packet, 4},
+ Driver:family(), {nodelay, true}] ++ Opts,
+ net_kernel:connecttime()) of
+ {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} ->
+ _ = monitor_pid(DistCtrl),
+ ok = ssl:controlling_process(SslSocket, self()),
+ HSData0 = hs_data_common(SslSocket),
+ HSData =
+ HSData0#hs_data{
+ kernel_pid = Kernel,
+ other_node = Node,
+ this_node = MyNode,
+ socket = DistCtrl,
+ timer = Timer,
+ this_flags = 0,
+ other_version = Version,
+ request_type = Type},
+ link(DistCtrl),
+ dist_util:handshake_we_started(trace(HSData));
+ Other ->
+ %% Other Node may have closed since
+ %% port_please !
+ ?shutdown2(
+ Node,
+ trace(
+ {ssl_connect_failed, Ip, TcpPort, Other}))
end.
close(Socket) ->
- gen_tcp:close(Socket),
- ok.
+ gen_close(inet, Socket).
-do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
- process_flag(priority, max),
- receive
- {AcceptPid, controller} ->
- Timer = dist_util:start_timer(SetupTime),
- case check_ip(Driver, Socket) of
- true ->
- HSData = accept_hs_data(Kernel, MyNode, Socket, Timer, Allowed),
- dist_util:handshake_other_started(HSData);
- {false,IP} ->
- error_logger:error_msg("** Connection attempt from "
- "disallowed IP ~w ** ~n", [IP]),
- ?shutdown(no_node)
- end
+gen_close(Driver, Socket) ->
+ trace(Driver:close(Socket)).
+
+
+%% ------------------------------------------------------------
+%% Determine if EPMD module supports address resolving. Default
+%% is to use inet_tcp:getaddr/2.
+%% ------------------------------------------------------------
+get_address_resolver(EpmdModule, _Driver) ->
+ case erlang:function_exported(EpmdModule, address_please, 3) of
+ true -> {EpmdModule, address_please};
+ _ -> {erl_epmd, address_please}
end.
+
%% ------------------------------------------------------------
%% Do only accept new connection attempts from nodes at our
%% own LAN, if the check_ip environment parameter is true.
@@ -147,16 +586,26 @@ check_ip(Driver, Socket) ->
case get_ifs(Socket) of
{ok, IFs, IP} ->
check_ip(Driver, IFs, IP);
- _ ->
- ?shutdown(no_node)
+ Other ->
+ ?shutdown2(
+ no_node, trace({check_ip_failed, Socket, Other}))
end;
_ ->
true
end.
+check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) ->
+ case {Driver:mask(Netmask, PeerIP), Driver:mask(Netmask, OwnIP)} of
+ {M, M} -> true;
+ _ -> check_ip(IFs, PeerIP)
+ end;
+check_ip(_Driver, [], PeerIP) ->
+ {false, PeerIP}.
+
get_ifs(Socket) ->
case inet:peername(Socket) of
{ok, {IP, _}} ->
+ %% XXX this is seriously broken for IPv6
case inet:getif(Socket) of
{ok, IFs} -> {ok, IFs, IP};
Error -> Error
@@ -165,125 +614,262 @@ get_ifs(Socket) ->
Error
end.
-check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) ->
- case {Driver:mask(Netmask, PeerIP), Driver:mask(Netmask, OwnIP)} of
- {M, M} -> true;
- _ -> check_ip(IFs, PeerIP)
+
+%% Look in Extensions, in all subjectAltName:s
+%% to find node names in this certificate.
+%% Host names are picked up as a subjectAltName containing
+%% a dNSName, and the first subjectAltName containing
+%% a commonName is the node name.
+%%
+cert_nodes(
+ #'OTPCertificate'{
+ tbsCertificate = #'OTPTBSCertificate'{extensions = Extensions}}) ->
+ parse_extensions(Extensions).
+
+
+parse_extensions(Extensions) when is_list(Extensions) ->
+ parse_extensions(Extensions, [], []);
+parse_extensions(asn1_NOVALUE) ->
+ undefined. % Allow all nodes
+%%
+parse_extensions([], [], []) ->
+ undefined; % Allow all nodes
+parse_extensions([], Hosts, []) ->
+ lists:reverse(Hosts);
+parse_extensions([], [], Names) ->
+ [Name ++ "@" || Name <- lists:reverse(Names)];
+parse_extensions([], Hosts, Names) ->
+ [Name ++ "@" ++ Host ||
+ Host <- lists:reverse(Hosts),
+ Name <- lists:reverse(Names)];
+parse_extensions(
+ [#'Extension'{
+ extnID = ?'id-ce-subjectAltName',
+ extnValue = AltNames}
+ |Extensions],
+ Hosts, Names) ->
+ case parse_subject_altname(AltNames) of
+ none ->
+ parse_extensions(Extensions, Hosts, Names);
+ {host,Host} ->
+ parse_extensions(Extensions, [Host|Hosts], Names);
+ {name,Name} ->
+ parse_extensions(Extensions, Hosts, [Name|Names])
end;
-check_ip(_Driver, [], PeerIP) ->
- {false, PeerIP}.
+parse_extensions([_|Extensions], Hosts, Names) ->
+ parse_extensions(Extensions, Hosts, Names).
+
+parse_subject_altname([]) ->
+ none;
+parse_subject_altname([{dNSName,Host}|_AltNames]) ->
+ {host,Host};
+parse_subject_altname(
+ [{directoryName,{rdnSequence,[Rdn|_]}}|AltNames]) ->
+ %%
+ %% XXX Why is rdnSequence a sequence?
+ %% Should we parse all members?
+ %%
+ case parse_rdn(Rdn) of
+ none ->
+ parse_subject_altname(AltNames);
+ Name ->
+ {name,Name}
+ end;
+parse_subject_altname([_|AltNames]) ->
+ parse_subject_altname(AltNames).
+
+
+parse_rdn([]) ->
+ none;
+parse_rdn(
+ [#'AttributeTypeAndValue'{
+ type = ?'id-at-commonName',
+ value = {utf8String,CommonName}}|_]) ->
+ unicode:characters_to_list(CommonName);
+parse_rdn([_|Rdn]) ->
+ parse_rdn(Rdn).
%% If Node is illegal terminate the connection setup!!
-splitnode(Driver, Node, LongOrShortNames) ->
- case split_node(atom_to_list(Node), $@, []) of
- [Name|Tail] when Tail =/= [] ->
- Host = lists:append(Tail),
- check_node(Driver, Name, Node, Host, LongOrShortNames);
- [_] ->
- error_logger:error_msg("** Nodename ~p illegal, no '@' character **~n",
- [Node]),
- ?shutdown(Node);
+split_node(Driver, Node, LongOrShortNames) ->
+ case dist_util:split_node(Node) of
+ {node, Name, Host} ->
+ check_node(Driver, Node, Name, Host, LongOrShortNames);
+ {host, _} ->
+ ?LOG_ERROR(
+ "** Nodename ~p illegal, no '@' character **~n",
+ [Node]),
+ ?shutdown2(Node, trace({illegal_node_n@me, Node}));
_ ->
- error_logger:error_msg("** Nodename ~p illegal **~n", [Node]),
- ?shutdown(Node)
+ ?LOG_ERROR(
+ "** Nodename ~p illegal **~n", [Node]),
+ ?shutdown2(Node, trace({illegal_node_name, Node}))
end.
-check_node(Driver, Name, Node, Host, LongOrShortNames) ->
- case split_node(Host, $., []) of
- [_] when LongOrShortNames == longnames ->
+check_node(Driver, Node, Name, Host, LongOrShortNames) ->
+ case string:split(Host, ".", all) of
+ [_] when LongOrShortNames =:= longnames ->
case Driver:parse_address(Host) of
{ok, _} ->
- [Name, Host];
+ {Name, Host};
_ ->
- error_logger:error_msg("** System running to use "
- "fully qualified "
- "hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown(Node)
+ ?LOG_ERROR(
+ "** System running to use "
+ "fully qualified hostnames **~n"
+ "** Hostname ~s is illegal **~n",
+ [Host]),
+ ?shutdown2(Node, trace({not_longnames, Host}))
end;
- [_, _ | _] when LongOrShortNames == shortnames ->
- error_logger:error_msg("** System NOT running to use fully qualified "
- "hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown(Node);
+ [_,_|_] when LongOrShortNames =:= shortnames ->
+ ?LOG_ERROR(
+ "** System NOT running to use "
+ "fully qualified hostnames **~n"
+ "** Hostname ~s is illegal **~n",
+ [Host]),
+ ?shutdown2(Node, trace({not_shortnames, Host}));
_ ->
- [Name, Host]
+ {Name, Host}
end.
-split_node([Chr|T], Chr, Ack) ->
- [lists:reverse(Ack)|split_node(T, Chr, [])];
-split_node([H|T], Chr, Ack) ->
- split_node(T, Chr, [H|Ack]);
-split_node([], _, Ack) ->
- [lists:reverse(Ack)].
-
-connect_hs_data(Kernel, Node, MyNode, Socket, Timer, Version, Ip, TcpPort, Address, Type) ->
- common_hs_data(Kernel, MyNode, Socket, Timer,
- #hs_data{other_node = Node,
- other_version = Version,
- f_address =
- fun(_,_) ->
- #net_address{address = {Ip,TcpPort},
- host = Address,
- protocol = proxy,
- family = inet}
- end,
- request_type = Type
- }).
-
-accept_hs_data(Kernel, MyNode, Socket, Timer, Allowed) ->
- common_hs_data(Kernel, MyNode, Socket, Timer, #hs_data{
- allowed = Allowed,
- f_address = fun get_remote_id/2
- }).
-
-common_hs_data(Kernel, MyNode, Socket, Timer, HsData) ->
- HsData#hs_data{
- kernel_pid = Kernel,
- this_node = MyNode,
- socket = Socket,
- timer = Timer,
- this_flags = 0,
- f_send =
- fun(S,D) ->
- gen_tcp:send(S,D)
- end,
- f_recv =
- fun(S,N,T) ->
- gen_tcp:recv(S,N,T)
- end,
- f_setopts_pre_nodeup =
- fun(S) ->
- inet:setopts(S, [{active, false}, {packet, 4}])
- end,
- f_setopts_post_nodeup =
- fun(S) ->
- inet:setopts(S, [{deliver, port},{active, true}])
- end,
- f_getll =
- fun(S) ->
- inet:getll(S)
- end,
- mf_tick =
- fun(S) ->
- gen_tcp:send(S, <<>>)
- end,
- mf_getstat =
- fun(S) ->
- {ok, Stats} = inet:getstat(S, [recv_cnt, send_cnt, send_pend]),
- R = proplists:get_value(recv_cnt, Stats, 0),
- W = proplists:get_value(send_cnt, Stats, 0),
- P = proplists:get_value(send_pend, Stats, 0),
- {ok, R,W,P}
- end}.
-
-get_remote_id(Socket, _Node) ->
- case ssl_tls_dist_proxy:get_tcp_address(Socket) of
- {ok, Address} ->
- Address;
- {error, _Reason} ->
- ?shutdown(no_node)
+%% -------------------------------------------------------------------------
+
+connect_options(Opts) ->
+ case application:get_env(kernel, inet_dist_connect_options) of
+ {ok,ConnectOpts} ->
+ lists:ukeysort(1, ConnectOpts ++ Opts);
+ _ ->
+ Opts
+ end.
+
+%% we may not always want the nodelay behaviour
+%% for performance reasons
+nodelay() ->
+ case application:get_env(kernel, dist_nodelay) of
+ undefined ->
+ {nodelay, true};
+ {ok, true} ->
+ {nodelay, true};
+ {ok, false} ->
+ {nodelay, false};
+ _ ->
+ {nodelay, true}
+ end.
+
+
+get_ssl_options(Type) ->
+ try ets:lookup(ssl_dist_opts, Type) of
+ [{Type, Opts}] ->
+ [{erl_dist, true} | Opts];
+ _ ->
+ get_ssl_dist_arguments(Type)
+ catch
+ error:badarg ->
+ get_ssl_dist_arguments(Type)
end.
+
+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}]
+ end.
+
+
+ssl_options(_Type, []) ->
+ [];
+ssl_options(client, ["client_" ++ Opt, Value | T] = Opts) ->
+ ssl_options(client, T, Opts, Opt, Value);
+ssl_options(server, ["server_" ++ Opt, Value | T] = Opts) ->
+ ssl_options(server, T, Opts, Opt, Value);
+ssl_options(Type, [_Opt, _Value | T]) ->
+ ssl_options(Type, T).
+%%
+ssl_options(Type, T, Opts, Opt, Value) ->
+ case ssl_option(Type, Opt) of
+ error ->
+ error(malformed_ssl_dist_opt, [Type, Opts]);
+ Fun ->
+ [{list_to_atom(Opt), Fun(Value)}|ssl_options(Type, T)]
+ end.
+
+ssl_option(server, Opt) ->
+ case Opt of
+ "dhfile" -> fun listify/1;
+ "fail_if_no_peer_cert" -> fun atomize/1;
+ _ -> ssl_option(client, Opt)
+ end;
+ssl_option(client, Opt) ->
+ case Opt of
+ "certfile" -> fun listify/1;
+ "cacertfile" -> fun listify/1;
+ "keyfile" -> fun listify/1;
+ "password" -> fun listify/1;
+ "verify" -> fun atomize/1;
+ "verify_fun" -> fun verify_fun/1;
+ "crl_check" -> fun atomize/1;
+ "crl_cache" -> fun termify/1;
+ "reuse_sessions" -> fun atomize/1;
+ "secure_renegotiate" -> fun atomize/1;
+ "depth" -> fun erlang:list_to_integer/1;
+ "hibernate_after" -> fun erlang:list_to_integer/1;
+ "ciphers" -> fun listify/1;
+ _ -> error
+ end.
+
+listify(List) when is_list(List) ->
+ List.
+
+atomize(List) when is_list(List) ->
+ list_to_atom(List);
+atomize(Atom) when is_atom(Atom) ->
+ Atom.
+
+termify(String) when is_list(String) ->
+ {ok, Tokens, _} = erl_scan:string(String ++ "."),
+ {ok, Term} = erl_parse:parse_term(Tokens),
+ Term.
+
+verify_fun(Value) ->
+ case termify(Value) of
+ {Mod, Func, State} when is_atom(Mod), is_atom(Func) ->
+ Fun = fun Mod:Func/3,
+ {Fun, State};
+ _ ->
+ error(malformed_ssl_dist_opt, [Value])
+ end.
+
+%% -------------------------------------------------------------------------
+
+%% Trace point
+trace(Term) -> Term.
+
+%% Keep an eye on distribution Pid:s we know of
+monitor_pid(Pid) ->
+ %%spawn(
+ %% fun () ->
+ %% MRef = erlang:monitor(process, Pid),
+ %% receive
+ %% {'DOWN', MRef, _, _, normal} ->
+ %% ?LOG_ERROR(
+ %% [{slogan, dist_proc_died},
+ %% {reason, normal},
+ %% {pid, Pid}]);
+ %% {'DOWN', MRef, _, _, Reason} ->
+ %% ?LOG_NOTICE(
+ %% [{slogan, dist_proc_died},
+ %% {reason, Reason},
+ %% {pid, Pid}])
+ %% end
+ %% end),
+ Pid.
+
+dbg() ->
+ dbg:stop(),
+ dbg:tracer(),
+ dbg:p(all, c),
+ dbg:tpl(?MODULE, cx),
+ dbg:tpl(erlang, dist_ctrl_get_data_notification, cx),
+ dbg:tpl(erlang, dist_ctrl_get_data, cx),
+ dbg:tpl(erlang, dist_ctrl_put_data, cx),
+ ok.
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 064dcd6892..e7a4d73ec4 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -4,13 +4,17 @@
{modules, [
%% TLS/SSL
tls_connection,
+ tls_connection_1_3,
tls_handshake,
+ tls_handshake_1_3,
tls_record,
+ tls_record_1_3,
tls_socket,
tls_v1,
ssl_v3,
- ssl_v2,
tls_connection_sup,
+ tls_sender,
+ ssl_dh_groups,
%% DTLS
dtls_connection,
dtls_handshake,
@@ -18,12 +22,10 @@
dtls_socket,
dtls_v1,
dtls_connection_sup,
- dtls_udp_listener,
- dtls_udp_sup,
+ dtls_packet_demux,
+ dtls_listener_sup,
%% API
ssl, %% Main API
- tls, %% TLS specific
- dtls, %% DTLS specific
ssl_session_cache_api,
%% Both TLS/SSL and DTLS
ssl_config,
@@ -31,13 +33,13 @@
ssl_handshake,
ssl_record,
ssl_cipher,
+ ssl_cipher_format,
ssl_srp_primes,
ssl_alert,
ssl_listen_tracker_sup, %% may be used by DTLS over SCTP
%% Erlang Distribution over SSL/TLS
inet_tls_dist,
inet6_tls_dist,
- ssl_tls_dist_proxy,
ssl_dist_sup,
ssl_dist_connection_sup,
ssl_dist_admin_sup,
@@ -53,6 +55,8 @@
ssl_crl_cache,
ssl_crl_cache_api,
ssl_crl_hash_dir,
+ %% Logging
+ ssl_logger,
%% App structure
ssl_app,
ssl_sup,
@@ -63,7 +67,5 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-3.2","public_key-1.2","kernel-3.0",
- "erts-7.0","crypto-3.3", "inets-5.10.7"]}]}.
-
-
+ {runtime_dependencies, ["stdlib-3.5","public_key-1.5","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 2eda9d9491..ae4d60b6ed 100644
--- a/lib/ssl/src/ssl.appup.src
+++ b/lib/ssl/src/ssl.appup.src
@@ -1,7 +1,7 @@
%% -*- erlang -*-
{"%VSN%",
- [
- {<<"8.1.1">>, [{load_module, tls_connection, soft_purge, soft_purge, []}]},
+[
+ {<<"9\\..*">>, [{restart_application, ssl}]},
{<<"8\\..*">>, [{restart_application, ssl}]},
{<<"7\\..*">>, [{restart_application, ssl}]},
{<<"6\\..*">>, [{restart_application, ssl}]},
@@ -10,7 +10,7 @@
{<<"3\\..*">>, [{restart_application, ssl}]}
],
[
- {<<"8.1.1">>, [{load_module, tls_connection, soft_purge, soft_purge, []}]},
+ {<<"9\\..*">>, [{restart_application, ssl}]},
{<<"8\\..*">>, [{restart_application, ssl}]},
{<<"7\\..*">>, [{restart_application, ssl}]},
{<<"6\\..*">>, [{restart_application, ssl}]},
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 9a106f9742..7ff9aed8ea 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2019. 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.
@@ -23,31 +23,10 @@
%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl
-module(ssl).
--include("ssl_internal.hrl").
--include_lib("public_key/include/public_key.hrl").
-
-%% Application handling
--export([start/0, start/1, stop/0, clear_pem_cache/0]).
-
-%% Socket handling
--export([connect/3, connect/2, connect/4,
- listen/2, transport_accept/1, transport_accept/2,
- ssl_accept/1, ssl_accept/2, ssl_accept/3,
- controlling_process/2, peername/1, peercert/1, sockname/1,
- close/1, close/2, shutdown/2, recv/2, recv/3, send/2,
- getopts/2, setopts/2, getstat/1, getstat/2
- ]).
-%% SSL/TLS protocol handling
--export([cipher_suites/0, cipher_suites/1, eccs/0, eccs/1,
- connection_info/1, versions/0, session_info/1, format_error/1,
- renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1,
- connection_information/1, connection_information/2]).
-%% Misc
--export([handle_options/2, tls_version/1]).
--deprecated({negotiated_next_protocol, 1, next_major_release}).
--deprecated({connection_info, 1, next_major_release}).
+-include_lib("public_key/include/public_key.hrl").
+-include("ssl_internal.hrl").
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
@@ -55,18 +34,425 @@
-include("ssl_handshake.hrl").
-include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
+%% Application handling
+-export([start/0,
+ start/1,
+ stop/0,
+ clear_pem_cache/0]).
+
+%% Socket handling
+-export([connect/3,
+ connect/2,
+ connect/4,
+ listen/2,
+ transport_accept/1,
+ transport_accept/2,
+ handshake/1,
+ handshake/2,
+ handshake/3,
+ handshake_continue/2,
+ handshake_continue/3,
+ handshake_cancel/1,
+ ssl_accept/1,
+ ssl_accept/2,
+ ssl_accept/3,
+ controlling_process/2,
+ peername/1,
+ peercert/1,
+ sockname/1,
+ close/1,
+ close/2,
+ shutdown/2,
+ recv/2,
+ recv/3,
+ send/2,
+ getopts/2,
+ setopts/2,
+ getstat/1,
+ getstat/2
+ ]).
+
+%% SSL/TLS protocol handling
+-export([cipher_suites/0,
+ cipher_suites/1,
+ cipher_suites/2,
+ cipher_suites/3,
+ filter_cipher_suites/2,
+ prepend_cipher_suites/2,
+ append_cipher_suites/2,
+ eccs/0,
+ eccs/1,
+ versions/0,
+ groups/0,
+ groups/1,
+ format_error/1,
+ renegotiate/1,
+ prf/5,
+ negotiated_protocol/1,
+ connection_information/1,
+ connection_information/2]).
+%% Misc
+-export([handle_options/2,
+ tls_version/1,
+ new_ssl_options/3,
+ suite_to_str/1,
+ suite_to_openssl_str/1,
+ str_to_suite/1]).
+
+-deprecated({ssl_accept, 1, eventually}).
+-deprecated({ssl_accept, 2, eventually}).
+-deprecated({ssl_accept, 3, eventually}).
+
+-export_type([socket/0,
+ sslsocket/0,
+ socket_option/0,
+ active_msgs/0,
+ host/0,
+ tls_option/0,
+ tls_client_option/0,
+ tls_server_option/0,
+ erl_cipher_suite/0,
+ old_cipher_suite/0,
+ ciphers/0,
+ cipher/0,
+ hash/0,
+ key/0,
+ kex_algo/0,
+ prf_random/0,
+ cipher_filters/0,
+ sign_algo/0,
+ protocol_version/0,
+ protocol_extensions/0,
+ session_id/0,
+ error_alert/0,
+ tls_alert/0,
+ srp_param_type/0,
+ named_curve/0,
+ sign_scheme/0,
+ group/0]).
+
+%% -------------------------------------------------------------------------------------------------------
+
+-type socket() :: gen_tcp:socket(). % exported
+-type socket_option() :: gen_tcp:connect_option() | gen_tcp:listen_option() | gen_udp:option(). % exported
+-type sslsocket() :: any(). % exported
+-type tls_option() :: tls_client_option() | tls_server_option(). % exported
+-type tls_client_option() :: client_option() | common_option() | socket_option() | transport_option(). % exported
+-type tls_server_option() :: server_option() | common_option() | socket_option() | transport_option(). % exported
+-type active_msgs() :: {ssl, sslsocket(), Data::binary() | list()} | {ssl_closed, sslsocket()} |
+ {ssl_error, sslsocket(), Reason::any()} | {ssl_passive, sslsocket()}. % exported
+-type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
+ ClosedTag::atom(), ErrTag::atom()}} |
+ {cb_info, {CallbackModule::atom(), DataTag::atom(),
+ ClosedTag::atom(), ErrTag::atom(), PassiveTag::atom()}}.
+-type host() :: hostname() | ip_address(). % exported
+-type hostname() :: string().
+-type ip_address() :: inet:ip_address().
+-type session_id() :: binary(). % exported
+-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 dtls_legacy_version() :: 'dtlsv1'.
+-type verify_type() :: verify_none | verify_peer.
+-type cipher() :: aes_128_cbc |
+ aes_256_cbc |
+ aes_128_gcm |
+ aes_256_gcm |
+ aes_128_ccm |
+ aes_256_ccm |
+ aes_128_ccm_8 |
+ aes_256_ccm_8 |
+ chacha20_poly1305 |
+ legacy_cipher(). % exported
+-type legacy_cipher() :: rc4_128 |
+ des_cbc |
+ '3des_ede_cbc'.
+
+-type hash() :: sha |
+ sha2() |
+ legacy_hash(). % exported
+
+-type sha2() :: sha224 |
+ sha256 |
+ sha384 |
+ sha512.
+
+-type legacy_hash() :: md5.
+
+-type sign_algo() :: rsa | dsa | ecdsa. % exported
+
+-type sign_scheme() :: rsa_pkcs1_sha256
+ | rsa_pkcs1_sha384
+ | rsa_pkcs1_sha512
+ | ecdsa_secp256r1_sha256
+ | ecdsa_secp384r1_sha384
+ | ecdsa_secp521r1_sha512
+ | rsa_pss_rsae_sha256
+ | rsa_pss_rsae_sha384
+ | rsa_pss_rsae_sha512
+ | rsa_pss_pss_sha256
+ | rsa_pss_pss_sha384
+ | rsa_pss_pss_sha512
+ | rsa_pkcs1_sha1
+ | ecdsa_sha1. % exported
+
+-type kex_algo() :: rsa |
+ dhe_rsa | dhe_dss |
+ ecdhe_ecdsa | ecdh_ecdsa | ecdh_rsa |
+ srp_rsa| srp_dss |
+ psk | dhe_psk | rsa_psk |
+ dh_anon | ecdh_anon | srp_anon |
+ any. %% TLS 1.3 , exported
+-type erl_cipher_suite() :: #{key_exchange := kex_algo(),
+ cipher := cipher(),
+ mac := hash() | aead,
+ prf := hash() | default_prf %% Old cipher suites, version dependent
+ }.
+
+-type old_cipher_suite() :: {kex_algo(), cipher(), hash()} % Pre TLS 1.2
+ %% TLS 1.2, internally PRE TLS 1.2 will use default_prf
+ | {kex_algo(), cipher(), hash() | aead, hash()}.
+
+-type named_curve() :: sect571r1 |
+ sect571k1 |
+ secp521r1 |
+ brainpoolP512r1 |
+ sect409k1 |
+ sect409r1 |
+ brainpoolP384r1 |
+ secp384r1 |
+ sect283k1 |
+ sect283r1 |
+ brainpoolP256r1 |
+ secp256k1 |
+ secp256r1 |
+ sect239k1 |
+ sect233k1 |
+ sect233r1 |
+ secp224k1 |
+ secp224r1 |
+ sect193r1 |
+ sect193r2 |
+ secp192k1 |
+ secp192r1 |
+ sect163k1 |
+ sect163r1 |
+ sect163r2 |
+ secp160k1 |
+ secp160r1 |
+ secp160r2. % exported
+
+-type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
+ ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. % exported
+
+-type srp_param_type() :: srp_1024 |
+ srp_1536 |
+ srp_2048 |
+ srp_3072 |
+ srp_4096 |
+ srp_6144 |
+ srp_8192. % exported
+
+-type error_alert() :: {tls_alert, {tls_alert(), Description::string()}}. % exported
+
+-type tls_alert() :: close_notify |
+ unexpected_message |
+ bad_record_mac |
+ record_overflow |
+ handshake_failure |
+ bad_certificate |
+ unsupported_certificate |
+ certificate_revoked |
+ certificate_expired |
+ certificate_unknown |
+ illegal_parameter |
+ unknown_ca |
+ access_denied |
+ decode_error |
+ decrypt_error |
+ export_restriction|
+ protocol_version |
+ insufficient_security |
+ internal_error |
+ inappropriate_fallback |
+ user_canceled |
+ no_renegotiation |
+ unsupported_extension |
+ certificate_unobtainable |
+ unrecognized_name |
+ bad_certificate_status_response |
+ bad_certificate_hash_value |
+ unknown_psk_identity |
+ no_application_protocol. % exported
+
+%% -------------------------------------------------------------------------------------------------------
+-type common_option() :: {protocol, protocol()} |
+ {handshake, handshake_completion()} |
+ {cert, cert()} |
+ {certfile, cert_pem()} |
+ {key, key()} |
+ {keyfile, key_pem()} |
+ {password, key_password()} |
+ {ciphers, cipher_suites()} |
+ {eccs, [named_curve()]} |
+ {signature_algs_cert, signature_schemes()} |
+ {supported_groups, supported_groups()} |
+ {secure_renegotiate, secure_renegotiation()} |
+ {depth, allowed_cert_chain_length()} |
+ {verify_fun, custom_verify()} |
+ {crl_check, crl_check()} |
+ {crl_cache, crl_cache_opts()} |
+ {max_handshake_size, handshake_size()} |
+ {partial_chain, root_fun()} |
+ {versions, protocol_versions()} |
+ {user_lookup_fun, custom_user_lookup()} |
+ {log_level, logging_level()} |
+ {log_alert, log_alert()} |
+ {hibernate_after, hibernate_after()} |
+ {padding_check, padding_check()} |
+ {beast_mitigation, beast_mitigation()} |
+ {ssl_imp, ssl_imp()}.
+
+-type protocol() :: tls | dtls.
+-type handshake_completion() :: hello | full.
+-type cert() :: public_key:der_encoded().
+-type cert_pem() :: file:filename().
+-type key() :: {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo',
+ public_key:der_encoded()} |
+ #{algorithm := rsa | dss | ecdsa,
+ engine := crypto:engine_ref(),
+ key_id := crypto:key_id(),
+ password => crypto:password()}. % exported
+-type key_pem() :: file:filename().
+-type key_password() :: string().
+-type cipher_suites() :: ciphers().
+-type ciphers() :: [erl_cipher_suite()] |
+ string(). % (according to old API) exported
+-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 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 handshake_size() :: integer().
+-type hibernate_after() :: timeout().
+-type root_fun() :: fun().
+-type protocol_versions() :: [protocol_version()].
+-type signature_algs() :: [{hash(), sign_algo()}].
+-type signature_schemes() :: [sign_scheme()].
+-type supported_groups() :: [group()].
+-type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: any()}.
+-type padding_check() :: boolean().
+-type beast_mitigation() :: one_n_minus_one | zero_n | disabled.
+-type srp_identity() :: {Username :: string(), Password :: string()}.
+-type psk_identity() :: string().
+-type log_alert() :: boolean().
+-type logging_level() :: logger:level().
+
+%% -------------------------------------------------------------------------------------------------------
+
+-type client_option() :: {verify, client_verify_type()} |
+ {reuse_session, client_reuse_session()} |
+ {reuse_sessions, client_reuse_sessions()} |
+ {cacerts, client_cacerts()} |
+ {cacertfile, client_cafile()} |
+ {alpn_advertised_protocols, client_alpn()} |
+ {client_preferred_next_protocols, client_preferred_next_protocols()} |
+ {psk_identity, client_psk_identity()} |
+ {srp_identity, client_srp_identity()} |
+ {server_name_indication, sni()} |
+ {customize_hostname_check, customize_hostname_check()} |
+ {signature_algs, client_signature_algs()} |
+ {fallback, fallback()}.
+
+-type client_verify_type() :: verify_type().
+-type client_reuse_session() :: session_id().
+-type client_reuse_sessions() :: boolean() | save.
+-type client_cacerts() :: [public_key:der_encoded()].
+-type client_cafile() :: file:filename().
+-type app_level_protocol() :: binary().
+-type client_alpn() :: [app_level_protocol()].
+-type client_preferred_next_protocols() :: {Precedence :: server | client,
+ ClientPrefs :: [app_level_protocol()]} |
+ {Precedence :: server | client,
+ ClientPrefs :: [app_level_protocol()],
+ Default::app_level_protocol()}.
+-type client_psk_identity() :: psk_identity().
+-type client_srp_identity() :: srp_identity().
+-type customize_hostname_check() :: list().
+-type sni() :: HostName :: hostname() | disable.
+-type client_signature_algs() :: signature_algs().
+-type fallback() :: boolean().
+-type ssl_imp() :: new | old.
+
+%% -------------------------------------------------------------------------------------------------------
+
+-type server_option() :: {cacerts, server_cacerts()} |
+ {cacertfile, server_cafile()} |
+ {dh, dh_der()} |
+ {dhfile, dh_file()} |
+ {verify, server_verify_type()} |
+ {fail_if_no_peer_cert, fail_if_no_peer_cert()} |
+ {reuse_sessions, server_reuse_sessions()} |
+ {reuse_session, server_reuse_session()} |
+ {alpn_preferred_protocols, server_alpn()} |
+ {next_protocols_advertised, server_next_protocol()} |
+ {psk_identity, server_psk_identity()} |
+ {honor_cipher_order, boolean()} |
+ {sni_hosts, sni_hosts()} |
+ {sni_fun, sni_fun()} |
+ {honor_cipher_order, honor_cipher_order()} |
+ {honor_ecc_order, honor_ecc_order()} |
+ {client_renegotiation, client_renegotiation()}|
+ {signature_algs, server_signature_algs()}.
+
+-type server_cacerts() :: [public_key:der_encoded()].
+-type server_cafile() :: file:filename().
+-type server_alpn() :: [app_level_protocol()].
+-type server_next_protocol() :: [app_level_protocol()].
+-type server_psk_identity() :: psk_identity().
+-type dh_der() :: binary().
+-type dh_file() :: file:filename().
+-type server_verify_type() :: verify_type().
+-type fail_if_no_peer_cert() :: boolean().
+-type server_signature_algs() :: signature_algs().
+-type server_reuse_session() :: fun().
+-type server_reuse_sessions() :: boolean().
+-type sni_hosts() :: [{hostname(), [server_option() | common_option()]}].
+-type sni_fun() :: fun().
+-type honor_cipher_order() :: boolean().
+-type honor_ecc_order() :: boolean().
+-type client_renegotiation() :: boolean().
+%% -------------------------------------------------------------------------------------------------------
+-type prf_random() :: client_random | server_random. % exported
+-type protocol_extensions() :: #{renegotiation_info => binary(),
+ signature_algs => signature_algs(),
+ alpn => app_level_protocol(),
+ srp => binary(),
+ next_protocol => app_level_protocol(),
+ ec_point_formats => [0..2],
+ elliptic_curves => [public_key:oid()],
+ sni => hostname()}. % exported
+%% -------------------------------------------------------------------------------------------------------
+
+%%%--------------------------------------------------------------------
+%%% API
+%%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec start() -> ok | {error, reason()}.
--spec start(permanent | transient | temporary) -> ok | {error, reason()}.
%%
%% Description: Utility function that starts the ssl and applications
%% that it depends on.
%% see application(3)
%%--------------------------------------------------------------------
+-spec start() -> ok | {error, reason()}.
start() ->
start(temporary).
+-spec start(permanent | transient | temporary) -> ok | {error, reason()}.
start(Type) ->
case application:ensure_all_started(ssl, Type) of
{ok, _} ->
@@ -83,24 +469,38 @@ stop() ->
application:stop(ssl).
%%--------------------------------------------------------------------
--spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
- {error, reason()}.
--spec connect(host() | port(), [connect_option()] | inet:port_number(),
- timeout() | list()) ->
- {ok, #sslsocket{}} | {error, reason()}.
--spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
%%
%% Description: Connect to an ssl server.
%%--------------------------------------------------------------------
+
+-spec connect(TCPSocket, TLSOptions) ->
+ {ok, sslsocket()} |
+ {error, reason()} |
+ {option_not_a_key_value_tuple, any()} when
+ TCPSocket :: socket(),
+ TLSOptions :: [tls_client_option()].
+
connect(Socket, SslOptions) when is_port(Socket) ->
connect(Socket, SslOptions, infinity).
+-spec connect(TCPSocket, TLSOptions, Timeout) ->
+ {ok, sslsocket()} | {error, reason()} when
+ TCPSocket :: socket(),
+ TLSOptions :: [tls_client_option()],
+ Timeout :: timeout();
+ (Host, Port, TLSOptions) ->
+ {ok, sslsocket()} |
+ {ok, sslsocket(),Ext :: protocol_extensions()} |
+ {error, reason()} |
+ {option_not_a_key_value_tuple, any()} when
+ Host :: host(),
+ Port :: inet:port_number(),
+ TLSOptions :: [tls_client_option()].
+
connect(Socket, SslOptions0, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0,
- {gen_tcp, tcp, tcp_closed, tcp_error}),
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+ CbInfo = handle_option(cb_info, SslOptions0, default_cb_info(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
@@ -113,9 +513,20 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket),
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
+
+-spec connect(Host, Port, TLSOptions, Timeout) ->
+ {ok, sslsocket()} |
+ {ok, sslsocket(),Ext :: protocol_extensions()} |
+ {error, reason()} |
+ {option_not_a_key_value_tuple, any()} when
+ Host :: host(),
+ Port :: inet:port_number(),
+ TLSOptions :: [tls_client_option()],
+ Timeout :: timeout().
+
connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
try
- {ok, Config} = handle_options(Options, client),
+ {ok, Config} = handle_options(Options, client, Host),
case Config#config.connection_cb of
tls_connection ->
tls_socket:connect(Host,Port,Config,Timeout);
@@ -128,7 +539,10 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout
end.
%%--------------------------------------------------------------------
--spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
+-spec listen(Port, Options) -> {ok, ListenSocket} | {error, reason()} when
+ Port::inet:port_number(),
+ Options::[tls_server_option()],
+ ListenSocket :: sslsocket().
%%
%% Description: Creates an ssl listen socket.
@@ -144,16 +558,23 @@ listen(Port, Options0) ->
Error
end.
%%--------------------------------------------------------------------
--spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
- {error, reason()}.
--spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {error, reason()}.
%%
%% Description: Performs transport accept on an ssl listen socket
%%--------------------------------------------------------------------
+-spec transport_accept(ListenSocket) -> {ok, SslSocket} |
+ {error, reason()} when
+ ListenSocket :: sslsocket(),
+ SslSocket :: sslsocket().
+
transport_accept(ListenSocket) ->
transport_accept(ListenSocket, infinity).
+-spec transport_accept(ListenSocket, Timeout) -> {ok, SslSocket} |
+ {error, reason()} when
+ ListenSocket :: sslsocket(),
+ Timeout :: timeout(),
+ SslSocket :: sslsocket().
+
transport_accept(#sslsocket{pid = {ListenSocket,
#config{connection_cb = ConnectionCb} = Config}}, Timeout)
when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
@@ -165,29 +586,106 @@ transport_accept(#sslsocket{pid = {ListenSocket,
end.
%%--------------------------------------------------------------------
--spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}.
--spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option()
- | transport_option()]) ->
- ok | {ok, #sslsocket{}} | {error, reason()}.
-
--spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
+-spec ssl_accept(SslSocket) ->
+ ok |
+ {error, Reason} when
+ SslSocket :: sslsocket(),
+ Reason :: closed | timeout | error_alert().
+
ssl_accept(ListenSocket) ->
- ssl_accept(ListenSocket, infinity).
+ ssl_accept(ListenSocket, [], infinity).
+
+-spec ssl_accept(Socket, TimeoutOrOptions) ->
+ ok |
+ {ok, sslsocket()} | {error, Reason} when
+ Socket :: sslsocket() | socket(),
+ TimeoutOrOptions :: timeout() | [tls_server_option()],
+ Reason :: timeout | closed | {options, any()} | error_alert().
+
+ssl_accept(Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+ ssl_accept(Socket, [], Timeout);
+ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+ ssl_accept(ListenSocket, SslOptions, infinity);
+ssl_accept(Socket, Timeout) ->
+ ssl_accept(Socket, [], Timeout).
+
+-spec ssl_accept(Socket, Options, Timeout) ->
+ ok | {ok, sslsocket()} | {error, Reason} when
+ Socket :: sslsocket() | socket(),
+ Options :: [tls_server_option()],
+ Timeout :: timeout(),
+ Reason :: timeout | closed | {options, any()} | error_alert().
+
+ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
+ handshake(Socket, SslOptions, Timeout);
+ssl_accept(Socket, SslOptions, Timeout) ->
+ case handshake(Socket, SslOptions, Timeout) of
+ {ok, _} ->
+ ok;
+ Error ->
+ Error
+ end.
+%%--------------------------------------------------------------------
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
-ssl_accept(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+%% Performs the SSL/TLS/DTLS server-side handshake.
+-spec handshake(HsSocket) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason} when
+ HsSocket :: sslsocket(),
+ SslSocket :: sslsocket(),
+ Ext :: protocol_extensions(),
+ Reason :: closed | timeout | error_alert().
+
+handshake(ListenSocket) ->
+ handshake(ListenSocket, infinity).
+
+-spec handshake(HsSocket, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason} when
+ HsSocket :: sslsocket(),
+ Timeout :: timeout(),
+ SslSocket :: sslsocket(),
+ Ext :: protocol_extensions(),
+ Reason :: closed | timeout | error_alert();
+ (Socket, Options) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason} when
+ Socket :: socket() | sslsocket(),
+ SslSocket :: sslsocket(),
+ Options :: [server_option()],
+ Ext :: protocol_extensions(),
+ Reason :: closed | timeout | error_alert().
+
+handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
+ (Timeout == infinity) ->
ssl_connection:handshake(Socket, Timeout);
-ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
- ssl_accept(ListenSocket, SslOptions, infinity).
-
-ssl_accept(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
- ssl_accept(Socket, Timeout);
-ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when
+%% 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
+%% the SSL socket.
+%%
+%% 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, infinity).
+
+-spec handshake(Socket, Options, Timeout) ->
+ {ok, SslSocket} |
+ {ok, SslSocket, Ext} |
+ {error, Reason} when
+ Socket :: socket() | sslsocket(),
+ SslSocket :: sslsocket(),
+ Options :: [server_option()],
+ Timeout :: timeout(),
+ Ext :: protocol_extensions(),
+ Reason :: closed | timeout | {options, any()} | error_alert().
+
+handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
+ (Timeout == infinity)->
+ handshake(Socket, Timeout);
+handshake(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
{ok, EmOpts, _} = tls_socket:get_all_opts(Tracker),
@@ -196,19 +694,19 @@ ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when
catch
Error = {error, _Reason} -> Error
end;
-ssl_accept(#sslsocket{pid = Pid, fd = {_, _, _}} = Socket, SslOpts, Timeout) when
+handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout) when
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
- {ok, EmOpts, _} = dtls_udp_listener:get_all_opts(Pid),
+ {ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid),
ssl_connection:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
end;
-ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- {Transport,_,_,_} =
- proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}),
+handshake(Socket, SslOptions, Timeout) when is_port(Socket),
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+ CbInfo = handle_option(cb_info, SslOptions, default_cb_info(tls)),
+ Transport = element(1, CbInfo),
EmulatedOptions = tls_socket:emulated_options(),
{ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
ConnetionCb = connection_cb(SslOptions),
@@ -216,85 +714,148 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket),
{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:ssl_accept(ConnetionCb, Port, Socket,
- {SslOpts,
- tls_socket:emulated_socket_options(EmOpts, #socket_options{}), undefined},
- self(), CbInfo, Timeout)
+ ssl_connection:handshake(ConnetionCb, Port, Socket,
+ {SslOpts,
+ tls_socket:emulated_socket_options(EmOpts, #socket_options{}), undefined},
+ self(), CbInfo, Timeout)
catch
Error = {error, _Reason} -> Error
end.
+
+
+%%--------------------------------------------------------------------
+-spec handshake_continue(HsSocket, Options) ->
+ {ok, SslSocket} | {error, Reason} when
+ HsSocket :: sslsocket(),
+ Options :: [tls_client_option() | tls_server_option()],
+ SslSocket :: sslsocket(),
+ Reason :: closed | timeout | error_alert().
+%%
+%%
+%% Description: Continues the handshke possible with newly supplied options.
%%--------------------------------------------------------------------
--spec close(#sslsocket{}) -> term().
+handshake_continue(Socket, SSLOptions) ->
+ handshake_continue(Socket, SSLOptions, infinity).
+%%--------------------------------------------------------------------
+-spec handshake_continue(HsSocket, Options, Timeout) ->
+ {ok, SslSocket} | {error, Reason} when
+ HsSocket :: sslsocket(),
+ Options :: [tls_client_option() | tls_server_option()],
+ Timeout :: timeout(),
+ SslSocket :: sslsocket(),
+ Reason :: closed | timeout | error_alert().
+%%
+%%
+%% Description: Continues the handshke possible with newly supplied options.
+%%--------------------------------------------------------------------
+handshake_continue(Socket, SSLOptions, Timeout) ->
+ ssl_connection: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).
+
+%%--------------------------------------------------------------------
+-spec close(SslSocket) -> ok | {error, Reason} when
+ SslSocket :: sslsocket(),
+ Reason :: any().
%%
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
-close(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT});
-close(#sslsocket{pid = {udp, #config{udp_handler = {Pid, _}}}}) ->
- dtls_udp_listener:close(Pid);
-close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}) ->
+close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) ->
+ dtls_packet_demux:close(Pid);
+close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}) ->
Transport:close(ListenSocket).
%%--------------------------------------------------------------------
--spec close(#sslsocket{}, timeout() | {pid(), integer()}) -> term().
+-spec close(SslSocket, How) -> ok | {ok, port()} | {error,Reason} when
+ SslSocket :: sslsocket(),
+ How :: timeout() | {NewController::pid(), timeout()},
+ Reason :: any().
%%
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
-close(#sslsocket{pid = TLSPid},
+close(#sslsocket{pid = [TLSPid|_]},
{Pid, Timeout} = DownGrade) when is_pid(TLSPid),
is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_connection:close(TLSPid, {close, DownGrade});
-close(#sslsocket{pid = TLSPid}, Timeout) when is_pid(TLSPid),
+close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_connection:close(TLSPid, {close, Timeout});
-close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}, _) ->
+close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
Transport:close(ListenSocket).
%%--------------------------------------------------------------------
--spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}.
+-spec send(SslSocket, Data) -> ok | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Data :: iodata().
%%
%% Description: Sends data over the ssl connection
%%--------------------------------------------------------------------
-send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) ->
+send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) ->
ssl_connection:send(Pid, Data);
-send(#sslsocket{pid = {_, #config{transport_info={gen_udp, _, _, _}}}}, _) ->
+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, _, _}}}}, _) ->
{error,enotconn}; %% Emulate connection behaviour
-send(#sslsocket{pid = {udp,_}}, _) ->
- {error,enotconn};
-send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}}}}, Data) ->
+send(#sslsocket{pid = {dtls,_}}, _) ->
+ {error,enotconn}; %% Emulate connection behaviour
+send(#sslsocket{pid = {ListenSocket, #config{transport_info = Info}}}, Data) ->
+ Transport = element(1, Info),
Transport:send(ListenSocket, Data). %% {error,enotconn}
%%--------------------------------------------------------------------
--spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
--spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
%%
%% Description: Receives data when active = false
%%--------------------------------------------------------------------
+-spec recv(SslSocket, Length) -> {ok, Data} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Length :: integer(),
+ Data :: binary() | list() | HttpPacket,
+ HttpPacket :: any().
+
recv(Socket, Length) ->
recv(Socket, Length, infinity).
-recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid),
+
+-spec recv(SslSocket, Length, Timeout) -> {ok, Data} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Length :: integer(),
+ Data :: binary() | list() | HttpPacket,
+ Timeout :: timeout(),
+ HttpPacket :: any().
+
+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);
-recv(#sslsocket{pid = {udp,_}}, _, _) ->
+recv(#sslsocket{pid = {dtls,_}}, _, _) ->
{error,enotconn};
recv(#sslsocket{pid = {Listen,
- #config{transport_info = {Transport, _, _, _}}}}, _,_) when is_port(Listen)->
+ #config{transport_info = Info}}},_,_) when is_port(Listen)->
+ Transport = element(1, Info),
Transport:recv(Listen, 0). %% {error,enotconn}
%%--------------------------------------------------------------------
--spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}.
+-spec controlling_process(SslSocket, NewOwner) -> ok | {error, Reason} when
+ SslSocket :: sslsocket(),
+ NewOwner :: pid(),
+ Reason :: any().
%%
%% Description: Changes process that receives the messages when active = true
%% or once.
%%--------------------------------------------------------------------
-controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
+controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
ssl_connection:new_user(Pid, NewOwner);
-controlling_process(#sslsocket{pid = {udp, _}},
+controlling_process(#sslsocket{pid = {dtls, _}},
NewOwner) when is_pid(NewOwner) ->
ok; %% Meaningless but let it be allowed to conform with TLS
controlling_process(#sslsocket{pid = {Listen,
- #config{transport_info = {Transport, _, _, _}}}},
+ #config{transport_info = {Transport,_,_,_,_}}}},
NewOwner) when is_port(Listen),
is_pid(NewOwner) ->
%% Meaningless but let it be allowed to conform with normal sockets
@@ -302,12 +863,16 @@ controlling_process(#sslsocket{pid = {Listen,
%%--------------------------------------------------------------------
--spec connection_information(#sslsocket{}) -> {ok, list()} | {error, reason()}.
+-spec connection_information(SslSocket) -> {ok, Result} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Result :: [{OptionName, OptionValue}],
+ OptionName :: atom(),
+ OptionValue :: any().
%%
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
-connection_information(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- case ssl_connection:connection_information(Pid) of
+connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
+ case ssl_connection:connection_information(Pid, false) of
{ok, Info} ->
{ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]};
Error ->
@@ -315,16 +880,21 @@ connection_information(#sslsocket{pid = Pid}) when is_pid(Pid) ->
end;
connection_information(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
{error, enotconn};
-connection_information(#sslsocket{pid = {udp,_}}) ->
+connection_information(#sslsocket{pid = {dtls,_}}) ->
{error,enotconn}.
%%--------------------------------------------------------------------
--spec connection_information(#sslsocket{}, [atom()]) -> {ok, list()} | {error, reason()}.
+-spec connection_information(SslSocket, Items) -> {ok, Result} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Items :: [OptionName],
+ Result :: [{OptionName, OptionValue}],
+ OptionName :: atom(),
+ OptionValue :: any().
%%
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
-connection_information(#sslsocket{} = SSLSocket, Items) ->
- case connection_information(SSLSocket) of
+connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) ->
+ case ssl_connection:connection_information(Pid, include_security_info(Items)) of
{ok, Info} ->
{ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items),
Value =/= undefined]};
@@ -333,99 +903,180 @@ connection_information(#sslsocket{} = SSLSocket, Items) ->
end.
%%--------------------------------------------------------------------
-%% Deprecated
--spec connection_info(#sslsocket{}) -> {ok, {tls_record:tls_atom_version(), ssl_cipher:erl_cipher_suite()}} |
- {error, reason()}.
-%%
-%% Description: Returns ssl protocol and cipher used for the connection
-%%--------------------------------------------------------------------
-connection_info(#sslsocket{} = SSLSocket) ->
- case connection_information(SSLSocket) of
- {ok, Result} ->
- {ok, {proplists:get_value(protocol, Result), proplists:get_value(cipher_suite, Result)}};
- Error ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
+-spec peername(SslSocket) -> {ok, {Address, Port}} |
+ {error, reason()} when
+ SslSocket :: sslsocket(),
+ Address :: inet:ip_address(),
+ Port :: inet:port_number().
%%
%% Description: same as inet:peername/1.
%%--------------------------------------------------------------------
-peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid)->
+peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket,_}}) when is_pid(Pid)->
dtls_socket:peername(Transport, Socket);
-peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid)->
+peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket,_,_}}) when is_pid(Pid)->
tls_socket:peername(Transport, Socket);
-peername(#sslsocket{pid = {udp = Transport, #config{udp_handler = {_Pid, _}}}}) ->
- dtls_socket:peername(Transport, undefined);
-peername(#sslsocket{pid = Pid, fd = {gen_udp= Transport, Socket, _, _}}) when is_pid(Pid) ->
- dtls_socket:peername(Transport, Socket);
-peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}) ->
+peername(#sslsocket{pid = {dtls, #config{dtls_handler = {_Pid,_}}}}) ->
+ dtls_socket:peername(dtls, undefined);
+peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_,_}}}}) ->
tls_socket:peername(Transport, ListenSocket); %% Will return {error, enotconn}
-peername(#sslsocket{pid = {udp,_}}) ->
+peername(#sslsocket{pid = {dtls,_}}) ->
{error,enotconn}.
%%--------------------------------------------------------------------
--spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}.
+-spec peercert(SslSocket) -> {ok, Cert} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Cert :: binary().
%%
%% Description: Returns the peercert.
%%--------------------------------------------------------------------
-peercert(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
case ssl_connection:peer_certificate(Pid) of
{ok, undefined} ->
{error, no_peercert};
Result ->
Result
end;
-peercert(#sslsocket{pid = {udp, _}}) ->
+peercert(#sslsocket{pid = {dtls, _}}) ->
{error, enotconn};
peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
{error, enotconn}.
%%--------------------------------------------------------------------
--spec negotiated_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}.
+-spec negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, Reason} when
+ SslSocket :: sslsocket(),
+ Protocol :: binary(),
+ Reason :: protocol_not_negotiated.
%%
%% Description: Returns the protocol that has been negotiated. If no
%% protocol has been negotiated will return {error, protocol_not_negotiated}
%%--------------------------------------------------------------------
-negotiated_protocol(#sslsocket{pid = Pid}) ->
+negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
ssl_connection:negotiated_protocol(Pid).
%%--------------------------------------------------------------------
--spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}.
-%%
-%% Description: Returns the next protocol that has been negotiated. If no
-%% protocol has been negotiated will return {error, next_protocol_not_negotiated}
-%%--------------------------------------------------------------------
-negotiated_next_protocol(Socket) ->
- case negotiated_protocol(Socket) of
- {error, protocol_not_negotiated} ->
- {error, next_protocol_not_negotiated};
- Res ->
- Res
- end.
-
-%%--------------------------------------------------------------------
--spec cipher_suites() -> [ssl_cipher:erl_cipher_suite()] | [string()].
+-spec cipher_suites() -> [old_cipher_suite()] | [string()].
%%--------------------------------------------------------------------
cipher_suites() ->
cipher_suites(erlang).
%%--------------------------------------------------------------------
--spec cipher_suites(erlang | openssl | all) -> [ssl_cipher:erl_cipher_suite()] |
- [string()].
+-spec cipher_suites(Type) -> [old_cipher_suite() | string()] when
+ Type :: erlang | openssl | all.
+
%% Description: Returns all supported cipher suites.
%%--------------------------------------------------------------------
cipher_suites(erlang) ->
- [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(default)];
+ [ssl_cipher_format:suite_legacy(Suite) || Suite <- available_suites(default)];
cipher_suites(openssl) ->
- [ssl_cipher:openssl_suite_name(Suite) || Suite <- available_suites(default)];
+ [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite)) ||
+ Suite <- available_suites(default)];
cipher_suites(all) ->
- [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(all)].
+ [ssl_cipher_format:suite_legacy(Suite) || Suite <- available_suites(all)].
+
+%%--------------------------------------------------------------------
+-spec cipher_suites(Supported, Version) -> ciphers() when
+ Supported :: default | all | 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';
+ 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 == '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)].
+
+%%--------------------------------------------------------------------
+-spec cipher_suites(Supported, Version, rfc | openssl) -> [string()] when
+ Supported :: default | all | 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';
+ 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)].
+
+%%--------------------------------------------------------------------
+-spec filter_cipher_suites(Suites, Filters) -> Ciphers when
+ Suites :: ciphers(),
+ Filters :: cipher_filters(),
+ Ciphers :: ciphers().
+
+%% Description: Removes cipher suites if any of the filter functions returns false
+%% for any part of the cipher suite. This function also calls default filter functions
+%% to make sure the cipher suite are supported by crypto.
+%%--------------------------------------------------------------------
+filter_cipher_suites(Suites, Filters0) ->
+ #{key_exchange_filters := KexF,
+ cipher_filters := CipherF,
+ mac_filters := MacF,
+ prf_filters := PrfF}
+ = ssl_cipher:crypto_support_filters(),
+ Filters = #{key_exchange_filters => add_filter(proplists:get_value(key_exchange, Filters0), KexF),
+ cipher_filters => add_filter(proplists:get_value(cipher, Filters0), CipherF),
+ mac_filters => add_filter(proplists:get_value(mac, Filters0), MacF),
+ prf_filters => add_filter(proplists:get_value(prf, Filters0), PrfF)},
+ ssl_cipher:filter_suites(Suites, Filters).
+%%--------------------------------------------------------------------
+-spec prepend_cipher_suites(Preferred, Suites) -> ciphers() when
+ Preferred :: ciphers() | cipher_filters(),
+ Suites :: ciphers().
+
+%% 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
+%% filters are use on Suites to extract the the preferred
+%% cipher list.
+%% --------------------------------------------------------------------
+prepend_cipher_suites([First | _] = Preferred, Suites0) when is_map(First) ->
+ Suites = Preferred ++ (Suites0 -- Preferred),
+ Suites;
+prepend_cipher_suites(Filters, Suites) ->
+ Preferred = filter_cipher_suites(Suites, Filters),
+ Preferred ++ (Suites -- Preferred).
+%%--------------------------------------------------------------------
+-spec append_cipher_suites(Deferred, Suites) -> ciphers() when
+ Deferred :: ciphers() | cipher_filters(),
+ Suites :: ciphers().
+
+%% Description: Make <Deferred> suites suites become the
+%% least prefered suites that is put them at the end of the cipher suite list
+%% and removed them from <Suites> if present.
+%%
+%%--------------------------------------------------------------------
+append_cipher_suites([First | _] = Deferred, Suites0) when is_map(First)->
+ Suites = (Suites0 -- Deferred) ++ Deferred,
+ Suites;
+append_cipher_suites(Filters, Suites) ->
+ Deferred = filter_cipher_suites(Suites, Filters),
+ (Suites -- Deferred) ++ Deferred.
%%--------------------------------------------------------------------
--spec eccs() -> tls_v1:curves().
+-spec eccs() -> NamedCurves when
+ NamedCurves :: [named_curve()].
+
%% Description: returns all supported curves across all versions
%%--------------------------------------------------------------------
eccs() ->
@@ -433,18 +1084,24 @@ eccs() ->
eccs_filter_supported(Curves).
%%--------------------------------------------------------------------
--spec eccs(tls_record:tls_version() | tls_record:tls_atom_version()) ->
- tls_v1:curves().
+-spec eccs(Version) -> NamedCurves when
+ Version :: protocol_version(),
+ NamedCurves :: [named_curve()].
+
%% Description: returns the curves supported for a given version of
%% ssl/tls.
%%--------------------------------------------------------------------
-eccs({3,0}) ->
+eccs(sslv3) ->
[];
-eccs({3,_}) ->
+eccs('dtlsv1') ->
+ eccs('tlsv1.1');
+eccs('dtlsv1.2') ->
+ eccs('tlsv1.2');
+eccs(Version) when Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == tlsv1 ->
Curves = tls_v1:ecc_curves(all),
- eccs_filter_supported(Curves);
-eccs(AtomVersion) when is_atom(AtomVersion) ->
- eccs(tls_record:protocol_version(AtomVersion)).
+ eccs_filter_supported(Curves).
eccs_filter_supported(Curves) ->
CryptoCurves = crypto:ec_curves(),
@@ -452,14 +1109,40 @@ eccs_filter_supported(Curves) ->
Curves).
%%--------------------------------------------------------------------
--spec getopts(#sslsocket{}, [gen_tcp:option_name()]) ->
- {ok, [gen_tcp:option()]} | {error, reason()}.
+-spec groups() -> [group()].
+%% Description: returns all supported groups (TLS 1.3 and later)
+%%--------------------------------------------------------------------
+groups() ->
+ tls_v1:groups(4).
+
+%%--------------------------------------------------------------------
+-spec groups(default) -> [group()].
+%% Description: returns the default groups (TLS 1.3 and later)
+%%--------------------------------------------------------------------
+groups(default) ->
+ tls_v1:default_groups(4).
+
+%%--------------------------------------------------------------------
+-spec getopts(SslSocket, OptionNames) ->
+ {ok, [gen_tcp:option()]} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ OptionNames :: [gen_tcp:option_name()].
%%
%% Description: Gets options
%%--------------------------------------------------------------------
-getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) ->
+getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) ->
ssl_connection:get_opts(Pid, OptionTags);
-getopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket,
+getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) ->
+ try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of
+ {ok, _} = Result ->
+ Result;
+ {error, InetError} ->
+ {error, {options, {socket_options, OptionTags, InetError}}}
+ catch
+ _:Error ->
+ {error, {options, {socket_options, OptionTags, Error}}}
+ end;
+getopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket,
OptionTags) when is_list(OptionTags) ->
try tls_socket:getopts(Transport, ListenSocket, OptionTags) of
{ok, _} = Result ->
@@ -474,11 +1157,32 @@ getopts(#sslsocket{}, OptionTags) ->
{error, {options, {socket_options, OptionTags}}}.
%%--------------------------------------------------------------------
--spec setopts(#sslsocket{}, [gen_tcp:option()]) -> ok | {error, reason()}.
+-spec setopts(SslSocket, Options) -> ok | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Options :: [gen_tcp:option()].
%%
%% Description: Sets options
%%--------------------------------------------------------------------
-setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) ->
+setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Options0) ->
+ try proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Options0) of
+ Options ->
+ case proplists:get_value(packet, Options, undefined) of
+ undefined ->
+ ssl_connection:set_opts(Pid, Options);
+ PacketOpt ->
+ case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of
+ ok ->
+ ssl_connection:set_opts(Pid, Options);
+ Error ->
+ Error
+ end
+ end
+ catch
+ _:_ ->
+ {error, {options, {not_a_proplist, Options0}}}
+ end;
+setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0) ->
try proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Options0) of
Options ->
@@ -487,8 +1191,17 @@ setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) ->
_:_ ->
{error, {options, {not_a_proplist, Options0}}}
end;
-
-setopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) ->
+setopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, Options) when is_list(Options) ->
+ try dtls_socket:setopts(Transport, ListenSocket, Options) of
+ ok ->
+ ok;
+ {error, InetError} ->
+ {error, {options, {socket_options, Options, InetError}}}
+ catch
+ _:Error ->
+ {error, {options, {socket_options, Options, Error}}}
+ end;
+setopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, Options) when is_list(Options) ->
try tls_socket:setopts(Transport, ListenSocket, Options) of
ok ->
ok;
@@ -502,9 +1215,9 @@ setopts(#sslsocket{}, Options) ->
{error, {options,{not_a_proplist, Options}}}.
%%---------------------------------------------------------------
--spec getstat(Socket) ->
- {ok, OptionValues} | {error, inet:posix()} when
- Socket :: #sslsocket{},
+-spec getstat(SslSocket) ->
+ {ok, OptionValues} | {error, inet:posix()} when
+ SslSocket :: sslsocket(),
OptionValues :: [{inet:stat_option(), integer()}].
%%
%% Description: Get all statistic options for a socket.
@@ -513,97 +1226,111 @@ getstat(Socket) ->
getstat(Socket, inet:stats()).
%%---------------------------------------------------------------
--spec getstat(Socket, Options) ->
- {ok, OptionValues} | {error, inet:posix()} when
- Socket :: #sslsocket{},
+-spec getstat(SslSocket, Options) ->
+ {ok, OptionValues} | {error, inet:posix()} when
+ SslSocket :: sslsocket(),
Options :: [inet:stat_option()],
OptionValues :: [{inet:stat_option(), integer()}].
%%
%% Description: Get one or more statistic options for a socket.
%%--------------------------------------------------------------------
-getstat(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}, Options) when is_port(Listen), is_list(Options) ->
+getstat(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _, _}}}}, Options) when is_port(Listen), is_list(Options) ->
tls_socket:getstat(Transport, Listen, Options);
-getstat(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) ->
+getstat(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) ->
tls_socket:getstat(Transport, Socket, Options).
%%---------------------------------------------------------------
--spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}.
+-spec shutdown(SslSocket, How) -> ok | {error, reason()} when
+ SslSocket :: sslsocket(),
+ How :: read | write | read_write.
%%
%% Description: Same as gen_tcp:shutdown/2
%%--------------------------------------------------------------------
-shutdown(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}},
+shutdown(#sslsocket{pid = {Listen, #config{transport_info = Info}}},
How) when is_port(Listen) ->
+ Transport = element(1, Info),
Transport:shutdown(Listen, How);
-shutdown(#sslsocket{pid = {udp,_}},_) ->
+shutdown(#sslsocket{pid = {dtls,_}},_) ->
{error, enotconn};
-shutdown(#sslsocket{pid = Pid}, How) ->
+shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) ->
ssl_connection:shutdown(Pid, How).
%%--------------------------------------------------------------------
--spec sockname(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
+-spec sockname(SslSocket) ->
+ {ok, {Address, Port}} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Address :: inet:ip_address(),
+ Port :: inet:port_number().
%%
%% Description: Same as inet:sockname/1
%%--------------------------------------------------------------------
-sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}) when is_port(Listen) ->
+sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_,_,_,_}}}}) when is_port(Listen) ->
tls_socket:sockname(Transport, Listen);
-sockname(#sslsocket{pid = {udp, #config{udp_handler = {Pid, _}}}}) ->
- dtls_udp_listener:sockname(Pid);
-sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid) ->
+sockname(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) ->
+ dtls_packet_demux:sockname(Pid);
+sockname(#sslsocket{pid = [Pid|_], fd = {Transport, Socket,_}}) when is_pid(Pid) ->
dtls_socket:sockname(Transport, Socket);
-sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid) ->
+sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket,_,_}}) when is_pid(Pid) ->
tls_socket:sockname(Transport, Socket).
%%---------------------------------------------------------------
--spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Returns list of session info currently [{session_id, session_id(),
-%% {cipher_suite, cipher_suite()}]
-%%--------------------------------------------------------------------
-session_info(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- ssl_connection:session_info(Pid);
-session_info(#sslsocket{pid = {udp,_}}) ->
- {error, enotconn};
-session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
- {error, enotconn}.
-
-%%---------------------------------------------------------------
--spec versions() -> [{ssl_app, string()} | {supported, [tls_record:tls_atom_version()]} |
- {available, [tls_record:tls_atom_version()]}].
+-spec versions() -> [VersionInfo] when
+ VersionInfo :: {ssl_app, string()} |
+ {supported | available, [tls_version()]} |
+ {supported_dtls | available_dtls, [dtls_version()]}.
%%
%% Description: Returns a list of relevant versions.
%%--------------------------------------------------------------------
versions() ->
- Vsns = tls_record:supported_protocol_versions(),
- SupportedVsns = [tls_record:protocol_version(Vsn) || Vsn <- Vsns],
- AvailableVsns = ?ALL_AVAILABLE_VERSIONS,
- %% TODO Add DTLS versions when supported
- [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}].
+ 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},
+ {supported_dtls, SupportedDTLSVsns},
+ {available, AvailableTLSVsns},
+ {available_dtls, AvailableDTLSVsns}].
%%---------------------------------------------------------------
--spec renegotiate(#sslsocket{}) -> ok | {error, reason()}.
+-spec renegotiate(SslSocket) -> ok | {error, reason()} when
+ SslSocket :: sslsocket().
%%
%% Description: Initiates a renegotiation.
%%--------------------------------------------------------------------
-renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+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);
+ Error ->
+ Error
+ end;
+renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) ->
ssl_connection:renegotiation(Pid);
-renegotiate(#sslsocket{pid = {udp,_}}) ->
+renegotiate(#sslsocket{pid = {dtls,_}}) ->
{error, enotconn};
renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
{error, enotconn}.
%%--------------------------------------------------------------------
--spec prf(#sslsocket{}, binary() | 'master_secret', binary(),
- binary() | prf_random(), non_neg_integer()) ->
- {ok, binary()} | {error, reason()}.
+-spec prf(SslSocket, Secret, Label, Seed, WantedLength) ->
+ {ok, binary()} | {error, reason()} when
+ SslSocket :: sslsocket(),
+ Secret :: binary() | 'master_secret',
+ Label::binary(),
+ Seed :: [binary() | prf_random()],
+ WantedLength :: non_neg_integer().
%%
%% Description: use a ssl sessions TLS PRF to generate key material
%%--------------------------------------------------------------------
-prf(#sslsocket{pid = Pid},
+prf(#sslsocket{pid = [Pid|_]},
Secret, Label, Seed, WantedLength) when is_pid(Pid) ->
ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength);
-prf(#sslsocket{pid = {udp,_}}, _,_,_,_) ->
+prf(#sslsocket{pid = {dtls,_}}, _,_,_,_) ->
{error, enotconn};
prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) ->
{error, enotconn}.
@@ -617,7 +1344,8 @@ clear_pem_cache() ->
ssl_pem_cache:clear().
%%---------------------------------------------------------------
--spec format_error({error, term()}) -> list().
+-spec format_error({error, Reason}) -> string() when
+ Reason :: any().
%%
%% Description: Creates error string.
%%--------------------------------------------------------------------
@@ -627,8 +1355,8 @@ format_error(Reason) when is_list(Reason) ->
Reason;
format_error(closed) ->
"TLS connection is closed";
-format_error({tls_alert, Description}) ->
- "TLS Alert: " ++ Description;
+format_error({tls_alert, {_, Description}}) ->
+ Description;
format_error({options,{FileType, File, Reason}}) when FileType == cacertfile;
FileType == certfile;
FileType == keyfile;
@@ -655,30 +1383,84 @@ tls_version({3, _} = Version) ->
tls_version({254, _} = Version) ->
dtls_v1:corresponding_tls_version(Version).
+%%--------------------------------------------------------------------
+-spec suite_to_str(CipherSuite) -> string() when
+ CipherSuite :: erl_cipher_suite();
+ (CipherSuite) -> string() when
+ %% For internal use!
+ CipherSuite :: #{key_exchange := null,
+ cipher := null,
+ mac := null,
+ prf := null}.
+%%
+%% Description: Return the string representation of a cipher suite.
+%%--------------------------------------------------------------------
+suite_to_str(Cipher) ->
+ ssl_cipher_format:suite_map_to_str(Cipher).
+
+%%--------------------------------------------------------------------
+-spec suite_to_openssl_str(CipherSuite) -> string() when
+ CipherSuite :: erl_cipher_suite().
+%%
+%% Description: Return the string representation of a cipher suite.
+%%--------------------------------------------------------------------
+suite_to_openssl_str(Cipher) ->
+ ssl_cipher_format:suite_map_to_openssl_str(Cipher).
+
+%%
+%%--------------------------------------------------------------------
+-spec str_to_suite(CipherSuiteName) -> erl_cipher_suite() when
+ CipherSuiteName :: string() | {error, {not_recognized, CipherSuiteName :: string()}}.
+%%
+%% Description: Return the map representation of a cipher suite.
+%%--------------------------------------------------------------------
+str_to_suite(CipherSuiteName) ->
+ try
+ %% Note in TLS-1.3 OpenSSL conforms to RFC names
+ %% so if CipherSuiteName starts with TLS this
+ %% function will call ssl_cipher_format:suite_str_to_map
+ %% so both RFC names and legacy OpenSSL names of supported
+ %% cipher suites will be handled
+ ssl_cipher_format:suite_openssl_str_to_map(CipherSuiteName)
+ catch
+ _:_ ->
+ {error, {not_recognized, CipherSuiteName}}
+ end.
+
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
-
+
%% Possible filters out suites not supported by crypto
available_suites(default) ->
Version = tls_record:highest_protocol_version([]),
ssl_cipher:filter_suites(ssl_cipher:suites(Version));
-
available_suites(all) ->
Version = tls_record:highest_protocol_version([]),
ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)).
-do_listen(Port, #config{transport_info = {Transport, _, _, _}} = Config, tls_connection) ->
+supported_suites(default, Version) ->
+ ssl_cipher:suites(Version);
+supported_suites(all, Version) ->
+ ssl_cipher:all_suites(Version);
+supported_suites(anonymous, Version) ->
+ ssl_cipher:anonymous_suites(Version).
+
+do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_connection) ->
tls_socket:listen(Transport, Port, Config);
-do_listen(Port, #config{transport_info = {Transport, _, _, _}} = Config, dtls_connection) ->
- dtls_socket:listen(Transport, Port, Config).
+do_listen(Port, Config, dtls_connection) ->
+ dtls_socket:listen(Port, Config).
%% Handle extra ssl options given to ssl_accept
-spec handle_options([any()], #ssl_options{}) -> #ssl_options{}
; ([any()], client | server) -> {ok, #config{}}.
+handle_options(Opts, Role) ->
+ handle_options(Opts, Role, undefined).
+
+
handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0,
- cacertfile = CaCertFile0} = InheritedSslOpts) ->
+ cacertfile = CaCertFile0} = InheritedSslOpts, _) ->
RecordCB = record_cb(Protocol),
CaCerts = handle_option(cacerts, Opts0, CaCerts0),
{Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder,
@@ -705,37 +1487,44 @@ handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0,
[] ->
new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB);
Value ->
- Versions = [RecordCB:protocol_version(Vsn) || Vsn <- Value],
+ Versions0 = [RecordCB:protocol_version(Vsn) || Vsn <- Value],
+ Versions1 = lists:sort(fun RecordCB:is_higher/2, Versions0),
new_ssl_options(proplists:delete(versions, SslOpts1),
- NewVerifyOpts#ssl_options{versions = Versions}, record_cb(Protocol))
+ NewVerifyOpts#ssl_options{versions = Versions1}, record_cb(Protocol))
end;
%% Handle all options in listen and connect
-handle_options(Opts0, Role) ->
+handle_options(Opts0, Role, Host) ->
Opts = proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Opts0),
assert_proplist(Opts),
RecordCb = record_cb(Opts),
-
- ReuseSessionFun = fun(_, _, _, _) -> true end,
CaCerts = handle_option(cacerts, Opts, undefined),
{Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} =
handle_verify_options(Opts, CaCerts),
CertFile = handle_option(certfile, Opts, <<>>),
- RecordCb = record_cb(Opts),
- Versions = case handle_option(versions, Opts, []) of
- [] ->
- RecordCb:supported_protocol_versions();
- Vsns ->
- [RecordCb:protocol_version(Vsn) || Vsn <- Vsns]
- end,
+ [HighestVersion|_] = Versions =
+ case handle_option(versions, Opts, []) of
+ [] ->
+ RecordCb:supported_protocol_versions();
+ Vsns ->
+ Versions0 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns],
+ lists:sort(fun RecordCb:is_higher/2, Versions0)
+ end,
Protocol = handle_option(protocol, Opts, tls),
- SSLOptions = #ssl_options{
+ case Versions of
+ [{3, 0}] ->
+ reject_alpn_next_prot_options(Opts);
+ _ ->
+ ok
+ end,
+
+ SSLOptions0 = #ssl_options{
versions = Versions,
verify = validate_option(verify, Verify),
verify_fun = VerifyFun,
@@ -756,17 +1545,32 @@ handle_options(Opts0, Role) ->
psk_identity = handle_option(psk_identity, Opts, undefined),
srp_identity = handle_option(srp_identity, Opts, undefined),
ciphers = handle_cipher_option(proplists:get_value(ciphers, Opts, []),
- RecordCb:highest_protocol_version(Versions)),
+ HighestVersion),
eccs = handle_eccs_option(proplists:get_value(eccs, Opts, eccs()),
- RecordCb:highest_protocol_version(Versions)),
- signature_algs = handle_hashsigns_option(proplists:get_value(signature_algs, Opts,
- default_option_role(server,
- tls_v1:default_signature_algs(Versions), Role)),
- tls_version(RecordCb:highest_protocol_version(Versions))),
- %% Server side option
- reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
- reuse_sessions = handle_option(reuse_sessions, Opts, true),
- secure_renegotiate = handle_option(secure_renegotiate, Opts, false),
+ HighestVersion),
+ supported_groups = handle_supported_groups_option(
+ proplists:get_value(supported_groups, Opts, groups(default)),
+ HighestVersion),
+ signature_algs =
+ handle_hashsigns_option(
+ proplists:get_value(
+ signature_algs,
+ Opts,
+ default_option_role_sign_algs(server,
+ tls_v1:default_signature_algs(HighestVersion),
+ Role,
+ HighestVersion)),
+ tls_version(HighestVersion)),
+ signature_algs_cert =
+ handle_signature_algorithms_option(
+ proplists:get_value(
+ signature_algs_cert,
+ Opts,
+ undefined), %% Do not send by default
+ tls_version(HighestVersion)),
+ reuse_sessions = handle_reuse_sessions_option(reuse_sessions, Opts, Role),
+ reuse_session = handle_reuse_session_option(reuse_session, Opts, Role),
+ secure_renegotiate = handle_option(secure_renegotiate, Opts, true),
client_renegotiation = handle_option(client_renegotiation, Opts,
default_option_role(server, true, Role),
server, Role),
@@ -782,8 +1586,9 @@ handle_options(Opts0, Role) ->
next_protocol_selector =
make_next_protocol_selector(
handle_option(client_preferred_next_protocols, Opts, undefined)),
- log_alert = handle_option(log_alert, Opts, true),
- server_name_indication = handle_option(server_name_indication, Opts, undefined),
+ server_name_indication = handle_option(server_name_indication, Opts,
+ default_option_role(client,
+ server_name_indication_default(Host), Role)),
sni_hosts = handle_option(sni_hosts, Opts, []),
sni_fun = handle_option(sni_fun, Opts, undefined),
honor_cipher_order = handle_option(honor_cipher_order, Opts,
@@ -802,11 +1607,16 @@ handle_options(Opts0, Role) ->
client, Role),
crl_check = handle_option(crl_check, Opts, false),
crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}),
- v2_hello_compatible = handle_option(v2_hello_compatible, Opts, false),
- max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE)
+ max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE),
+ handshake = handle_option(handshake, Opts, full),
+ customize_hostname_check = handle_option(customize_hostname_check, Opts, [])
},
+ LogLevel = handle_option(log_alert, Opts, true),
+ SSLOptions = SSLOptions0#ssl_options{
+ log_level = handle_option(log_level, Opts, LogLevel)
+ },
- CbInfo = proplists:get_value(cb_info, Opts, default_cb_info(Protocol)),
+ CbInfo = handle_option(cb_info, Opts, default_cb_info(Protocol)),
SslOptions = [protocol, versions, verify, verify_fun, partial_chain,
fail_if_no_peer_cert, verify_client_once,
depth, cert, certfile, key, keyfile,
@@ -816,10 +1626,11 @@ handle_options(Opts0, Role) ->
cb_info, renegotiate_at, secure_renegotiate, hibernate_after,
erl_dist, alpn_advertised_protocols, sni_hosts, sni_fun,
alpn_preferred_protocols, next_protocols_advertised,
- client_preferred_next_protocols, log_alert,
+ client_preferred_next_protocols, log_alert, log_level,
server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache,
- fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation, v2_hello_compatible,
- max_handshake_size],
+ fallback, signature_algs, signature_algs_cert, eccs, honor_ecc_order,
+ beast_mitigation, max_handshake_size, handshake, customize_hostname_check,
+ supported_groups],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -829,11 +1640,9 @@ handle_options(Opts0, Role) ->
ConnetionCb = connection_cb(Opts),
{ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = Sock,
- inet_user = SockOpts, transport_info = CbInfo, connection_cb = ConnetionCb
+ inet_user = Sock, transport_info = CbInfo, connection_cb = ConnetionCb
}}.
-
-
handle_option(OptionName, Opts, Default, Role, Role) ->
handle_option(OptionName, Opts, Default);
handle_option(_, _, undefined = Value, _, _) ->
@@ -851,6 +1660,10 @@ handle_option(sni_fun, Opts, Default) ->
_ ->
throw({error, {conflict_options, [sni_fun, sni_hosts]}})
end;
+handle_option(cb_info, Opts, Default) ->
+ CbInfo = proplists:get_value(cb_info, Opts, Default),
+ true = validate_option(cb_info, CbInfo),
+ handle_cb_info(CbInfo, Default);
handle_option(OptionName, Opts, Default) ->
validate_option(OptionName,
proplists:get_value(OptionName, Opts, Default)).
@@ -909,7 +1722,8 @@ validate_option(key, {KeyType, Value}) when is_binary(Value),
KeyType == 'ECPrivateKey';
KeyType == 'PrivateKeyInfo' ->
{KeyType, Value};
-
+validate_option(key, #{algorithm := _} = Value) ->
+ Value;
validate_option(keyfile, undefined) ->
<<>>;
validate_option(keyfile, Value) when is_binary(Value) ->
@@ -955,11 +1769,16 @@ validate_option(srp_identity, {Username, Password})
{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) ->
Value;
+validate_option(reuse_session, Value) when is_binary(Value) ->
+ Value;
validate_option(reuse_sessions, Value) when is_boolean(Value) ->
Value;
-
+validate_option(reuse_sessions, save = Value) ->
+ Value;
validate_option(secure_renegotiate, Value) when is_boolean(Value) ->
Value;
validate_option(client_renegotiation, Value) when is_boolean(Value) ->
@@ -976,63 +1795,65 @@ validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 ->
validate_option(erl_dist,Value) when is_boolean(Value) ->
Value;
-validate_option(Opt, Value)
- when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols,
- is_list(Value) ->
- case tls_record:highest_protocol_version([]) of
- {3,0} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- _ ->
- validate_binary_list(Opt, Value),
- Value
- end;
+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(Opt, Value)
when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols,
Value =:= undefined ->
undefined;
-validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value)
+validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols})
when is_list(PreferredProtocols) ->
- case tls_record:highest_protocol_version([]) of
- {3,0} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- _ ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- {Precedence, PreferredProtocols, ?NO_PROTOCOL}
- end;
-validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value)
- when is_list(PreferredProtocols), is_binary(Default),
- byte_size(Default) > 0, byte_size(Default) < 256 ->
- case tls_record:highest_protocol_version([]) of
- {3,0} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- _ ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- Value
- end;
-
+ 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(log_alert, Value) when is_boolean(Value) ->
+validate_option(log_alert, true) ->
+ notice;
+validate_option(log_alert, false) ->
+ warning;
+validate_option(log_level, Value) when
+ is_atom(Value) andalso
+ (Value =:= emergency orelse
+ Value =:= alert orelse
+ Value =:= critical orelse
+ Value =:= error orelse
+ Value =:= warning orelse
+ Value =:= notice orelse
+ 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 = Opt, Value) when is_list(Value) ->
- case tls_record:highest_protocol_version([]) of
- {3,0} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- _ ->
- validate_binary_list(next_protocols_advertised, Value),
- Value
- end;
-
validate_option(next_protocols_advertised, 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...
Value;
-validate_option(server_name_indication, disable) ->
- disable;
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) ->
@@ -1065,30 +1886,97 @@ validate_option(beast_mitigation, Value) when Value == one_n_minus_one orelse
Value == zero_n orelse
Value == disabled ->
Value;
-validate_option(v2_hello_compatible, Value) when is_boolean(Value) ->
- Value;
validate_option(max_handshake_size, Value) when is_integer(Value) andalso Value =< ?MAX_UNIT24 ->
Value;
validate_option(protocol, Value = tls) ->
Value;
validate_option(protocol, Value = dtls) ->
Value;
+validate_option(handshake, hello = Value) ->
+ Value;
+validate_option(handshake, full = Value) ->
+ Value;
+validate_option(customize_hostname_check, Value) when is_list(Value) ->
+ Value;
+validate_option(cb_info, {V1, V2, V3, V4}) when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4)
+ ->
+ true;
+validate_option(cb_info, {V1, V2, V3, V4, V5}) when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4),
+ is_atom(V5)
+ ->
+ true;
+validate_option(cb_info, _) ->
+ false;
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
-handle_hashsigns_option(Value, {Major, Minor} = Version) when is_list(Value)
- andalso Major >= 3 andalso Minor >= 3->
+handle_cb_info({V1, V2, V3, V4}, {_,_,_,_,_}) ->
+ {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")};
+handle_cb_info(CbInfo, _) ->
+ CbInfo.
+
+handle_hashsigns_option(Value, Version) when is_list(Value)
+ andalso Version >= {3, 4} ->
+ case tls_v1:signature_schemes(Version, Value) of
+ [] ->
+ throw({error, {options,
+ no_supported_signature_schemes,
+ {signature_algs, Value}}});
+ _ ->
+ Value
+ end;
+handle_hashsigns_option(Value, Version) when is_list(Value)
+ andalso Version =:= {3, 3} ->
case tls_v1:signature_algs(Version, Value) of
[] ->
throw({error, {options, no_supported_algorithms, {signature_algs, Value}}});
_ ->
Value
end;
-handle_hashsigns_option(_, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3->
+handle_hashsigns_option(_, Version) when Version =:= {3, 3} ->
handle_hashsigns_option(tls_v1:default_signature_algs(Version), Version);
handle_hashsigns_option(_, _Version) ->
undefined.
+handle_signature_algorithms_option(Value, Version) when is_list(Value)
+ andalso Version >= {3, 4} ->
+ case tls_v1:signature_schemes(Version, Value) of
+ [] ->
+ throw({error, {options,
+ no_supported_signature_schemes,
+ {signature_algs_cert, Value}}});
+ _ ->
+ Value
+ end;
+handle_signature_algorithms_option(_, _Version) ->
+ undefined.
+
+handle_reuse_sessions_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_sessions_option(Key, Opts0, server) ->
+ Opts = proplists:delete({Key, save}, Opts0),
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value.
+
+handle_reuse_session_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, undefined),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_session_option(Key, Opts, server) ->
+ ReuseSessionFun = fun(_, _, _, _) -> true end,
+ Value = proplists:get_value(Key, Opts, ReuseSessionFun),
+ validate_option(Key, Value),
+ Value.
+
validate_options([]) ->
[];
validate_options([{Opt, Value} | Tail]) ->
@@ -1112,23 +2000,25 @@ validate_binary_list(Opt, List) ->
end, List).
validate_versions([], Versions) ->
Versions;
-validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2';
+validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
Version == 'tlsv1.1';
Version == tlsv1;
Version == sslv3 ->
tls_validate_versions(Rest, Versions);
validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
- Version == 'dtlsv2'->
+ Version == 'dtlsv1.2'->
dtls_validate_versions(Rest, Versions);
validate_versions([Ver| _], Versions) ->
throw({error, {options, {Ver, {versions, Versions}}}}).
tls_validate_versions([], Versions) ->
Versions;
-tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1;
- Version == sslv3 ->
+tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == tlsv1;
+ Version == sslv3 ->
tls_validate_versions(Rest, Versions);
tls_validate_versions([Ver| _], Versions) ->
throw({error, {options, {Ver, {versions, Versions}}}}).
@@ -1136,29 +2026,11 @@ tls_validate_versions([Ver| _], Versions) ->
dtls_validate_versions([], Versions) ->
Versions;
dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
- Version == 'dtlsv2'->
+ Version == 'dtlsv1.2'->
dtls_validate_versions(Rest, Versions);
dtls_validate_versions([Ver| _], Versions) ->
throw({error, {options, {Ver, {versions, Versions}}}}).
-validate_inet_option(mode, Value)
- when Value =/= list, Value =/= binary ->
- throw({error, {options, {mode,Value}}});
-validate_inet_option(packet, Value)
- when not (is_atom(Value) orelse is_integer(Value)) ->
- throw({error, {options, {packet,Value}}});
-validate_inet_option(packet_size, Value)
- when not is_integer(Value) ->
- throw({error, {options, {packet_size,Value}}});
-validate_inet_option(header, Value)
- when not is_integer(Value) ->
- throw({error, {options, {header,Value}}});
-validate_inet_option(active, Value)
- when Value =/= true, Value =/= false, Value =/= once ->
- throw({error, {options, {active,Value}}});
-validate_inet_option(_, _) ->
- ok.
-
%% The option cacerts overrides cacertsfile
ca_cert_default(_,_, [_|_]) ->
undefined;
@@ -1173,31 +2045,11 @@ ca_cert_default(verify_peer, undefined, _) ->
emulated_options(Protocol, Opts) ->
case Protocol of
tls ->
- emulated_options(Opts, tls_socket:internal_inet_values(), tls_socket:default_inet_values());
+ tls_socket:emulated_options(Opts);
dtls ->
- emulated_options(Opts, dtls_socket:internal_inet_values(), dtls_socket:default_inet_values())
+ dtls_socket:emulated_options(Opts)
end.
-emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) ->
- validate_inet_option(mode, Value),
- emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]);
-emulated_options([{header, Value} = Opt | Opts], Inet, Emulated) ->
- validate_inet_option(header, Value),
- emulated_options(Opts, Inet, [Opt | proplists:delete(header, Emulated)]);
-emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) ->
- validate_inet_option(active, Value),
- emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]);
-emulated_options([{packet, Value} = Opt |Opts], Inet, Emulated) ->
- validate_inet_option(packet, Value),
- emulated_options(Opts, Inet, [Opt | proplists:delete(packet, Emulated)]);
-emulated_options([{packet_size, Value} = Opt | Opts], Inet, Emulated) ->
- validate_inet_option(packet_size, Value),
- emulated_options(Opts, Inet, [Opt | proplists:delete(packet_size, Emulated)]);
-emulated_options([Opt|Opts], Inet, Emulated) ->
- emulated_options(Opts, [Opt|Inet], Emulated);
-emulated_options([], Inet,Emulated) ->
- {Inet, Emulated}.
-
handle_cipher_option(Value, Version) when is_list(Value) ->
try binary_cipher_suites(Version, Value) of
Suites ->
@@ -1212,30 +2064,57 @@ handle_cipher_option(Value, Version) when is_list(Value) ->
binary_cipher_suites(Version, []) ->
%% Defaults to all supported suites that does
%% not require explicit configuration
- ssl_cipher:filter_suites(ssl_cipher:suites(tls_version(Version)));
+ default_binary_suites(Version);
+binary_cipher_suites(Version, [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) ->
- Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0],
+ 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) ->
- All = ssl_cipher:all_suites(tls_version(Version)),
+ 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
- ssl_cipher:filter_suites(ssl_cipher:suites(tls_version(Version)));
+ default_binary_suites(Version);
Ciphers ->
Ciphers
end;
binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) ->
%% Format: ["RC4-SHA","RC4-MD5"]
- Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0],
+ Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- Ciphers0],
binary_cipher_suites(Version, Ciphers);
binary_cipher_suites(Version, Ciphers0) ->
%% Format: "RC4-SHA:RC4-MD5"
- Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")],
+ Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")],
binary_cipher_suites(Version, Ciphers).
+default_binary_suites(Version) ->
+ ssl_cipher:filter_suites(ssl_cipher:suites(Version)).
+
+tuple_to_map({Kex, Cipher, Mac}) ->
+ #{key_exchange => Kex,
+ cipher => Cipher,
+ mac => Mac,
+ prf => default_prf};
+tuple_to_map({Kex, Cipher, Mac, Prf}) ->
+ #{key_exchange => Kex,
+ cipher => Cipher,
+ mac => tuple_to_map_mac(Cipher, Mac),
+ prf => Prf}.
+
+%% Backwards compatible
+tuple_to_map_mac(aes_128_gcm, _) ->
+ aead;
+tuple_to_map_mac(aes_256_gcm, _) ->
+ aead;
+tuple_to_map_mac(chacha20_poly1305, _) ->
+ aead;
+tuple_to_map_mac(_, MAC) ->
+ MAC.
+
handle_eccs_option(Value, Version) when is_list(Value) ->
{_Major, Minor} = tls_version(Version),
try tls_v1:ecc_curves(Minor, Value) of
@@ -1245,6 +2124,16 @@ handle_eccs_option(Value, Version) when is_list(Value) ->
error:_ -> throw({error, {options, {eccs, Value}}})
end.
+handle_supported_groups_option(Value, Version) when is_list(Value) ->
+ {_Major, Minor} = tls_version(Version),
+ try tls_v1:groups(Minor, Value) of
+ Groups -> #supported_groups{supported_groups = Groups}
+ catch
+ exit:_ -> throw({error, {options, {supported_groups, Value}}});
+ error:_ -> throw({error, {options, {supported_groups, Value}}})
+ end.
+
+
unexpected_format(Error) ->
lists:flatten(io_lib:format("Unexpected error: ~p", [Error])).
@@ -1390,8 +2279,10 @@ new_ssl_options([{next_protocols_advertised, Value} | Rest], #ssl_options{} = Op
new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{next_protocol_selector =
make_next_protocol_selector(validate_option(client_preferred_next_protocols, Value))}, RecordCB);
-new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#ssl_options{log_alert = validate_option(log_alert, Value)}, RecordCB);
+new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{log_level = validate_option(log_alert, Value)}, RecordCB);
+new_ssl_options([{log_level, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{log_level = validate_option(log_level, Value)}, RecordCB);
new_ssl_options([{server_name_indication, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{server_name_indication = validate_option(server_name_indication, Value)}, RecordCB);
new_ssl_options([{honor_cipher_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
@@ -1404,12 +2295,26 @@ new_ssl_options([{eccs, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
handle_eccs_option(Value, RecordCB:highest_protocol_version())
},
RecordCB);
+new_ssl_options([{supported_groups, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest,
+ Opts#ssl_options{supported_groups =
+ handle_supported_groups_option(Value, RecordCB:highest_protocol_version())
+ },
+ RecordCB);
new_ssl_options([{signature_algs, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest,
Opts#ssl_options{signature_algs =
handle_hashsigns_option(Value,
tls_version(RecordCB:highest_protocol_version()))},
RecordCB);
+new_ssl_options([{signature_algs_cert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(
+ Rest,
+ Opts#ssl_options{signature_algs_cert =
+ handle_signature_algorithms_option(
+ Value,
+ tls_version(RecordCB:highest_protocol_version()))},
+ RecordCB);
new_ssl_options([{protocol, dtls = Value} | Rest], #ssl_options{} = Opts, dtls_record = RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{protocol = Value}, RecordCB);
new_ssl_options([{protocol, tls = Value} | Rest], #ssl_options{} = Opts, tls_record = RecordCB) ->
@@ -1471,12 +2376,60 @@ handle_verify_options(Opts, CaCerts) ->
throw({error, {options, {verify, Value}}})
end.
+%% Added to handle default values for signature_algs in TLS 1.3
+default_option_role_sign_algs(_, Value, _, Version) when Version >= {3,4} ->
+ Value;
+default_option_role_sign_algs(Role, Value, Role, _) ->
+ Value;
+default_option_role_sign_algs(_, _, _, _) ->
+ undefined.
+
default_option_role(Role, Value, Role) ->
Value;
default_option_role(_,_,_) ->
undefined.
+
default_cb_info(tls) ->
- {gen_tcp, tcp, tcp_closed, tcp_error};
+ {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive};
default_cb_info(dtls) ->
- {gen_udp, udp, udp_closed, udp_error}.
+ {gen_udp, udp, udp_closed, udp_error, udp_passive}.
+
+include_security_info([]) ->
+ false;
+include_security_info([Item | Items]) ->
+ case lists:member(Item, [client_random, server_random, master_secret]) of
+ true ->
+ true;
+ false ->
+ include_security_info(Items)
+ end.
+
+server_name_indication_default(Host) when is_list(Host) ->
+ 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) ->
+ [Filter | Filters].
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 696a55e4b9..2d57b72f7b 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% 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.
@@ -32,7 +32,11 @@
-include("ssl_record.hrl").
-include("ssl_internal.hrl").
--export([decode/1, alert_txt/1, reason_code/2]).
+-export([decode/1,
+ own_alert_txt/1,
+ alert_txt/1,
+ alert_txt/4,
+ reason_code/4]).
%%====================================================================
%% Internal application API
@@ -48,25 +52,54 @@ decode(Bin) ->
decode(Bin, [], 0).
%%--------------------------------------------------------------------
--spec reason_code(#alert{}, client | server) -> closed | {essl, string()}.
+-spec reason_code(#alert{}, client | server, ProtocolName::string(), StateName::atom()) ->
+ {tls_alert, {atom(), unicode:chardata()}} | closed.
%%
%% Description: Returns the error reason that will be returned to the
%% user.
%%--------------------------------------------------------------------
-reason_code(#alert{description = ?CLOSE_NOTIFY}, _) ->
+reason_code(#alert{description = ?CLOSE_NOTIFY}, _, _, _) ->
closed;
-reason_code(#alert{description = Description}, _) ->
- {tls_alert, description_txt(Description)}.
+reason_code(#alert{description = Description, role = Role} = Alert, Role, ProtocolName, StateName) ->
+ Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, own_alert_txt(Alert))),
+ {tls_alert, {description_atom(Description), Txt}};
+reason_code(#alert{description = Description} = Alert, Role, ProtocolName, StateName) ->
+ Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, alert_txt(Alert))),
+ {tls_alert, {description_atom(Description), Txt}}.
+
+%%--------------------------------------------------------------------
+-spec alert_txt(string(), server | client, StateNam::atom(), string()) -> string().
+%%
+%% Description: Generates alert text for log or string part of error return.
+%%--------------------------------------------------------------------
+alert_txt(ProtocolName, Role, StateName, Txt) ->
+ io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]).
+
+%%--------------------------------------------------------------------
+-spec own_alert_txt(#alert{}) -> string().
+%%
+%% Description: Returns the error string for given alert generated
+%% by the erlang implementation.
+%%--------------------------------------------------------------------
+own_alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined, role = Role}) ->
+ "at " ++ Mod ++ ":" ++ integer_to_list(Line) ++ " generated " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++
+ level_txt(Level) ++ description_txt(Description);
+own_alert_txt(#alert{reason = Reason} = Alert) ->
+ BaseTxt = own_alert_txt(Alert#alert{reason = undefined}),
+ FormatDepth = 9, % Some limit on printed representation of an error
+ ReasonTxt = lists:flatten(io_lib:format("~P", [Reason, FormatDepth])),
+ BaseTxt ++ " - " ++ ReasonTxt.
%%--------------------------------------------------------------------
-spec alert_txt(#alert{}) -> string().
%%
-%% Description: Returns the error string for given alert.
+%% Description: Returns the error string for given alert received from
+%% the peer.
%%--------------------------------------------------------------------
-alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined}) ->
- Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++
- level_txt(Level) ++" "++ description_txt(Description);
+alert_txt(#alert{level = Level, description = Description, reason = undefined, role = Role}) ->
+ "received " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++
+ level_txt(Level) ++ description_txt(Description);
alert_txt(#alert{reason = Reason} = Alert) ->
BaseTxt = alert_txt(Alert#alert{reason = undefined}),
FormatDepth = 9, % Some limit on printed representation of an error
@@ -93,73 +126,148 @@ decode(<<>>, Acc, _) ->
lists:reverse(Acc, []).
level_txt(?WARNING) ->
- "Warning:";
+ "Warning - ";
level_txt(?FATAL) ->
- "Fatal error:".
+ "Fatal - ".
description_txt(?CLOSE_NOTIFY) ->
- "close notify";
+ "Close Notify";
description_txt(?UNEXPECTED_MESSAGE) ->
- "unexpected message";
+ "Unexpected Message";
description_txt(?BAD_RECORD_MAC) ->
- "bad record mac";
-description_txt(?DECRYPTION_FAILED) ->
- "decryption failed";
+ "Bad Record MAC";
+description_txt(?DECRYPTION_FAILED_RESERVED) ->
+ "Decryption Failed Reserved";
description_txt(?RECORD_OVERFLOW) ->
- "record overflow";
+ "Record Overflow";
description_txt(?DECOMPRESSION_FAILURE) ->
- "decompression failure";
+ "Decompression Failure";
description_txt(?HANDSHAKE_FAILURE) ->
- "handshake failure";
+ "Handshake Failure";
description_txt(?NO_CERTIFICATE_RESERVED) ->
- "No certificate reserved";
+ "No Certificate Reserved";
description_txt(?BAD_CERTIFICATE) ->
- "bad certificate";
+ "Bad Certificate";
description_txt(?UNSUPPORTED_CERTIFICATE) ->
- "unsupported certificate";
+ "Unsupported Certificate";
description_txt(?CERTIFICATE_REVOKED) ->
- "certificate revoked";
+ "Certificate Revoked";
description_txt(?CERTIFICATE_EXPIRED) ->
- "certificate expired";
+ "Certificate Expired";
description_txt(?CERTIFICATE_UNKNOWN) ->
- "certificate unknown";
+ "Certificate Unknown";
description_txt(?ILLEGAL_PARAMETER) ->
- "illegal parameter";
+ "Illegal Parameter";
description_txt(?UNKNOWN_CA) ->
- "unknown ca";
+ "Unknown CA";
description_txt(?ACCESS_DENIED) ->
- "access denied";
+ "Access Denied";
description_txt(?DECODE_ERROR) ->
- "decode error";
+ "Decode Error";
description_txt(?DECRYPT_ERROR) ->
- "decrypt error";
+ "Decrypt Error";
description_txt(?EXPORT_RESTRICTION) ->
- "export restriction";
+ "Export Restriction";
description_txt(?PROTOCOL_VERSION) ->
- "protocol version";
+ "Protocol Version";
description_txt(?INSUFFICIENT_SECURITY) ->
- "insufficient security";
+ "Insufficient Security";
description_txt(?INTERNAL_ERROR) ->
- "internal error";
+ "Internal Error";
+description_txt(?INAPPROPRIATE_FALLBACK) ->
+ "Inappropriate Fallback";
description_txt(?USER_CANCELED) ->
- "user canceled";
+ "User Canceled";
description_txt(?NO_RENEGOTIATION) ->
- "no renegotiation";
+ "No Renegotiation";
+description_txt(?MISSING_EXTENSION) ->
+ "Missing extension";
description_txt(?UNSUPPORTED_EXTENSION) ->
- "unsupported extension";
+ "Unsupported Extension";
description_txt(?CERTIFICATE_UNOBTAINABLE) ->
- "certificate unobtainable";
+ "Certificate Unobtainable";
description_txt(?UNRECOGNISED_NAME) ->
- "unrecognised name";
+ "Unrecognised Name";
description_txt(?BAD_CERTIFICATE_STATUS_RESPONSE) ->
- "bad certificate status response";
+ "Bad Certificate Status Response";
description_txt(?BAD_CERTIFICATE_HASH_VALUE) ->
- "bad certificate hash value";
+ "Bad Certificate Hash Value";
description_txt(?UNKNOWN_PSK_IDENTITY) ->
- "unknown psk identity";
-description_txt(?INAPPROPRIATE_FALLBACK) ->
- "inappropriate fallback";
+ "Unknown Psk Identity";
+description_txt(?CERTIFICATE_REQUIRED) ->
+ "Certificate required";
description_txt(?NO_APPLICATION_PROTOCOL) ->
- "no application protocol";
+ "No application protocol";
description_txt(Enum) ->
lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])).
+
+description_atom(?CLOSE_NOTIFY) ->
+ close_notify;
+description_atom(?UNEXPECTED_MESSAGE) ->
+ unexpected_message;
+description_atom(?BAD_RECORD_MAC) ->
+ bad_record_mac;
+description_atom(?DECRYPTION_FAILED_RESERVED) ->
+ decryption_failed_reserved;
+description_atom(?RECORD_OVERFLOW) ->
+ record_overflow;
+description_atom(?DECOMPRESSION_FAILURE) ->
+ decompression_failure;
+description_atom(?HANDSHAKE_FAILURE) ->
+ handshake_failure;
+description_atom(?NO_CERTIFICATE_RESERVED) ->
+ no_certificate_reserved;
+description_atom(?BAD_CERTIFICATE) ->
+ bad_certificate;
+description_atom(?UNSUPPORTED_CERTIFICATE) ->
+ unsupported_certificate;
+description_atom(?CERTIFICATE_REVOKED) ->
+ certificate_revoked;
+description_atom(?CERTIFICATE_EXPIRED) ->
+ certificate_expired;
+description_atom(?CERTIFICATE_UNKNOWN) ->
+ certificate_unknown;
+description_atom(?ILLEGAL_PARAMETER) ->
+ illegal_parameter;
+description_atom(?UNKNOWN_CA) ->
+ unknown_ca;
+description_atom(?ACCESS_DENIED) ->
+ access_denied;
+description_atom(?DECODE_ERROR) ->
+ decode_error;
+description_atom(?DECRYPT_ERROR) ->
+ decrypt_error;
+description_atom(?EXPORT_RESTRICTION) ->
+ export_restriction;
+description_atom(?PROTOCOL_VERSION) ->
+ protocol_version;
+description_atom(?INSUFFICIENT_SECURITY) ->
+ insufficient_security;
+description_atom(?INTERNAL_ERROR) ->
+ internal_error;
+description_atom(?INAPPROPRIATE_FALLBACK) ->
+ inappropriate_fallback;
+description_atom(?USER_CANCELED) ->
+ user_canceled;
+description_atom(?NO_RENEGOTIATION) ->
+ no_renegotiation;
+description_atom(?MISSING_EXTENSION) ->
+ missing_extension;
+description_atom(?UNSUPPORTED_EXTENSION) ->
+ unsupported_extension;
+description_atom(?CERTIFICATE_UNOBTAINABLE) ->
+ certificate_unobtainable;
+description_atom(?UNRECOGNISED_NAME) ->
+ unrecognised_name;
+description_atom(?BAD_CERTIFICATE_STATUS_RESPONSE) ->
+ bad_certificate_status_response;
+description_atom(?BAD_CERTIFICATE_HASH_VALUE) ->
+ bad_certificate_hash_value;
+description_atom(?UNKNOWN_PSK_IDENTITY) ->
+ unknown_psk_identity;
+description_atom(?CERTIFICATE_REQUIRED) ->
+ certificate_required;
+description_atom(?NO_APPLICATION_PROTOCOL) ->
+ no_application_protocol;
+description_atom(_) ->
+ 'unsupported/unknown_alert'.
diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl
index f3743ba0f0..9b2322da17 100644
--- a/lib/ssl/src/ssl_alert.hrl
+++ b/lib/ssl/src/ssl_alert.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% 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.
@@ -29,6 +29,9 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Alert protocol - RFC 2246 section 7.2
+%%% updated by RFC 8486 with
+%%% missing_extension(109),
+%%% certificate_required(116),
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% AlertLevel
@@ -40,7 +43,7 @@
%% close_notify(0),
%% unexpected_message(10),
%% bad_record_mac(20),
-%% decryption_failed(21),
+%% decryption_failed_reserved(21),
%% record_overflow(22),
%% decompression_failure(30),
%% handshake_failure(40),
@@ -78,7 +81,7 @@
-define(CLOSE_NOTIFY, 0).
-define(UNEXPECTED_MESSAGE, 10).
-define(BAD_RECORD_MAC, 20).
--define(DECRYPTION_FAILED, 21).
+-define(DECRYPTION_FAILED_RESERVED, 21).
-define(RECORD_OVERFLOW, 22).
-define(DECOMPRESSION_FAILURE, 30).
-define(HANDSHAKE_FAILURE, 40).
@@ -100,12 +103,14 @@
-define(INAPPROPRIATE_FALLBACK, 86).
-define(USER_CANCELED, 90).
-define(NO_RENEGOTIATION, 100).
+-define(MISSING_EXTENSION, 109).
-define(UNSUPPORTED_EXTENSION, 110).
-define(CERTIFICATE_UNOBTAINABLE, 111).
-define(UNRECOGNISED_NAME, 112).
-define(BAD_CERTIFICATE_STATUS_RESPONSE, 113).
-define(BAD_CERTIFICATE_HASH_VALUE, 114).
-define(UNKNOWN_PSK_IDENTITY, 115).
+-define(CERTIFICATE_REQUIRED, 116).
-define(NO_APPLICATION_PROTOCOL, 120).
-define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where={?FILE, ?LINE}}).
@@ -118,6 +123,7 @@
level,
description,
where = {?FILE, ?LINE},
+ role,
reason
}).
-endif. % -ifdef(ssl_alert).
diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl
index 2bd51cf91e..f4594912bd 100644
--- a/lib/ssl/src/ssl_api.hrl
+++ b/lib/ssl/src/ssl_api.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-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.
@@ -21,48 +21,7 @@
-ifndef(ssl_api).
-define(ssl_api, true).
--include("ssl_cipher.hrl").
-
-%% Visible in API
--export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
- prf_random/0, sslsocket/0]).
-
-
%% Looks like it does for backwards compatibility reasons
-record(sslsocket, {fd = nil, pid = nil}).
-
--type sslsocket() :: #sslsocket{}.
--type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
--type socket_connect_option() :: gen_tcp:connect_option().
--type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
--type socket_listen_option() :: gen_tcp:listen_option().
-
--type ssl_option() :: {versions, ssl_record:ssl_atom_version()} |
- {verify, verify_type()} |
- {verify_fun, {fun(), InitialUserState::term()}} |
- {fail_if_no_peer_cert, boolean()} | {depth, integer()} |
- {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} |
- {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
- {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
- {user_lookup_fun, {fun(), InitialUserState::term()}} |
- {psk_identity, string()} |
- {srp_identity, {string(), string()}} |
- {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
- {reuse_session, fun()} | {hibernate_after, integer()|undefined} |
- {alpn_advertised_protocols, [binary()]} |
- {alpn_preferred_protocols, [binary()]} |
- {next_protocols_advertised, list(binary())} |
- {client_preferred_next_protocols, binary(), client | server, list(binary())}.
-
--type verify_type() :: verify_none | verify_peer.
--type path() :: string().
--type ciphers() :: [ssl_cipher:erl_cipher_suite()] |
- string(). % (according to old API)
--type ssl_imp() :: new | old.
-
--type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
- ClosedTag::atom(), ErrTag::atom()}}.
--type prf_random() :: client_random | server_random.
-
-endif. % -ifdef(ssl_api).
diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl
index 62e8765d4a..9e6d676bef 100644
--- a/lib/ssl/src/ssl_app.erl
+++ b/lib/ssl/src/ssl_app.erl
@@ -29,9 +29,26 @@
-export([start/2, stop/1]).
start(_Type, _StartArgs) ->
+ start_logger(),
ssl_sup:start_link().
stop(_State) ->
+ stop_logger(),
ok.
+%%
+%% Description: Start SSL logger
+start_logger() ->
+ Config = #{level => debug,
+ filter_default => stop,
+ formatter => {ssl_logger, #{}}},
+ Filter = {fun logger_filters:domain/2,{log,sub,[otp,ssl]}},
+ logger:add_handler(ssl_handler, logger_std_h, Config),
+ logger:add_handler_filter(ssl_handler, filter_non_ssl, Filter),
+ logger:set_application_level(ssl, debug).
+%%
+%% Description: Stop SSL logger
+stop_logger() ->
+ logger:unset_application_level(ssl),
+ logger:remove_handler(ssl_handler).
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 2046ec75b3..9997f5e0c8 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017 All Rights Reserved.
+%% 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.
@@ -33,6 +33,7 @@
-export([trusted_cert_and_path/4,
certificate_chain/3,
+ certificate_chain/4,
file_to_certificats/2,
file_to_crls/2,
validate/3,
@@ -40,7 +41,8 @@
is_valid_key_usage/2,
select_extension/2,
extensions_list/1,
- public_key_type/1
+ public_key_type/1,
+ foldl_db/3
]).
%%====================================================================
@@ -69,7 +71,7 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -
case SignedAndIssuerID of
{error, issuer_not_found} ->
- %% The root CA was not sent and can not be found.
+ %% The root CA was not sent and cannot be found.
handle_incomplete_chain(Path, PartialChainHandler);
{self, _} when length(Path) == 1 ->
{selfsigned_peer, Path};
@@ -79,7 +81,8 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -
%% Trusted must be selfsigned or it is an incomplete chain
handle_path(Trusted, Path, PartialChainHandler);
_ ->
- %% Root CA could not be verified
+ %% Root CA could not be verified, but partial
+ %% chain handler may trusted a cert that we got
handle_incomplete_chain(Path, PartialChainHandler)
end
end.
@@ -94,10 +97,23 @@ certificate_chain(undefined, _, _) ->
{error, no_cert};
certificate_chain(OwnCert, CertDbHandle, CertsDbRef) when is_binary(OwnCert) ->
ErlCert = public_key:pkix_decode_cert(OwnCert, otp),
- certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert]);
+ certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert], []);
certificate_chain(OwnCert, CertDbHandle, CertsDbRef) ->
DerCert = public_key:pkix_encode('OTPCertificate', OwnCert, otp),
- certificate_chain(OwnCert, DerCert, CertDbHandle, CertsDbRef, [DerCert]).
+ certificate_chain(OwnCert, DerCert, CertDbHandle, CertsDbRef, [DerCert], []).
+
+%%--------------------------------------------------------------------
+-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref(), [der_cert()]) ->
+ {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}.
+%%
+%% Description: Create certificate chain with certs from
+%%--------------------------------------------------------------------
+certificate_chain(Cert, CertDbHandle, CertsDbRef, Candidates) when is_binary(Cert) ->
+ ErlCert = public_key:pkix_decode_cert(Cert, otp),
+ certificate_chain(ErlCert, Cert, CertDbHandle, CertsDbRef, [Cert], Candidates);
+certificate_chain(Cert, CertDbHandle, CertsDbRef, Candidates) ->
+ DerCert = public_key:pkix_encode('OTPCertificate', Cert, otp),
+ certificate_chain(Cert, DerCert, CertDbHandle, CertsDbRef, [DerCert], Candidates).
%%--------------------------------------------------------------------
-spec file_to_certificats(binary(), term()) -> [der_cert()].
%%
@@ -125,7 +141,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, _,_, _, _, _}) ->
case is_valid_extkey_usage(KeyUse, Role) of
true ->
{valid, UserState};
@@ -138,8 +154,10 @@ validate(_, {bad_cert, _} = Reason, _) ->
{fail, Reason};
validate(_, valid, UserState) ->
{valid, UserState};
-validate(_, valid_peer, 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}.
%%--------------------------------------------------------------------
-spec is_valid_key_usage(list(), term()) -> boolean().
@@ -185,9 +203,20 @@ public_key_type(?'id-ecPublicKey') ->
ec.
%%--------------------------------------------------------------------
+-spec foldl_db(fun(), db_handle() | {extracted, list()}, list()) ->
+ {ok, term()} | issuer_not_found.
+%%
+%% Description:
+%%--------------------------------------------------------------------
+foldl_db(IsIssuerFun, CertDbHandle, []) ->
+ ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle);
+foldl_db(IsIssuerFun, _, [_|_] = ListDb) ->
+ lists:foldl(IsIssuerFun, issuer_not_found, ListDb).
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) ->
+certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain, ListDb) ->
IssuerAndSelfSigned =
case public_key:pkix_is_self_signed(OtpCert) of
true ->
@@ -198,12 +227,12 @@ certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) ->
case IssuerAndSelfSigned of
{_, true = SelfSigned} ->
- certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned);
+ do_certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned, ListDb);
{{error, issuer_not_found}, SelfSigned} ->
- case find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) of
+ case find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef, ListDb) of
{ok, {SerialNr, Issuer}} ->
- certificate_chain(CertDbHandle, CertsDbRef, Chain,
- SerialNr, Issuer, SelfSigned);
+ do_certificate_chain(CertDbHandle, CertsDbRef, Chain,
+ SerialNr, Issuer, SelfSigned, ListDb);
_ ->
%% Guess the the issuer must be the root
%% certificate. The verification of the
@@ -212,19 +241,19 @@ certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) ->
{ok, undefined, lists:reverse(Chain)}
end;
{{ok, {SerialNr, Issuer}}, SelfSigned} ->
- certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned)
+ do_certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned, ListDb)
end.
-certificate_chain(_, _, [RootCert | _] = Chain, _, _, true) ->
+do_certificate_chain(_, _, [RootCert | _] = Chain, _, _, true, _) ->
{ok, RootCert, lists:reverse(Chain)};
-certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) ->
+do_certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _, ListDb) ->
case ssl_manager:lookup_trusted_cert(CertDbHandle, CertsDbRef,
SerialNr, Issuer) of
{ok, {IssuerCert, ErlCert}} ->
ErlCert = public_key:pkix_decode_cert(IssuerCert, otp),
certificate_chain(ErlCert, IssuerCert,
- CertDbHandle, CertsDbRef, [IssuerCert | Chain]);
+ CertDbHandle, CertsDbRef, [IssuerCert | Chain], ListDb);
_ ->
%% The trusted cert may be obmitted from the chain as the
%% counter part needs to have it anyway to be able to
@@ -232,7 +261,8 @@ certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned
{ok, undefined, lists:reverse(Chain)}
end.
-find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) ->
+
+find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef, ListDb) ->
IsIssuerFun =
fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) ->
case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of
@@ -250,26 +280,29 @@ find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) ->
Acc
end,
- if is_reference(CertsDbRef) -> % actual DB exists
- try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of
- issuer_not_found ->
- {error, issuer_not_found}
- catch
- {ok, _IssuerId} = Return ->
- Return
- end;
- is_tuple(CertsDbRef), element(1,CertsDbRef) =:= extracted -> % cache bypass byproduct
- {extracted, CertsData} = CertsDbRef,
- DB = [Entry || {decoded, Entry} <- CertsData],
- try lists:foldl(IsIssuerFun, issuer_not_found, DB) of
- issuer_not_found ->
- {error, issuer_not_found}
- catch
- {ok, _IssuerId} = Return ->
- Return
- end
+ Result = case is_reference(CertsDbRef) of
+ true ->
+ do_find_issuer(IsIssuerFun, CertDbHandle, ListDb);
+ false ->
+ {extracted, CertsData} = CertsDbRef,
+ DB = [Entry || {decoded, Entry} <- CertsData],
+ do_find_issuer(IsIssuerFun, CertDbHandle, DB)
+ end,
+ case Result of
+ issuer_not_found ->
+ {error, issuer_not_found};
+ Result ->
+ Result
end.
+do_find_issuer(IssuerFun, CertDbHandle, CertDb) ->
+ try
+ foldl_db(IssuerFun, CertDbHandle, CertDb)
+ catch
+ throw:{ok, _} = Return ->
+ Return
+ end.
+
is_valid_extkey_usage(KeyUse, client) ->
%% Client wants to verify server
is_valid_key_usage(KeyUse,?'id-kp-serverAuth');
@@ -298,7 +331,7 @@ other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) ->
{ok, IssuerId} ->
{other, IssuerId};
{error, issuer_not_found} ->
- case find_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) of
+ case find_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef, []) of
{ok, IssuerId} ->
{other, IssuerId};
Other ->
@@ -330,3 +363,32 @@ 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 ->
+ {valid, UserState};
+ false ->
+ case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}], Customize) of
+ true ->
+ {valid, UserState};
+ false ->
+ {fail, {bad_cert, hostname_check_failed}}
+ end
+ end;
+
+verify_hostname({fallback, Hostname}, Customize, Cert, UserState) ->
+ case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}], Customize) of
+ true ->
+ {valid, UserState};
+ false ->
+ {fail, {bad_cert, hostname_check_failed}}
+ end;
+
+verify_hostname(Hostname, Customize, Cert, UserState) ->
+ case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of
+ true ->
+ {valid, UserState};
+ false ->
+ {fail, {bad_cert, hostname_check_failed}}
+ end.
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 32f05628bb..c16e2331ff 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. 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.
@@ -31,42 +31,33 @@
-include("ssl_cipher.hrl").
-include("ssl_handshake.hrl").
-include("ssl_alert.hrl").
+-include("tls_handshake_1_3.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([security_parameters/2, security_parameters/3, suite_definition/1,
- erl_suite_definition/1,
- cipher_init/3, decipher/6, cipher/5, decipher_aead/6, cipher_aead/6,
- suite/1, suites/1, all_suites/1,
- ec_keyed_suites/0, anonymous_suites/1, psk_suites/1, srp_suites/0,
- rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/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,
+ 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_aad/3, calc_mac_hash/4,
- is_stream_ciphersuite/1]).
-
--export_type([cipher_suite/0,
- erl_cipher_suite/0, openssl_cipher_suite/0,
- hash/0, key_algo/0, sign_algo/0]).
-
--type cipher() :: null |rc4_128 | des_cbc | '3des_ede_cbc'
- | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305.
--type hash() :: null | md5 | sha | sha224 | sha256 | sha384 | sha512.
--type sign_algo() :: rsa | dsa | ecdsa.
--type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss |
- psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon.
--type erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2
- %% TLS 1.2, internally PRE TLS 1.2 will use default_prf
- | {key_algo(), cipher(), hash(), hash() | default_prf}.
-
-
--type cipher_suite() :: binary().
--type cipher_enum() :: integer().
--type openssl_cipher_suite() :: string().
+ 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]).
+%% RFC 8446 TLS 1.3
+-export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]).
-compile(inline).
+-type cipher_enum() :: integer().
+
+-export_type([cipher_enum/0]).
+
%%--------------------------------------------------------------------
--spec security_parameters(cipher_suite(), #security_parameters{}) ->
+-spec security_parameters(ssl_cipher_format:cipher_suite(), #security_parameters{}) ->
#security_parameters{}.
%% Only security_parameters/2 should call security_parameters/3 with undefined as
%% first argument.
@@ -76,14 +67,16 @@ security_parameters(?TLS_NULL_WITH_NULL_NULL = CipherSuite, SecParams) ->
security_parameters(undefined, CipherSuite, SecParams).
%%--------------------------------------------------------------------
--spec security_parameters(ssl_record:ssl_version() | undefined, cipher_suite(), #security_parameters{}) ->
+-spec security_parameters(ssl_record:ssl_version() | undefined,
+ ssl_cipher_format:cipher_suite(), #security_parameters{}) ->
#security_parameters{}.
%%
%% Description: Returns a security parameters record where the
%% cipher values has been updated according to <CipherSuite>
%%-------------------------------------------------------------------
security_parameters(Version, CipherSuite, SecParams) ->
- { _, Cipher, Hash, PrfHashAlg} = suite_definition(CipherSuite),
+ #{cipher := Cipher, mac := Hash,
+ prf := PrfHashAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
SecParams#security_parameters{
cipher_suite = CipherSuite,
bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
@@ -92,10 +85,19 @@ security_parameters(Version, CipherSuite, SecParams) ->
expanded_key_material_length = expanded_key_material(Cipher),
key_material_length = key_material(Cipher),
iv_size = iv_size(Cipher),
- mac_algorithm = hash_algorithm(Hash),
+ mac_algorithm = mac_algorithm(Hash),
prf_algorithm = prf_algorithm(PrfHashAlg, Version),
hash_size = hash_size(Hash)}.
+security_parameters_1_3(SecParams, CipherSuite) ->
+ #{cipher := Cipher, prf := PrfHashAlg} =
+ ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ SecParams#security_parameters{
+ cipher_suite = CipherSuite,
+ bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
+ prf_algorithm = PrfHashAlg, %% HKDF hash algorithm
+ cipher_type = ?AEAD}.
+
%%--------------------------------------------------------------------
-spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}.
%%
@@ -104,11 +106,21 @@ security_parameters(Version, CipherSuite, SecParams) ->
cipher_init(?RC4, IV, Key) ->
State = crypto:stream_init(rc4, Key),
#cipher_state{iv = IV, key = Key, state = State};
-cipher_init(?AES_GCM, IV, Key) ->
+cipher_init(Type, IV, Key) when Type == ?AES_GCM;
+ Type == ?AES_CCM ->
+ <<Nonce:64>> = random_bytes(8),
+ #cipher_state{iv = IV, key = Key, nonce = Nonce, tag_len = 16};
+cipher_init(?AES_CCM_8, IV, Key) ->
<<Nonce:64>> = random_bytes(8),
- #cipher_state{iv = IV, key = Key, nonce = Nonce};
+ #cipher_state{iv = IV, key = Key, nonce = Nonce, tag_len = 8};
+cipher_init(?CHACHA20_POLY1305, IV, Key) ->
+ #cipher_state{iv = IV, key = Key, tag_len = 16};
cipher_init(_BCA, IV, Key) ->
- #cipher_state{iv = IV, key = Key}.
+ %% Initialize random IV cache, not used for aead ciphers
+ #cipher_state{iv = IV, key = Key, state = <<>>}.
+
+nonce_seed(Seed, CipherState) ->
+ CipherState#cipher_state{nonce = Seed}.
%%--------------------------------------------------------------------
-spec cipher(cipher_enum(), #cipher_state{}, binary(), iodata(), ssl_record:ssl_version()) ->
@@ -120,12 +132,11 @@ cipher_init(_BCA, IV, Key) ->
%% data is calculated and the data plus the HMAC is ecncrypted.
%%-------------------------------------------------------------------
cipher(?NULL, CipherState, <<>>, Fragment, _Version) ->
- GenStreamCipherList = [Fragment, <<>>],
- {GenStreamCipherList, CipherState};
+ {iolist_to_binary(Fragment), CipherState};
cipher(?RC4, CipherState = #cipher_state{state = State0}, Mac, Fragment, _Version) ->
GenStreamCipherList = [Fragment, Mac],
{State1, T} = crypto:stream_encrypt(State0, GenStreamCipherList),
- {T, CipherState#cipher_state{state = State1}};
+ {iolist_to_binary(T), CipherState#cipher_state{state = State1}};
cipher(?DES, CipherState, Mac, Fragment, Version) ->
block_cipher(fun(Key, IV, T) ->
crypto:block_encrypt(des_cbc, Key, IV, T)
@@ -141,37 +152,24 @@ cipher(?AES_CBC, CipherState, Mac, Fragment, Version) ->
crypto:block_encrypt(aes_cbc256, Key, IV, T)
end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version).
-%%--------------------------------------------------------------------
--spec cipher_aead(cipher_enum(), #cipher_state{}, integer(), binary(), iodata(), ssl_record:ssl_version()) ->
- {binary(), #cipher_state{}}.
-%%
-%% Description: Encrypts the data and protects associated data (AAD) using chipher
-%% described by cipher_enum() and updating the cipher state
-%% Use for suites that use authenticated encryption with associated data (AEAD)
-%%-------------------------------------------------------------------
-cipher_aead(?AES_GCM, CipherState, SeqNo, AAD, Fragment, Version) ->
- aead_cipher(aes_gcm, CipherState, SeqNo, AAD, Fragment, Version);
-cipher_aead(?CHACHA20_POLY1305, CipherState, SeqNo, AAD, Fragment, Version) ->
- aead_cipher(chacha20_poly1305, CipherState, SeqNo, AAD, Fragment, Version).
-
-aead_cipher(chacha20_poly1305, #cipher_state{key=Key} = CipherState, SeqNo, AAD0, Fragment, _Version) ->
- CipherLen = erlang:iolist_size(Fragment),
- AAD = <<AAD0/binary, ?UINT16(CipherLen)>>,
- Nonce = <<SeqNo:64/integer>>,
- {Content, CipherTag} = crypto:block_encrypt(chacha20_poly1305, Key, Nonce, {AAD, Fragment}),
- {<<Content/binary, CipherTag/binary>>, CipherState};
-aead_cipher(Type, #cipher_state{key=Key, iv = IV0, nonce = Nonce} = CipherState, _SeqNo, AAD0, Fragment, _Version) ->
- CipherLen = erlang:iolist_size(Fragment),
- AAD = <<AAD0/binary, ?UINT16(CipherLen)>>,
- <<Salt:4/bytes, _/binary>> = IV0,
- IV = <<Salt/binary, Nonce:64/integer>>,
- {Content, CipherTag} = crypto:block_encrypt(Type, Key, IV, {AAD, Fragment}),
- {<<Nonce:64/integer, Content/binary, CipherTag/binary>>, CipherState#cipher_state{nonce = Nonce + 1}}.
+aead_encrypt(Type, Key, Nonce, Fragment, AdditionalData, TagLen) ->
+ crypto:block_encrypt(aead_type(Type), Key, Nonce, {AdditionalData, Fragment, TagLen}).
+
+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) ->
+ chacha20_poly1305.
build_cipher_block(BlockSz, Mac, Fragment) ->
TotSz = byte_size(Mac) + erlang:iolist_size(Fragment) + 1,
- {PaddingLength, Padding} = get_padding(TotSz, BlockSz),
- [Fragment, Mac, PaddingLength, Padding].
+ [Fragment, Mac, padding_with_len(TotSz, BlockSz)].
block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
Mac, Fragment, {3, N})
@@ -181,14 +179,21 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
NextIV = next_iv(T, IV),
{T, CS0#cipher_state{iv=NextIV}};
-block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
+block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV, state = IV_Cache0} = CS0,
Mac, Fragment, {3, N})
- when N == 2; N == 3 ->
- NextIV = random_iv(IV),
+ when N == 2; N == 3; N == 4 ->
+ IV_Size = byte_size(IV),
+ <<NextIV:IV_Size/binary, IV_Cache/binary>> =
+ case IV_Cache0 of
+ <<>> ->
+ random_bytes(IV_Size bsl 5); % 32 IVs
+ _ ->
+ IV_Cache0
+ end,
L0 = build_cipher_block(BlockSz, Mac, Fragment),
L = [NextIV|L0],
T = Fun(Key, IV, L),
- {T, CS0#cipher_state{iv=NextIV}}.
+ {T, CS0#cipher_state{iv=NextIV, state = IV_Cache}}.
%%--------------------------------------------------------------------
-spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(),
@@ -233,19 +238,6 @@ decipher(?AES_CBC, HashSz, CipherState, Fragment, Version, PaddingCheck) ->
crypto:block_decrypt(aes_cbc256, Key, IV, T)
end, CipherState, HashSz, Fragment, Version, PaddingCheck).
-%%--------------------------------------------------------------------
--spec decipher_aead(cipher_enum(), #cipher_state{}, integer(), binary(), binary(), ssl_record:ssl_version()) ->
- {binary(), binary(), #cipher_state{}} | #alert{}.
-%%
-%% Description: Decrypts the data and checks the associated data (AAD) MAC using
-%% cipher described by cipher_enum() and updating the cipher state.
-%% Use for suites that use authenticated encryption with associated data (AEAD)
-%%-------------------------------------------------------------------
-decipher_aead(?AES_GCM, CipherState, SeqNo, AAD, Fragment, Version) ->
- aead_decipher(aes_gcm, CipherState, SeqNo, AAD, Fragment, Version);
-decipher_aead(?CHACHA20_POLY1305, CipherState, SeqNo, AAD, Fragment, Version) ->
- aead_decipher(chacha20_poly1305, CipherState, SeqNo, AAD, Fragment, Version).
-
block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
HashSz, Fragment, Version, PaddingCheck) ->
try
@@ -276,36 +268,8 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
-aead_ciphertext_to_state(chacha20_poly1305, SeqNo, _IV, AAD0, Fragment, _Version) ->
- CipherLen = size(Fragment) - 16,
- <<CipherText:CipherLen/bytes, CipherTag:16/bytes>> = Fragment,
- AAD = <<AAD0/binary, ?UINT16(CipherLen)>>,
- Nonce = <<SeqNo:64/integer>>,
- {Nonce, AAD, CipherText, CipherTag};
-aead_ciphertext_to_state(_, _SeqNo, <<Salt:4/bytes, _/binary>>, AAD0, Fragment, _Version) ->
- CipherLen = size(Fragment) - 24,
- <<ExplicitNonce:8/bytes, CipherText:CipherLen/bytes, CipherTag:16/bytes>> = Fragment,
- AAD = <<AAD0/binary, ?UINT16(CipherLen)>>,
- Nonce = <<Salt/binary, ExplicitNonce/binary>>,
- {Nonce, AAD, CipherText, CipherTag}.
-
-aead_decipher(Type, #cipher_state{key = Key, iv = IV} = CipherState,
- SeqNo, AAD0, Fragment, Version) ->
- try
- {Nonce, AAD, CipherText, CipherTag} = aead_ciphertext_to_state(Type, SeqNo, IV, AAD0, Fragment, Version),
- case crypto:block_decrypt(Type, Key, Nonce, {AAD, CipherText, CipherTag}) of
- Content when is_binary(Content) ->
- {Content, CipherState};
- _ ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
- end
- catch
- _:_ ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
- end.
-
%%--------------------------------------------------------------------
--spec suites(ssl_record:ssl_version()) -> [cipher_suite()].
+-spec suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of supported cipher suites.
%%--------------------------------------------------------------------
@@ -318,26 +282,45 @@ suites({_, Minor}) ->
all_suites({3, _} = Version) ->
suites(Version)
- ++ anonymous_suites(Version)
+ ++ chacha_suites(Version)
++ psk_suites(Version)
- ++ srp_suites()
+ ++ srp_suites(Version)
++ rc4_suites(Version)
- ++ des_suites(Version);
+ ++ des_suites(Version)
+ ++ rsa_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()) -> [cipher_suite()].
+-spec anonymous_suites(ssl_record:ssl_version() | integer()) ->
+ [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the anonymous cipher suites, only supported
%% if explicitly set by user. Intended only for testing.
%%--------------------------------------------------------------------
-
-anonymous_suites({3, N}) ->
- anonymous_suites(N);
-
-anonymous_suites(N)
- when N >= 3 ->
+anonymous_suites({3, N} = Version) ->
+ srp_suites_anon(Version) ++ anonymous_suites(N);
+anonymous_suites({254, _} = Version) ->
+ dtls_v1:anonymous_suites(Version);
+anonymous_suites(4) ->
+ []; %% Raw public key negotiation may be used instead
+anonymous_suites( 3 = N) ->
+ psk_suites_anon(N) ++
[?TLS_DH_anon_WITH_AES_128_GCM_SHA256,
?TLS_DH_anon_WITH_AES_256_GCM_SHA384,
?TLS_DH_anon_WITH_AES_128_CBC_SHA256,
@@ -346,79 +329,125 @@ anonymous_suites(N)
?TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
?TLS_DH_anon_WITH_RC4_128_MD5];
-
-anonymous_suites(2) ->
+anonymous_suites(2 = N) ->
+ psk_suites_anon(N) ++
[?TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
?TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
?TLS_DH_anon_WITH_DES_CBC_SHA,
?TLS_DH_anon_WITH_RC4_128_MD5];
-
anonymous_suites(N) when N == 0;
N == 1 ->
- [?TLS_DH_anon_WITH_RC4_128_MD5,
- ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DH_anon_WITH_DES_CBC_SHA
- ].
+ psk_suites_anon(N) ++
+ [?TLS_DH_anon_WITH_RC4_128_MD5,
+ ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_DH_anon_WITH_DES_CBC_SHA
+ ].
%%--------------------------------------------------------------------
--spec psk_suites(ssl_record:ssl_version() | integer()) -> [cipher_suite()].
+-spec psk_suites(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the PSK cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
psk_suites({3, N}) ->
psk_suites(N);
+psk_suites(4) ->
+ []; %% TODO Add new PSK, PSK_(EC)DHE suites
+psk_suites(3) ->
+ [
+ ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
+ ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
+ ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
+ ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256
+ ] ++ psk_suites(0);
+psk_suites(_) ->
+ [?TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
+ ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
+ ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_RSA_PSK_WITH_RC4_128_SHA].
-psk_suites(N)
- when N >= 3 ->
+%%--------------------------------------------------------------------
+-spec psk_suites_anon(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
+%%
+%% Description: Returns a list of the anonymous PSK cipher suites, only supported
+%% if explicitly set by user.
+%%--------------------------------------------------------------------
+psk_suites_anon({3, N}) ->
+ psk_suites_anon(N);
+psk_suites_anon(3 = N) ->
[
?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384,
- ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
?TLS_PSK_WITH_AES_256_GCM_SHA384,
+ ?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
?TLS_PSK_WITH_AES_256_CBC_SHA384,
+ ?TLS_DHE_PSK_WITH_AES_256_CCM,
+ ?TLS_PSK_DHE_WITH_AES_256_CCM_8,
+ ?TLS_PSK_WITH_AES_256_CCM,
+ ?TLS_PSK_WITH_AES_256_CCM_8,
+ ?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256,
+ ?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256,
+ ?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256,
?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256,
- ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
?TLS_PSK_WITH_AES_128_GCM_SHA256,
+ ?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256,
+ ?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256,
+ ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
- ?TLS_PSK_WITH_AES_128_CBC_SHA256
- ] ++ psk_suites(0);
-
-psk_suites(_) ->
+ ?TLS_PSK_WITH_AES_128_CBC_SHA256,
+ ?TLS_DHE_PSK_WITH_AES_128_CCM,
+ ?TLS_PSK_DHE_WITH_AES_128_CCM_8,
+ ?TLS_PSK_WITH_AES_128_CCM,
+ ?TLS_PSK_WITH_AES_128_CCM_8,
+ ?TLS_ECDHE_PSK_WITH_RC4_128_SHA
+ ] ++ psk_suites_anon(N-1);
+psk_suites_anon(N) when N > 0 ->
[?TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
?TLS_PSK_WITH_AES_256_CBC_SHA,
+ ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
?TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
?TLS_PSK_WITH_AES_128_CBC_SHA,
+ ?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA,
?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
?TLS_PSK_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_ECDHE_PSK_WITH_RC4_128_SHA,
?TLS_DHE_PSK_WITH_RC4_128_SHA,
- ?TLS_RSA_PSK_WITH_RC4_128_SHA,
- ?TLS_PSK_WITH_RC4_128_SHA].
-
+ ?TLS_PSK_WITH_RC4_128_SHA];
+psk_suites_anon(0) ->
+ [].
%%--------------------------------------------------------------------
--spec srp_suites() -> [cipher_suite()].
+-spec srp_suites(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the SRP cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites() ->
- [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
+srp_suites({3,0}) ->
+ [];
+srp_suites(_) ->
+ [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA].
+
+%%--------------------------------------------------------------------
+-spec srp_suites_anon(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()].
+%%
+%% 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,
+ ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA].
+
%%--------------------------------------------------------------------
--spec rc4_suites(Version::ssl_record:ssl_version()) -> [cipher_suite()].
+-spec rc4_suites(Version::ssl_record:ssl_version() | integer()) ->
+ [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the RSA|(ECDH/RSA)| (ECDH/ECDSA)
%% with RC4 cipher suites, only supported if explicitly set by user.
@@ -426,17 +455,20 @@ srp_suites() ->
%% belonged to the user configured only category.
%%--------------------------------------------------------------------
rc4_suites({3, 0}) ->
+ rc4_suites(0);
+rc4_suites({3, Minor}) ->
+ rc4_suites(Minor) ++ rc4_suites(0);
+rc4_suites(0) ->
[?TLS_RSA_WITH_RC4_128_SHA,
?TLS_RSA_WITH_RC4_128_MD5];
-rc4_suites({3, N}) when N =< 3 ->
+rc4_suites(N) when N =< 4 ->
[?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
?TLS_ECDHE_RSA_WITH_RC4_128_SHA,
- ?TLS_RSA_WITH_RC4_128_SHA,
- ?TLS_RSA_WITH_RC4_128_MD5,
?TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
?TLS_ECDH_RSA_WITH_RC4_128_SHA].
+
%%--------------------------------------------------------------------
--spec des_suites(Version::ssl_record:ssl_version()) -> [cipher_suite()].
+-spec des_suites(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the cipher suites
%% with DES cipher, only supported if explicitly set by user.
@@ -444,1017 +476,132 @@ rc4_suites({3, N}) when N =< 3 ->
%%--------------------------------------------------------------------
des_suites(_)->
[?TLS_DHE_RSA_WITH_DES_CBC_SHA,
- ?TLS_RSA_WITH_DES_CBC_SHA].
-
-%%--------------------------------------------------------------------
--spec suite_definition(cipher_suite()) -> erl_cipher_suite().
-%%
-%% Description: Return erlang cipher suite definition.
-%% Note: Currently not supported suites are commented away.
-%% They should be supported or removed in the future.
-%%-------------------------------------------------------------------
-%% TLS v1.1 suites
-suite_definition(?TLS_NULL_WITH_NULL_NULL) ->
- {null, null, null, null};
-%% RFC 5746 - Not a real cipher suite used to signal empty "renegotiation_info" extension
-%% to avoid handshake failure from old servers that do not ignore
-%% hello extension data as they should.
-suite_definition(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) ->
- {null, null, null, null};
-%% suite_definition(?TLS_RSA_WITH_NULL_MD5) ->
-%% {rsa, null, md5, default_prf};
-%% suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
-%% {rsa, null, sha, default_prf};
-suite_definition(?TLS_RSA_WITH_RC4_128_MD5) ->
- {rsa, rc4_128, md5, default_prf};
-suite_definition(?TLS_RSA_WITH_RC4_128_SHA) ->
- {rsa, rc4_128, sha, default_prf};
-suite_definition(?TLS_RSA_WITH_DES_CBC_SHA) ->
- {rsa, des_cbc, sha, default_prf};
-suite_definition(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {rsa, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_DHE_DSS_WITH_DES_CBC_SHA) ->
- {dhe_dss, des_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_dss, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
- {dhe_rsa, des_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_rsa, '3des_ede_cbc', sha, default_prf};
-
-%%% TSL V1.1 AES suites
-suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA) ->
- {rsa, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) ->
- {dhe_dss, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ->
- {dhe_rsa, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA) ->
- {rsa, aes_256_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
- {dhe_dss, aes_256_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
- {dhe_rsa, aes_256_cbc, sha, default_prf};
-
-%% TLS v1.2 suites
-
-%% suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
-%% {rsa, null, sha, default_prf};
-suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA256) ->
- {rsa, aes_128_cbc, sha256, default_prf};
-suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA256) ->
- {rsa, aes_256_cbc, sha256, default_prf};
-suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) ->
- {dhe_dss, aes_128_cbc, sha256, default_prf};
-suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) ->
- {dhe_rsa, aes_128_cbc, sha256, default_prf};
-suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) ->
- {dhe_dss, aes_256_cbc, sha256, default_prf};
-suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) ->
- {dhe_rsa, aes_256_cbc, sha256, default_prf};
-
-%% not defined YET:
-%% TLS_DH_DSS_WITH_AES_128_CBC_SHA256 DH_DSS AES_128_CBC SHA256
-%% TLS_DH_RSA_WITH_AES_128_CBC_SHA256 DH_RSA AES_128_CBC SHA256
-%% TLS_DH_DSS_WITH_AES_256_CBC_SHA256 DH_DSS AES_256_CBC SHA256
-%% TLS_DH_RSA_WITH_AES_256_CBC_SHA256 DH_RSA AES_256_CBC SHA256
-
-%%% DH-ANON deprecated by TLS spec and not available
-%%% by default, but good for testing purposes.
-suite_definition(?TLS_DH_anon_WITH_RC4_128_MD5) ->
- {dh_anon, rc4_128, md5, default_prf};
-suite_definition(?TLS_DH_anon_WITH_DES_CBC_SHA) ->
- {dh_anon, des_cbc, sha, default_prf};
-suite_definition(?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA) ->
- {dh_anon, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA) ->
- {dh_anon, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA) ->
- {dh_anon, aes_256_cbc, sha, default_prf};
-suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) ->
- {dh_anon, aes_128_cbc, sha256, default_prf};
-suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) ->
- {dh_anon, aes_256_cbc, sha256, default_prf};
-
-%%% PSK Cipher Suites RFC 4279
-
-suite_definition(?TLS_PSK_WITH_RC4_128_SHA) ->
- {psk, rc4_128, sha, default_prf};
-suite_definition(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) ->
- {psk, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA) ->
- {psk, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA) ->
- {psk, aes_256_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_RC4_128_SHA) ->
- {dhe_psk, rc4_128, sha, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_psk, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA) ->
- {dhe_psk, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA) ->
- {dhe_psk, aes_256_cbc, sha, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_RC4_128_SHA) ->
- {rsa_psk, rc4_128, sha, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA) ->
- {rsa_psk, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA) ->
- {rsa_psk, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA) ->
- {rsa_psk, aes_256_cbc, sha, default_prf};
-
-%%% TLS 1.2 PSK Cipher Suites RFC 5487
-
-suite_definition(?TLS_PSK_WITH_AES_128_GCM_SHA256) ->
- {psk, aes_128_gcm, null, sha256};
-suite_definition(?TLS_PSK_WITH_AES_256_GCM_SHA384) ->
- {psk, aes_256_gcm, null, sha384};
-suite_definition(?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256) ->
- {dhe_psk, aes_128_gcm, null, sha256};
-suite_definition(?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384) ->
- {dhe_psk, aes_256_gcm, null, sha384};
-suite_definition(?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256) ->
- {rsa_psk, aes_128_gcm, null, sha256};
-suite_definition(?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384) ->
- {rsa_psk, aes_256_gcm, null, sha384};
-
-suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA256) ->
- {psk, aes_128_cbc, sha256, default_prf};
-suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA384) ->
- {psk, aes_256_cbc, sha384, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) ->
- {dhe_psk, aes_128_cbc, sha256, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) ->
- {dhe_psk, aes_256_cbc, sha384, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256) ->
- {rsa_psk, aes_128_cbc, sha256, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384) ->
- {rsa_psk, aes_256_cbc, sha384, default_prf};
-
-suite_definition(?TLS_PSK_WITH_NULL_SHA256) ->
- {psk, null, sha256, default_prf};
-suite_definition(?TLS_PSK_WITH_NULL_SHA384) ->
- {psk, null, sha384, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA256) ->
- {dhe_psk, null, sha256, default_prf};
-suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA384) ->
- {dhe_psk, null, sha384, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA256) ->
- {rsa_psk, null, sha256, default_prf};
-suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA384) ->
- {rsa_psk, null, sha384, default_prf};
-
-%%% SRP Cipher Suites RFC 5054
-
-suite_definition(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) ->
- {srp_anon, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {srp_rsa, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) ->
- {srp_dss, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_SRP_SHA_WITH_AES_128_CBC_SHA) ->
- {srp_anon, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) ->
- {srp_rsa, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) ->
- {srp_dss, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_SRP_SHA_WITH_AES_256_CBC_SHA) ->
- {srp_anon, aes_256_cbc, sha, default_prf};
-suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) ->
- {srp_rsa, aes_256_cbc, sha, default_prf};
-suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) ->
- {srp_dss, aes_256_cbc, sha, default_prf};
-
-%% RFC 4492 EC TLS suites
-suite_definition(?TLS_ECDH_ECDSA_WITH_NULL_SHA) ->
- {ecdh_ecdsa, null, sha, default_prf};
-suite_definition(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) ->
- {ecdh_ecdsa, rc4_128, sha, default_prf};
-suite_definition(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdh_ecdsa, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) ->
- {ecdh_ecdsa, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) ->
- {ecdh_ecdsa, aes_256_cbc, sha, default_prf};
-
-suite_definition(?TLS_ECDHE_ECDSA_WITH_NULL_SHA) ->
- {ecdhe_ecdsa, null, sha, default_prf};
-suite_definition(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) ->
- {ecdhe_ecdsa, rc4_128, sha, default_prf};
-suite_definition(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdhe_ecdsa, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) ->
- {ecdhe_ecdsa, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) ->
- {ecdhe_ecdsa, aes_256_cbc, sha, default_prf};
-
-suite_definition(?TLS_ECDH_RSA_WITH_NULL_SHA) ->
- {ecdh_rsa, null, sha, default_prf};
-suite_definition(?TLS_ECDH_RSA_WITH_RC4_128_SHA) ->
- {ecdh_rsa, rc4_128, sha, default_prf};
-suite_definition(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdh_rsa, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) ->
- {ecdh_rsa, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) ->
- {ecdh_rsa, aes_256_cbc, sha, default_prf};
-
-suite_definition(?TLS_ECDHE_RSA_WITH_NULL_SHA) ->
- {ecdhe_rsa, null, sha, default_prf};
-suite_definition(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) ->
- {ecdhe_rsa, rc4_128, sha, default_prf};
-suite_definition(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdhe_rsa, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) ->
- {ecdhe_rsa, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) ->
- {ecdhe_rsa, aes_256_cbc, sha, default_prf};
-
-suite_definition(?TLS_ECDH_anon_WITH_NULL_SHA) ->
- {ecdh_anon, null, sha, default_prf};
-suite_definition(?TLS_ECDH_anon_WITH_RC4_128_SHA) ->
- {ecdh_anon, rc4_128, sha, default_prf};
-suite_definition(?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA) ->
- {ecdh_anon, '3des_ede_cbc', sha, default_prf};
-suite_definition(?TLS_ECDH_anon_WITH_AES_128_CBC_SHA) ->
- {ecdh_anon, aes_128_cbc, sha, default_prf};
-suite_definition(?TLS_ECDH_anon_WITH_AES_256_CBC_SHA) ->
- {ecdh_anon, aes_256_cbc, sha, default_prf};
-
-%% RFC 5289 EC TLS suites
-suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) ->
- {ecdhe_ecdsa, aes_128_cbc, sha256, sha256};
-suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) ->
- {ecdhe_ecdsa, aes_256_cbc, sha384, sha384};
-suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) ->
- {ecdh_ecdsa, aes_128_cbc, sha256, sha256};
-suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) ->
- {ecdh_ecdsa, aes_256_cbc, sha384, sha384};
-suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) ->
- {ecdhe_rsa, aes_128_cbc, sha256, sha256};
-suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) ->
- {ecdhe_rsa, aes_256_cbc, sha384, sha384};
-suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) ->
- {ecdh_rsa, aes_128_cbc, sha256, sha256};
-suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) ->
- {ecdh_rsa, aes_256_cbc, sha384, sha384};
-
-%% RFC 5288 AES-GCM Cipher Suites
-suite_definition(?TLS_RSA_WITH_AES_128_GCM_SHA256) ->
- {rsa, aes_128_gcm, null, sha256};
-suite_definition(?TLS_RSA_WITH_AES_256_GCM_SHA384) ->
- {rsa, aes_256_gcm, null, sha384};
-suite_definition(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) ->
- {dhe_rsa, aes_128_gcm, null, sha256};
-suite_definition(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) ->
- {dhe_rsa, aes_256_gcm, null, sha384};
-suite_definition(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) ->
- {dh_rsa, aes_128_gcm, null, sha256};
-suite_definition(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) ->
- {dh_rsa, aes_256_gcm, null, sha384};
-suite_definition(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) ->
- {dhe_dss, aes_128_gcm, null, sha256};
-suite_definition(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) ->
- {dhe_dss, aes_256_gcm, null, sha384};
-suite_definition(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) ->
- {dh_dss, aes_128_gcm, null, sha256};
-suite_definition(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) ->
- {dh_dss, aes_256_gcm, null, sha384};
-suite_definition(?TLS_DH_anon_WITH_AES_128_GCM_SHA256) ->
- {dh_anon, aes_128_gcm, null, sha256};
-suite_definition(?TLS_DH_anon_WITH_AES_256_GCM_SHA384) ->
- {dh_anon, aes_256_gcm, null, sha384};
-
-%% RFC 5289 ECC AES-GCM Cipher Suites
-suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) ->
- {ecdhe_ecdsa, aes_128_gcm, null, sha256};
-suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) ->
- {ecdhe_ecdsa, aes_256_gcm, null, sha384};
-suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) ->
- {ecdh_ecdsa, aes_128_gcm, null, sha256};
-suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) ->
- {ecdh_ecdsa, aes_256_gcm, null, sha384};
-suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) ->
- {ecdhe_rsa, aes_128_gcm, null, sha256};
-suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) ->
- {ecdhe_rsa, aes_256_gcm, null, sha384};
-suite_definition(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) ->
- {ecdh_rsa, aes_128_gcm, null, sha256};
-suite_definition(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) ->
- {ecdh_rsa, aes_256_gcm, null, sha384};
-
-%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites
-suite_definition(?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
- {ecdhe_rsa, chacha20_poly1305, null, sha256};
-suite_definition(?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) ->
- {ecdhe_ecdsa, chacha20_poly1305, null, sha256};
-suite_definition(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
- {dhe_rsa, chacha20_poly1305, null, sha256}.
-
-%%--------------------------------------------------------------------
--spec erl_suite_definition(cipher_suite()) -> erl_cipher_suite().
-%%
-%% Description: Return erlang cipher suite definition. Filters last value
-%% for now (compatibility reasons).
-%%--------------------------------------------------------------------
-erl_suite_definition(S) ->
- case suite_definition(S) of
- {KeyExchange, Cipher, Hash, default_prf} ->
- {KeyExchange, Cipher, Hash};
- Suite ->
- Suite
- end.
-
-%%--------------------------------------------------------------------
--spec suite(erl_cipher_suite()) -> cipher_suite().
-%%
-%% Description: Return TLS cipher suite definition.
-%%--------------------------------------------------------------------
-
-%% TLS v1.1 suites
-%%suite({rsa, null, md5}) ->
-%% ?TLS_RSA_WITH_NULL_MD5;
-%%suite({rsa, null, sha}) ->
-%% ?TLS_RSA_WITH_NULL_SHA;
-suite({rsa, rc4_128, md5}) ->
- ?TLS_RSA_WITH_RC4_128_MD5;
-suite({rsa, rc4_128, sha}) ->
- ?TLS_RSA_WITH_RC4_128_SHA;
-suite({rsa, des_cbc, sha}) ->
- ?TLS_RSA_WITH_DES_CBC_SHA;
-suite({rsa, '3des_ede_cbc', sha}) ->
- ?TLS_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_dss, des_cbc, sha}) ->
- ?TLS_DHE_DSS_WITH_DES_CBC_SHA;
-suite({dhe_dss, '3des_ede_cbc', sha}) ->
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_rsa, des_cbc, sha}) ->
- ?TLS_DHE_RSA_WITH_DES_CBC_SHA;
-suite({dhe_rsa, '3des_ede_cbc', sha}) ->
- ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({dh_anon, rc4_128, md5}) ->
- ?TLS_DH_anon_WITH_RC4_128_MD5;
-suite({dh_anon, des_cbc, sha}) ->
- ?TLS_DH_anon_WITH_DES_CBC_SHA;
-suite({dh_anon, '3des_ede_cbc', sha}) ->
- ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
-
-%%% TSL V1.1 AES suites
-suite({rsa, aes_128_cbc, sha}) ->
- ?TLS_RSA_WITH_AES_128_CBC_SHA;
-suite({dhe_dss, aes_128_cbc, sha}) ->
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
-suite({dhe_rsa, aes_128_cbc, sha}) ->
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA;
-suite({dh_anon, aes_128_cbc, sha}) ->
- ?TLS_DH_anon_WITH_AES_128_CBC_SHA;
-suite({rsa, aes_256_cbc, sha}) ->
- ?TLS_RSA_WITH_AES_256_CBC_SHA;
-suite({dhe_dss, aes_256_cbc, sha}) ->
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA;
-suite({dhe_rsa, aes_256_cbc, sha}) ->
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
-suite({dh_anon, aes_256_cbc, sha}) ->
- ?TLS_DH_anon_WITH_AES_256_CBC_SHA;
-
-%% TLS v1.2 suites
-
-%% suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
-%% {rsa, null, sha, sha256};
-suite({rsa, aes_128_cbc, sha256}) ->
- ?TLS_RSA_WITH_AES_128_CBC_SHA256;
-suite({rsa, aes_256_cbc, sha256}) ->
- ?TLS_RSA_WITH_AES_256_CBC_SHA256;
-suite({dhe_dss, aes_128_cbc, sha256}) ->
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256;
-suite({dhe_rsa, aes_128_cbc, sha256}) ->
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256;
-suite({dhe_dss, aes_256_cbc, sha256}) ->
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256;
-suite({dhe_rsa, aes_256_cbc, sha256}) ->
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256;
-suite({dh_anon, aes_128_cbc, sha256}) ->
- ?TLS_DH_anon_WITH_AES_128_CBC_SHA256;
-suite({dh_anon, aes_256_cbc, sha256}) ->
- ?TLS_DH_anon_WITH_AES_256_CBC_SHA256;
-
-%%% PSK Cipher Suites RFC 4279
-
-suite({psk, rc4_128,sha}) ->
- ?TLS_PSK_WITH_RC4_128_SHA;
-suite({psk, '3des_ede_cbc',sha}) ->
- ?TLS_PSK_WITH_3DES_EDE_CBC_SHA;
-suite({psk, aes_128_cbc,sha}) ->
- ?TLS_PSK_WITH_AES_128_CBC_SHA;
-suite({psk, aes_256_cbc,sha}) ->
- ?TLS_PSK_WITH_AES_256_CBC_SHA;
-suite({dhe_psk, rc4_128,sha}) ->
- ?TLS_DHE_PSK_WITH_RC4_128_SHA;
-suite({dhe_psk, '3des_ede_cbc',sha}) ->
- ?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_psk, aes_128_cbc,sha}) ->
- ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA;
-suite({dhe_psk, aes_256_cbc,sha}) ->
- ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA;
-suite({rsa_psk, rc4_128,sha}) ->
- ?TLS_RSA_PSK_WITH_RC4_128_SHA;
-suite({rsa_psk, '3des_ede_cbc',sha}) ->
- ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA;
-suite({rsa_psk, aes_128_cbc,sha}) ->
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA;
-suite({rsa_psk, aes_256_cbc,sha}) ->
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA;
-
-%%% TLS 1.2 PSK Cipher Suites RFC 5487
-
-suite({psk, aes_128_gcm, null, sha256}) ->
- ?TLS_PSK_WITH_AES_128_GCM_SHA256;
-suite({psk, aes_256_gcm, null, sha384}) ->
- ?TLS_PSK_WITH_AES_256_GCM_SHA384;
-suite({dhe_psk, aes_128_gcm, null, sha256}) ->
- ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256;
-suite({dhe_psk, aes_256_gcm, null, sha384}) ->
- ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384;
-suite({rsa_psk, aes_128_gcm, null, sha256}) ->
- ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256;
-suite({rsa_psk, aes_256_gcm, null, sha384}) ->
- ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384;
-
-suite({psk, aes_128_cbc, sha256}) ->
- ?TLS_PSK_WITH_AES_128_CBC_SHA256;
-suite({psk, aes_256_cbc, sha384}) ->
- ?TLS_PSK_WITH_AES_256_CBC_SHA384;
-suite({dhe_psk, aes_128_cbc, sha256}) ->
- ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256;
-suite({dhe_psk, aes_256_cbc, sha384}) ->
- ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384;
-suite({rsa_psk, aes_128_cbc, sha256}) ->
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256;
-suite({rsa_psk, aes_256_cbc, sha384}) ->
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384;
-
-suite({psk, null, sha256}) ->
- ?TLS_PSK_WITH_NULL_SHA256;
-suite({psk, null, sha384}) ->
- ?TLS_PSK_WITH_NULL_SHA384;
-suite({dhe_psk, null, sha256}) ->
- ?TLS_DHE_PSK_WITH_NULL_SHA256;
-suite({dhe_psk, null, sha384}) ->
- ?TLS_DHE_PSK_WITH_NULL_SHA384;
-suite({rsa_psk, null, sha256}) ->
- ?TLS_RSA_PSK_WITH_NULL_SHA256;
-suite({rsa_psk, null, sha384}) ->
- ?TLS_RSA_PSK_WITH_NULL_SHA384;
-
-%%% SRP Cipher Suites RFC 5054
-
-suite({srp_anon, '3des_ede_cbc', sha}) ->
- ?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA;
-suite({srp_rsa, '3des_ede_cbc', sha}) ->
- ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({srp_dss, '3des_ede_cbc', sha}) ->
- ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA;
-suite({srp_anon, aes_128_cbc, sha}) ->
- ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA;
-suite({srp_rsa, aes_128_cbc, sha}) ->
- ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA;
-suite({srp_dss, aes_128_cbc, sha}) ->
- ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA;
-suite({srp_anon, aes_256_cbc, sha}) ->
- ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA;
-suite({srp_rsa, aes_256_cbc, sha}) ->
- ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA;
-suite({srp_dss, aes_256_cbc, sha}) ->
- ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA;
-
-%%% RFC 4492 EC TLS suites
-suite({ecdh_ecdsa, null, sha}) ->
- ?TLS_ECDH_ECDSA_WITH_NULL_SHA;
-suite({ecdh_ecdsa, rc4_128, sha}) ->
- ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA;
-suite({ecdh_ecdsa, '3des_ede_cbc', sha}) ->
- ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdh_ecdsa, aes_128_cbc, sha}) ->
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA;
-suite({ecdh_ecdsa, aes_256_cbc, sha}) ->
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdhe_ecdsa, null, sha}) ->
- ?TLS_ECDHE_ECDSA_WITH_NULL_SHA;
-suite({ecdhe_ecdsa, rc4_128, sha}) ->
- ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA;
-suite({ecdhe_ecdsa, '3des_ede_cbc', sha}) ->
- ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdhe_ecdsa, aes_128_cbc, sha}) ->
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA;
-suite({ecdhe_ecdsa, aes_256_cbc, sha}) ->
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdh_rsa, null, sha}) ->
- ?TLS_ECDH_RSA_WITH_NULL_SHA;
-suite({ecdh_rsa, rc4_128, sha}) ->
- ?TLS_ECDH_RSA_WITH_RC4_128_SHA;
-suite({ecdh_rsa, '3des_ede_cbc', sha}) ->
- ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdh_rsa, aes_128_cbc, sha}) ->
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA;
-suite({ecdh_rsa, aes_256_cbc, sha}) ->
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdhe_rsa, null, sha}) ->
- ?TLS_ECDHE_RSA_WITH_NULL_SHA;
-suite({ecdhe_rsa, rc4_128, sha}) ->
- ?TLS_ECDHE_RSA_WITH_RC4_128_SHA;
-suite({ecdhe_rsa, '3des_ede_cbc', sha}) ->
- ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdhe_rsa, aes_128_cbc, sha}) ->
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA;
-suite({ecdhe_rsa, aes_256_cbc, sha}) ->
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdh_anon, null, sha}) ->
- ?TLS_ECDH_anon_WITH_NULL_SHA;
-suite({ecdh_anon, rc4_128, sha}) ->
- ?TLS_ECDH_anon_WITH_RC4_128_SHA;
-suite({ecdh_anon, '3des_ede_cbc', sha}) ->
- ?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA;
-suite({ecdh_anon, aes_128_cbc, sha}) ->
- ?TLS_ECDH_anon_WITH_AES_128_CBC_SHA;
-suite({ecdh_anon, aes_256_cbc, sha}) ->
- ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA;
-
-%%% RFC 5289 EC TLS suites
-suite({ecdhe_ecdsa, aes_128_cbc, sha256, sha256}) ->
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
-suite({ecdhe_ecdsa, aes_256_cbc, sha384, sha384}) ->
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384;
-suite({ecdh_ecdsa, aes_128_cbc, sha256, sha256}) ->
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256;
-suite({ecdh_ecdsa, aes_256_cbc, sha384, sha384}) ->
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384;
-suite({ecdhe_rsa, aes_128_cbc, sha256, sha256}) ->
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256;
-suite({ecdhe_rsa, aes_256_cbc, sha384, sha384}) ->
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384;
-suite({ecdh_rsa, aes_128_cbc, sha256, sha256}) ->
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256;
-suite({ecdh_rsa, aes_256_cbc, sha384, sha384}) ->
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384;
-
-%% RFC 5288 AES-GCM Cipher Suites
-suite({rsa, aes_128_gcm, null, sha256}) ->
- ?TLS_RSA_WITH_AES_128_GCM_SHA256;
-suite({rsa, aes_256_gcm, null, sha384}) ->
- ?TLS_RSA_WITH_AES_256_GCM_SHA384;
-suite({dhe_rsa, aes_128_gcm, null, sha256}) ->
- ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256;
-suite({dhe_rsa, aes_256_gcm, null, sha384}) ->
- ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384;
-suite({dh_rsa, aes_128_gcm, null, sha256}) ->
- ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256;
-suite({dh_rsa, aes_256_gcm, null, sha384}) ->
- ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384;
-suite({dhe_dss, aes_128_gcm, null, sha256}) ->
- ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256;
-suite({dhe_dss, aes_256_gcm, null, sha384}) ->
- ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384;
-suite({dh_dss, aes_128_gcm, null, sha256}) ->
- ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256;
-suite({dh_dss, aes_256_gcm, null, sha384}) ->
- ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384;
-suite({dh_anon, aes_128_gcm, null, sha256}) ->
- ?TLS_DH_anon_WITH_AES_128_GCM_SHA256;
-suite({dh_anon, aes_256_gcm, null, sha384}) ->
- ?TLS_DH_anon_WITH_AES_256_GCM_SHA384;
-
-%% RFC 5289 ECC AES-GCM Cipher Suites
-suite({ecdhe_ecdsa, aes_128_gcm, null, sha256}) ->
- ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
-suite({ecdhe_ecdsa, aes_256_gcm, null, sha384}) ->
- ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
-suite({ecdh_ecdsa, aes_128_gcm, null, sha256}) ->
- ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256;
-suite({ecdh_ecdsa, aes_256_gcm, null, sha384}) ->
- ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384;
-suite({ecdhe_rsa, aes_128_gcm, null, sha256}) ->
- ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256;
-suite({ecdhe_rsa, aes_256_gcm, null, sha384}) ->
- ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384;
-suite({ecdh_rsa, aes_128_gcm, null, sha256}) ->
- ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256;
-suite({ecdh_rsa, aes_256_gcm, null, sha384}) ->
- ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384;
-
-
-%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites
-suite({ecdhe_rsa, chacha20_poly1305, null, sha256}) ->
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
-suite({ecdhe_ecdsa, chacha20_poly1305, null, sha256}) ->
- ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256;
-suite({dhe_rsa, chacha20_poly1305, null, sha256}) ->
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256.
+ ?TLS_RSA_WITH_DES_CBC_SHA,
+ ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
+ ].
%%--------------------------------------------------------------------
--spec openssl_suite(openssl_cipher_suite()) -> cipher_suite().
+-spec rsa_suites(Version::ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
%%
-%% Description: Return TLS cipher suite definition.
-%%--------------------------------------------------------------------
-%% translate constants <-> openssl-strings
-openssl_suite("DHE-RSA-AES256-SHA256") ->
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256;
-openssl_suite("DHE-DSS-AES256-SHA256") ->
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256;
-openssl_suite("AES256-SHA256") ->
- ?TLS_RSA_WITH_AES_256_CBC_SHA256;
-openssl_suite("DHE-RSA-AES128-SHA256") ->
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256;
-openssl_suite("DHE-DSS-AES128-SHA256") ->
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256;
-openssl_suite("AES128-SHA256") ->
- ?TLS_RSA_WITH_AES_128_CBC_SHA256;
-openssl_suite("DHE-RSA-AES256-SHA") ->
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
-openssl_suite("DHE-DSS-AES256-SHA") ->
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA;
-openssl_suite("AES256-SHA") ->
- ?TLS_RSA_WITH_AES_256_CBC_SHA;
-openssl_suite("EDH-RSA-DES-CBC3-SHA") ->
- ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("EDH-DSS-DES-CBC3-SHA") ->
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("DES-CBC3-SHA") ->
- ?TLS_RSA_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("DHE-RSA-AES128-SHA") ->
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA;
-openssl_suite("DHE-DSS-AES128-SHA") ->
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
-openssl_suite("AES128-SHA") ->
- ?TLS_RSA_WITH_AES_128_CBC_SHA;
-openssl_suite("RC4-SHA") ->
- ?TLS_RSA_WITH_RC4_128_SHA;
-openssl_suite("RC4-MD5") ->
- ?TLS_RSA_WITH_RC4_128_MD5;
-openssl_suite("EDH-RSA-DES-CBC-SHA") ->
- ?TLS_DHE_RSA_WITH_DES_CBC_SHA;
-openssl_suite("DES-CBC-SHA") ->
- ?TLS_RSA_WITH_DES_CBC_SHA;
-
-%%% SRP Cipher Suites RFC 5054
-
-openssl_suite("SRP-DSS-AES-256-CBC-SHA") ->
- ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA;
-openssl_suite("SRP-RSA-AES-256-CBC-SHA") ->
- ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA;
-openssl_suite("SRP-DSS-3DES-EDE-CBC-SHA") ->
- ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("SRP-RSA-3DES-EDE-CBC-SHA") ->
- ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("SRP-DSS-AES-128-CBC-SHA") ->
- ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA;
-openssl_suite("SRP-RSA-AES-128-CBC-SHA") ->
- ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA;
-
-%% RFC 4492 EC TLS suites
-openssl_suite("ECDH-ECDSA-RC4-SHA") ->
- ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA;
-openssl_suite("ECDH-ECDSA-DES-CBC3-SHA") ->
- ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("ECDH-ECDSA-AES128-SHA") ->
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA;
-openssl_suite("ECDH-ECDSA-AES256-SHA") ->
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA;
-
-openssl_suite("ECDHE-ECDSA-RC4-SHA") ->
- ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA;
-openssl_suite("ECDHE-ECDSA-DES-CBC3-SHA") ->
- ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("ECDHE-ECDSA-AES128-SHA") ->
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA;
-openssl_suite("ECDHE-ECDSA-AES256-SHA") ->
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
-
-openssl_suite("ECDHE-RSA-RC4-SHA") ->
- ?TLS_ECDHE_RSA_WITH_RC4_128_SHA;
-openssl_suite("ECDHE-RSA-DES-CBC3-SHA") ->
- ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("ECDHE-RSA-AES128-SHA") ->
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA;
-openssl_suite("ECDHE-RSA-AES256-SHA") ->
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA;
-
-openssl_suite("ECDH-RSA-RC4-SHA") ->
- ?TLS_ECDH_RSA_WITH_RC4_128_SHA;
-openssl_suite("ECDH-RSA-DES-CBC3-SHA") ->
- ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA;
-openssl_suite("ECDH-RSA-AES128-SHA") ->
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA;
-openssl_suite("ECDH-RSA-AES256-SHA") ->
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA;
-
-%% RFC 5289 EC TLS suites
-openssl_suite("ECDHE-ECDSA-AES128-SHA256") ->
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
-openssl_suite("ECDHE-ECDSA-AES256-SHA384") ->
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384;
-openssl_suite("ECDH-ECDSA-AES128-SHA256") ->
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256;
-openssl_suite("ECDH-ECDSA-AES256-SHA384") ->
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384;
-openssl_suite("ECDHE-RSA-AES128-SHA256") ->
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256;
-openssl_suite("ECDHE-RSA-AES256-SHA384") ->
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384;
-openssl_suite("ECDH-RSA-AES128-SHA256") ->
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256;
-openssl_suite("ECDH-RSA-AES256-SHA384") ->
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384;
-
-%% RFC 5288 AES-GCM Cipher Suites
-openssl_suite("AES128-GCM-SHA256") ->
- ?TLS_RSA_WITH_AES_128_GCM_SHA256;
-openssl_suite("AES256-GCM-SHA384") ->
- ?TLS_RSA_WITH_AES_256_GCM_SHA384;
-openssl_suite("DHE-RSA-AES128-GCM-SHA256") ->
- ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256;
-openssl_suite("DHE-RSA-AES256-GCM-SHA384") ->
- ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384;
-openssl_suite("DH-RSA-AES128-GCM-SHA256") ->
- ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256;
-openssl_suite("DH-RSA-AES256-GCM-SHA384") ->
- ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384;
-openssl_suite("DHE-DSS-AES128-GCM-SHA256") ->
- ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256;
-openssl_suite("DHE-DSS-AES256-GCM-SHA384") ->
- ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384;
-openssl_suite("DH-DSS-AES128-GCM-SHA256") ->
- ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256;
-openssl_suite("DH-DSS-AES256-GCM-SHA384") ->
- ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384;
-
-%% RFC 5289 ECC AES-GCM Cipher Suites
-openssl_suite("ECDHE-ECDSA-AES128-GCM-SHA256") ->
- ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
-openssl_suite("ECDHE-ECDSA-AES256-GCM-SHA384") ->
- ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
-openssl_suite("ECDH-ECDSA-AES128-GCM-SHA256") ->
- ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256;
-openssl_suite("ECDH-ECDSA-AES256-GCM-SHA384") ->
- ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384;
-openssl_suite("ECDHE-RSA-AES128-GCM-SHA256") ->
- ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256;
-openssl_suite("ECDHE-RSA-AES256-GCM-SHA384") ->
- ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384;
-openssl_suite("ECDH-RSA-AES128-GCM-SHA256") ->
- ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256;
-openssl_suite("ECDH-RSA-AES256-GCM-SHA384") ->
- ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384.
-
+%% Description: Returns a list of the RSA key exchange
+%% cipher suites, only supported if explicitly set by user.
+%% Are not considered secure any more.
%%--------------------------------------------------------------------
--spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite().
-%%
-%% Description: Return openssl cipher suite name.
-%%-------------------------------------------------------------------
-openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
- "DHE-RSA-AES256-SHA";
-openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
- "DHE-DSS-AES256-SHA";
-openssl_suite_name(?TLS_RSA_WITH_AES_256_CBC_SHA) ->
- "AES256-SHA";
-openssl_suite_name(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
- "EDH-RSA-DES-CBC3-SHA";
-openssl_suite_name(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) ->
- "EDH-DSS-DES-CBC3-SHA";
-openssl_suite_name(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) ->
- "DES-CBC3-SHA";
-openssl_suite_name( ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ->
- "DHE-RSA-AES128-SHA";
-openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) ->
- "DHE-DSS-AES128-SHA";
-openssl_suite_name(?TLS_RSA_WITH_AES_128_CBC_SHA) ->
- "AES128-SHA";
-openssl_suite_name(?TLS_RSA_WITH_RC4_128_SHA) ->
- "RC4-SHA";
-openssl_suite_name(?TLS_RSA_WITH_RC4_128_MD5) ->
- "RC4-MD5";
-openssl_suite_name(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
- "EDH-RSA-DES-CBC-SHA";
-openssl_suite_name(?TLS_RSA_WITH_DES_CBC_SHA) ->
- "DES-CBC-SHA";
-openssl_suite_name(?TLS_RSA_WITH_NULL_SHA256) ->
- "NULL-SHA256";
-openssl_suite_name(?TLS_RSA_WITH_AES_128_CBC_SHA256) ->
- "AES128-SHA256";
-openssl_suite_name(?TLS_RSA_WITH_AES_256_CBC_SHA256) ->
- "AES256-SHA256";
-openssl_suite_name(?TLS_DH_DSS_WITH_AES_128_CBC_SHA256) ->
- "DH-DSS-AES128-SHA256";
-openssl_suite_name(?TLS_DH_RSA_WITH_AES_128_CBC_SHA256) ->
- "DH-RSA-AES128-SHA256";
-openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) ->
- "DHE-DSS-AES128-SHA256";
-openssl_suite_name(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) ->
- "DHE-RSA-AES128-SHA256";
-openssl_suite_name(?TLS_DH_DSS_WITH_AES_256_CBC_SHA256) ->
- "DH-DSS-AES256-SHA256";
-openssl_suite_name(?TLS_DH_RSA_WITH_AES_256_CBC_SHA256) ->
- "DH-RSA-AES256-SHA256";
-openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) ->
- "DHE-DSS-AES256-SHA256";
-openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) ->
- "DHE-RSA-AES256-SHA256";
-
-%%% PSK Cipher Suites RFC 4279
-
-openssl_suite_name(?TLS_PSK_WITH_AES_256_CBC_SHA) ->
- "PSK-AES256-CBC-SHA";
-openssl_suite_name(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) ->
- "PSK-3DES-EDE-CBC-SHA";
-openssl_suite_name(?TLS_PSK_WITH_AES_128_CBC_SHA) ->
- "PSK-AES128-CBC-SHA";
-openssl_suite_name(?TLS_PSK_WITH_RC4_128_SHA) ->
- "PSK-RC4-SHA";
-
-%%% SRP Cipher Suites RFC 5054
-
-openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) ->
- "SRP-RSA-3DES-EDE-CBC-SHA";
-openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) ->
- "SRP-DSS-3DES-EDE-CBC-SHA";
-openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) ->
- "SRP-RSA-AES-128-CBC-SHA";
-openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) ->
- "SRP-DSS-AES-128-CBC-SHA";
-openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) ->
- "SRP-RSA-AES-256-CBC-SHA";
-openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) ->
- "SRP-DSS-AES-256-CBC-SHA";
-
-%% RFC 4492 EC TLS suites
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) ->
- "ECDH-ECDSA-RC4-SHA";
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
- "ECDH-ECDSA-DES-CBC3-SHA";
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) ->
- "ECDH-ECDSA-AES128-SHA";
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) ->
- "ECDH-ECDSA-AES256-SHA";
-
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) ->
- "ECDHE-ECDSA-RC4-SHA";
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
- "ECDHE-ECDSA-DES-CBC3-SHA";
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) ->
- "ECDHE-ECDSA-AES128-SHA";
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) ->
- "ECDHE-ECDSA-AES256-SHA";
-
-openssl_suite_name(?TLS_ECDH_RSA_WITH_RC4_128_SHA) ->
- "ECDH-RSA-RC4-SHA";
-openssl_suite_name(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) ->
- "ECDH-RSA-DES-CBC3-SHA";
-openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) ->
- "ECDH-RSA-AES128-SHA";
-openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) ->
- "ECDH-RSA-AES256-SHA";
-
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) ->
- "ECDHE-RSA-RC4-SHA";
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
- "ECDHE-RSA-DES-CBC3-SHA";
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) ->
- "ECDHE-RSA-AES128-SHA";
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) ->
- "ECDHE-RSA-AES256-SHA";
-
-%% RFC 5289 EC TLS suites
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) ->
- "ECDHE-ECDSA-AES128-SHA256";
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) ->
- "ECDHE-ECDSA-AES256-SHA384";
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) ->
- "ECDH-ECDSA-AES128-SHA256";
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) ->
- "ECDH-ECDSA-AES256-SHA384";
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) ->
- "ECDHE-RSA-AES128-SHA256";
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) ->
- "ECDHE-RSA-AES256-SHA384";
-openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) ->
- "ECDH-RSA-AES128-SHA256";
-openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) ->
- "ECDH-RSA-AES256-SHA384";
-
-%% RFC 5288 AES-GCM Cipher Suites
-openssl_suite_name(?TLS_RSA_WITH_AES_128_GCM_SHA256) ->
- "AES128-GCM-SHA256";
-openssl_suite_name(?TLS_RSA_WITH_AES_256_GCM_SHA384) ->
- "AES256-GCM-SHA384";
-openssl_suite_name(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) ->
- "DHE-RSA-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) ->
- "DHE-RSA-AES256-GCM-SHA384";
-openssl_suite_name(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) ->
- "DH-RSA-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) ->
- "DH-RSA-AES256-GCM-SHA384";
-openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) ->
- "DHE-DSS-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) ->
- "DHE-DSS-AES256-GCM-SHA384";
-openssl_suite_name(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) ->
- "DH-DSS-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) ->
- "DH-DSS-AES256-GCM-SHA384";
-
-%% RFC 5289 ECC AES-GCM Cipher Suites
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) ->
- "ECDHE-ECDSA-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) ->
- "ECDHE-ECDSA-AES256-GCM-SHA384";
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) ->
- "ECDH-ECDSA-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) ->
- "ECDH-ECDSA-AES256-GCM-SHA384";
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) ->
- "ECDHE-RSA-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) ->
- "ECDHE-RSA-AES256-GCM-SHA384";
-openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) ->
- "ECDH-RSA-AES128-GCM-SHA256";
-openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) ->
- "ECDH-RSA-AES256-GCM-SHA384";
-
-%% No oppenssl name
-openssl_suite_name(Cipher) ->
- suite_definition(Cipher).
+rsa_suites({3, 0}) ->
+ rsa_suites(0);
+rsa_suites({3, Minor}) ->
+ rsa_suites(Minor) ++ rsa_suites(0);
+rsa_suites(0) ->
+ [?TLS_RSA_WITH_AES_256_CBC_SHA,
+ ?TLS_RSA_WITH_AES_128_CBC_SHA,
+ ?TLS_RSA_WITH_3DES_EDE_CBC_SHA
+ ];
+rsa_suites(N) when N =< 4 ->
+ [
+ ?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
+ ].
%%--------------------------------------------------------------------
--spec filter(undefined | binary(), [cipher_suite()]) -> [cipher_suite()].
+-spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()],
+ ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Select the cipher suites that can be used together with the
%% supplied certificate. (Server side functionality)
%%-------------------------------------------------------------------
-filter(undefined, Ciphers) ->
+filter(undefined, Ciphers, _) ->
Ciphers;
-filter(DerCert, Ciphers) ->
+filter(DerCert, Ciphers0, Version) ->
OtpCert = public_key:pkix_decode_cert(DerCert, otp),
SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm,
PubKeyInfo = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo,
PubKeyAlg = PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm,
- Ciphers1 =
- case ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm) of
- rsa ->
- filter_keyuse(OtpCert, ((Ciphers -- dsa_signed_suites()) -- ec_keyed_suites()) -- ecdh_suites(),
- rsa_suites(), dhe_rsa_suites() ++ ecdhe_rsa_suites());
- dsa ->
- (Ciphers -- rsa_keyed_suites()) -- ec_keyed_suites();
- ec ->
- filter_keyuse(OtpCert, (Ciphers -- rsa_keyed_suites()) -- dsa_signed_suites(),
- [], ecdhe_ecdsa_suites())
- end,
-
- case public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm) of
- {_, rsa} ->
- Ciphers1 -- ecdsa_signed_suites();
- {_, dsa} ->
- Ciphers1;
- {_, ecdsa} ->
- Ciphers1 -- rsa_signed_suites()
- end.
-
+ Ciphers = filter_suites_pubkey(
+ ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm),
+ Ciphers0, Version, OtpCert),
+ {_, Sign} = public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm),
+ filter_suites_signature(Sign, Ciphers, Version).
+
+%%--------------------------------------------------------------------
+-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) ->
+ [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
+%%
+%% Description: Filter suites using supplied filter funs
+%%-------------------------------------------------------------------
+filter_suites(Suites, Filters) ->
+ ApplyFilters = fun(Suite) ->
+ filter_suite(Suite, Filters)
+ end,
+ lists:filter(ApplyFilters, Suites).
+
+filter_suite(#{key_exchange := KeyExchange,
+ cipher := Cipher,
+ mac := Hash,
+ prf := Prf},
+ #{key_exchange_filters := KeyFilters,
+ cipher_filters := CipherFilters,
+ mac_filters := HashFilters,
+ prf_filters := PrfFilters}) ->
+ all_filters(KeyExchange, KeyFilters) andalso
+ all_filters(Cipher, CipherFilters) andalso
+ all_filters(Hash, HashFilters) andalso
+ all_filters(Prf, PrfFilters);
+filter_suite(Suite, Filters) ->
+ filter_suite(ssl_cipher_format:suite_bin_to_map(Suite), Filters).
+
%%--------------------------------------------------------------------
--spec filter_suites([cipher_suite()]) -> [cipher_suite()].
+-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) ->
+ [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
%%
%% Description: Filter suites for algorithms supported by crypto.
%%-------------------------------------------------------------------
-filter_suites(Suites = [Value|_]) when is_tuple(Value) ->
- Algos = crypto:supports(),
- Hashs = proplists:get_value(hashs, Algos),
- lists:filter(fun({KeyExchange, Cipher, Hash}) ->
- is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso
- is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso
- is_acceptable_hash(Hash, proplists:get_value(hashs, Algos));
- ({KeyExchange, Cipher, Hash, Prf}) ->
- is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso
- is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso
- is_acceptable_hash(Hash, Hashs) andalso
- is_acceptable_prf(Prf, Hashs)
- end, Suites);
-
filter_suites(Suites) ->
+ Filters = crypto_support_filters(),
+ filter_suites(Suites, Filters).
+
+all_filters(_, []) ->
+ true;
+all_filters(Value, [Filter| Rest]) ->
+ case Filter(Value) of
+ true ->
+ all_filters(Value, Rest);
+ false ->
+ false
+ end.
+crypto_support_filters() ->
Algos = crypto:supports(),
Hashs = proplists:get_value(hashs, Algos),
- lists:filter(fun(Suite) ->
- {KeyExchange, Cipher, Hash, Prf} = ssl_cipher:suite_definition(Suite),
- is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso
- is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso
- is_acceptable_hash(Hash, Hashs) andalso
- is_acceptable_prf(Prf, Hashs)
- end, Suites).
+ #{key_exchange_filters =>
+ [fun(KeyExchange) ->
+ is_acceptable_keyexchange(KeyExchange,
+ proplists:get_value(public_keys, Algos))
+ end],
+ cipher_filters =>
+ [fun(Cipher) ->
+ is_acceptable_cipher(Cipher,
+ proplists:get_value(ciphers, Algos))
+ end],
+ mac_filters =>
+ [fun(Hash) ->
+ is_acceptable_hash(Hash, Hashs)
+ end],
+ prf_filters =>
+ [fun(Prf) ->
+ is_acceptable_prf(Prf,
+ proplists:get_value(hashs, Algos))
+ end]}.
is_acceptable_keyexchange(KeyExchange, _Algos) when KeyExchange == psk;
- KeyExchange == null ->
+ KeyExchange == null;
+ KeyExchange == any ->
true;
is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == dh_anon;
KeyExchange == dhe_psk ->
@@ -1465,7 +612,8 @@ is_acceptable_keyexchange(dhe_dss, Algos) ->
is_acceptable_keyexchange(dhe_rsa, Algos) ->
proplists:get_bool(dh, Algos) andalso
proplists:get_bool(rsa, Algos);
-is_acceptable_keyexchange(ecdh_anon, Algos) ->
+is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == ecdh_anon;
+ KeyExchange == ecdhe_psk ->
proplists:get_bool(ecdh, Algos);
is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == ecdh_ecdsa;
KeyExchange == ecdhe_ecdsa ->
@@ -1496,7 +644,7 @@ is_acceptable_cipher(rc4_128, Algos) ->
is_acceptable_cipher(des_cbc, Algos) ->
proplists:get_bool(des_cbc, Algos);
is_acceptable_cipher('3des_ede_cbc', Algos) ->
- proplists:get_bool(des3_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) ->
@@ -1505,11 +653,19 @@ 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);
is_acceptable_cipher(Cipher, Algos) ->
proplists:get_bool(Cipher, Algos).
is_acceptable_hash(null, _Algos) ->
true;
+is_acceptable_hash(aead, _Algos) ->
+ true;
is_acceptable_hash(Hash, Algos) ->
proplists:get_bool(Hash, Algos).
@@ -1531,24 +687,43 @@ is_fallback(CipherSuites)->
random_bytes(N) ->
crypto:strong_rand_bytes(N).
-calc_aad(Type, {MajVer, MinVer},
- #{sequence_number := SeqNo}) ->
- <<SeqNo:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
-
calc_mac_hash(Type, Version,
PlainFragment, #{sequence_number := SeqNo,
mac_secret := MacSecret,
- security_parameters:=
- SecPars}) ->
+ security_parameters :=
+ #security_parameters{mac_algorithm = MacAlgorithm}}) ->
+ calc_mac_hash(Type, Version, PlainFragment, MacAlgorithm, MacSecret, SeqNo).
+%%
+calc_mac_hash(Type, Version, PlainFragment, MacAlgorithm, MacSecret, SeqNo) ->
Length = erlang:iolist_size(PlainFragment),
- mac_hash(Version, SecPars#security_parameters.mac_algorithm,
- MacSecret, SeqNo, Type,
- Length, PlainFragment).
+ mac_hash(Version, MacAlgorithm, MacSecret, SeqNo, Type, Length, PlainFragment).
-is_stream_ciphersuite({_, rc4_128, _, _}) ->
+is_stream_ciphersuite(#{cipher := rc4_128}) ->
true;
is_stream_ciphersuite(_) ->
false.
+
+-spec hash_size(atom()) -> integer().
+hash_size(null) ->
+ 0;
+%% The AEAD MAC hash size is not used in the context
+%% of calculating the master secret. See RFC 5246 Section 6.2.3.3.
+hash_size(aead) ->
+ 0;
+hash_size(md5) ->
+ 16;
+hash_size(sha) ->
+ 20;
+%% Uncomment when adding cipher suite that needs it
+%hash_size(sha224) ->
+% 28;
+hash_size(sha256) ->
+ 32;
+hash_size(sha384) ->
+ 48;
+hash_size(sha512) ->
+ 64.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -1558,7 +733,7 @@ mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
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 ->
+ when N =:= 1; N =:= 2; N =:= 3; N =:= 4 ->
tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
Length, Fragment).
@@ -1576,6 +751,12 @@ bulk_cipher_algorithm(Cipher) when Cipher == aes_128_cbc;
bulk_cipher_algorithm(Cipher) when Cipher == aes_128_gcm;
Cipher == aes_256_gcm ->
?AES_GCM;
+bulk_cipher_algorithm(Cipher) when Cipher == aes_128_ccm;
+ Cipher == aes_256_ccm ->
+ ?AES_CCM;
+bulk_cipher_algorithm(Cipher) when Cipher == aes_128_ccm_8;
+ Cipher == aes_256_ccm_8 ->
+ ?AES_CCM_8;
bulk_cipher_algorithm(chacha20_poly1305) ->
?CHACHA20_POLY1305.
@@ -1590,6 +771,10 @@ type(Cipher) when Cipher == des_cbc;
?BLOCK;
type(Cipher) when Cipher == aes_128_gcm;
Cipher == aes_256_gcm;
+ Cipher == aes_128_ccm;
+ Cipher == aes_256_ccm;
+ Cipher == aes_128_ccm_8;
+ Cipher == aes_256_ccm_8;
Cipher == chacha20_poly1305 ->
?AEAD.
@@ -1607,8 +792,16 @@ key_material(aes_256_cbc) ->
32;
key_material(aes_128_gcm) ->
16;
+key_material(aes_128_ccm) ->
+ 16;
+key_material(aes_128_ccm_8) ->
+ 16;
key_material(aes_256_gcm) ->
32;
+key_material(aes_256_ccm_8) ->
+ 32;
+key_material(aes_256_ccm) ->
+ 32;
key_material(chacha20_poly1305) ->
32.
@@ -1624,45 +817,58 @@ expanded_key_material(Cipher) when Cipher == aes_128_cbc;
Cipher == aes_256_cbc;
Cipher == aes_128_gcm;
Cipher == aes_256_gcm;
+ Cipher == aes_128_ccm;
+ Cipher == aes_256_ccm;
+ Cipher == aes_128_ccm_8;
+ Cipher == aes_256_ccm_8;
Cipher == chacha20_poly1305 ->
unknown.
-
effective_key_bits(null) ->
0;
effective_key_bits(des_cbc) ->
56;
effective_key_bits(Cipher) when Cipher == rc4_128;
Cipher == aes_128_cbc;
- Cipher == aes_128_gcm ->
+ Cipher == aes_128_gcm;
+ Cipher == aes_128_ccm;
+ Cipher == aes_128_ccm_8 ->
128;
effective_key_bits('3des_ede_cbc') ->
168;
effective_key_bits(Cipher) when Cipher == aes_256_cbc;
Cipher == aes_256_gcm;
+ Cipher == aes_256_ccm;
+ Cipher == aes_256_ccm_8;
Cipher == chacha20_poly1305 ->
256.
iv_size(Cipher) when Cipher == null;
- Cipher == rc4_128;
- Cipher == chacha20_poly1305->
+ Cipher == rc4_128 ->
0;
-
iv_size(Cipher) when Cipher == aes_128_gcm;
- Cipher == aes_256_gcm ->
+ Cipher == aes_256_gcm;
+ Cipher == aes_128_ccm;
+ Cipher == aes_256_ccm;
+ Cipher == aes_128_ccm_8;
+ Cipher == aes_256_ccm_8 ->
4;
-
+iv_size(chacha20_poly1305) ->
+ 12;
iv_size(Cipher) ->
block_size(Cipher).
block_size(Cipher) when Cipher == des_cbc;
Cipher == '3des_ede_cbc' ->
8;
-
block_size(Cipher) when Cipher == aes_128_cbc;
Cipher == aes_256_cbc;
Cipher == aes_128_gcm;
Cipher == aes_256_gcm;
+ Cipher == aes_128_ccm;
+ Cipher == aes_256_ccm;
+ Cipher == aes_128_ccm_8;
+ Cipher == aes_256_ccm_8;
Cipher == chacha20_poly1305 ->
16.
@@ -1673,6 +879,11 @@ prf_algorithm(default_prf, {3, _}) ->
prf_algorithm(Algo, _) ->
hash_algorithm(Algo).
+mac_algorithm(aead) ->
+ aead;
+mac_algorithm(Algo) ->
+ hash_algorithm(Algo).
+
hash_algorithm(null) -> ?NULL;
hash_algorithm(md5) -> ?MD5;
hash_algorithm(sha) -> ?SHA; %% Only sha always refers to "SHA-1"
@@ -1701,22 +912,93 @@ sign_algorithm(?ECDSA) -> ecdsa;
sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 4) and (Other =< 223)) -> unassigned;
sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 224) and (Other =< 255)) -> Other.
-hash_size(null) ->
- 0;
-hash_size(md5) ->
- 16;
-hash_size(sha) ->
- 20;
-%% Uncomment when adding cipher suite that needs it
-%hash_size(sha224) ->
-% 28;
-hash_size(sha256) ->
- 32;
-hash_size(sha384) ->
- 48.
-%% Uncomment when adding cipher suite that needs it
-%hash_size(sha512) ->
-% 64.
+
+signature_scheme(rsa_pkcs1_sha256) -> ?RSA_PKCS1_SHA256;
+signature_scheme(rsa_pkcs1_sha384) -> ?RSA_PKCS1_SHA384;
+signature_scheme(rsa_pkcs1_sha512) -> ?RSA_PKCS1_SHA512;
+signature_scheme(ecdsa_secp256r1_sha256) -> ?ECDSA_SECP256R1_SHA256;
+signature_scheme(ecdsa_secp384r1_sha384) -> ?ECDSA_SECP384R1_SHA384;
+signature_scheme(ecdsa_secp521r1_sha512) -> ?ECDSA_SECP521R1_SHA512;
+signature_scheme(rsa_pss_rsae_sha256) -> ?RSA_PSS_RSAE_SHA256;
+signature_scheme(rsa_pss_rsae_sha384) -> ?RSA_PSS_RSAE_SHA384;
+signature_scheme(rsa_pss_rsae_sha512) -> ?RSA_PSS_RSAE_SHA512;
+signature_scheme(ed25519) -> ?ED25519;
+signature_scheme(ed448) -> ?ED448;
+signature_scheme(rsa_pss_pss_sha256) -> ?RSA_PSS_PSS_SHA256;
+signature_scheme(rsa_pss_pss_sha384) -> ?RSA_PSS_PSS_SHA384;
+signature_scheme(rsa_pss_pss_sha512) -> ?RSA_PSS_PSS_SHA512;
+signature_scheme(rsa_pkcs1_sha1) -> ?RSA_PKCS1_SHA1;
+signature_scheme(ecdsa_sha1) -> ?ECDSA_SHA1;
+%% Handling legacy signature algorithms
+signature_scheme({Hash0, Sign0}) ->
+ Hash = hash_algorithm(Hash0),
+ Sign = sign_algorithm(Sign0),
+ <<?UINT16(SigAlg)>> = <<?BYTE(Hash),?BYTE(Sign)>>,
+ SigAlg;
+signature_scheme(?RSA_PKCS1_SHA256) -> rsa_pkcs1_sha256;
+signature_scheme(?RSA_PKCS1_SHA384) -> rsa_pkcs1_sha384;
+signature_scheme(?RSA_PKCS1_SHA512) -> rsa_pkcs1_sha512;
+signature_scheme(?ECDSA_SECP256R1_SHA256) -> ecdsa_secp256r1_sha256;
+signature_scheme(?ECDSA_SECP384R1_SHA384) -> ecdsa_secp384r1_sha384;
+signature_scheme(?ECDSA_SECP521R1_SHA512) -> ecdsa_secp521r1_sha512;
+signature_scheme(?RSA_PSS_RSAE_SHA256) -> rsa_pss_rsae_sha256;
+signature_scheme(?RSA_PSS_RSAE_SHA384) -> rsa_pss_rsae_sha384;
+signature_scheme(?RSA_PSS_RSAE_SHA512) -> rsa_pss_rsae_sha512;
+signature_scheme(?ED25519) -> ed25519;
+signature_scheme(?ED448) -> ed448;
+signature_scheme(?RSA_PSS_PSS_SHA256) -> rsa_pss_pss_sha256;
+signature_scheme(?RSA_PSS_PSS_SHA384) -> rsa_pss_pss_sha384;
+signature_scheme(?RSA_PSS_PSS_SHA512) -> rsa_pss_pss_sha512;
+signature_scheme(?RSA_PKCS1_SHA1) -> rsa_pkcs1_sha1;
+signature_scheme(?ECDSA_SHA1) -> ecdsa_sha1;
+%% Handling legacy signature algorithms for logging purposes. These algorithms
+%% cannot be used in TLS 1.3 handshakes.
+signature_scheme(SignAlgo) when is_integer(SignAlgo) ->
+ <<?BYTE(Hash),?BYTE(Sign)>> = <<?UINT16(SignAlgo)>>,
+ {ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)};
+signature_scheme(_) -> unassigned.
+%% TODO: reserved code points?
+
+scheme_to_components(rsa_pkcs1_sha256) -> {sha256, rsa_pkcs1, undefined};
+scheme_to_components(rsa_pkcs1_sha384) -> {sha384, rsa_pkcs1, undefined};
+scheme_to_components(rsa_pkcs1_sha512) -> {sha512, rsa_pkcs1, undefined};
+scheme_to_components(ecdsa_secp256r1_sha256) -> {sha256, ecdsa, secp256r1};
+scheme_to_components(ecdsa_secp384r1_sha384) -> {sha384, ecdsa, secp384r1};
+scheme_to_components(ecdsa_secp521r1_sha512) -> {sha512, ecdsa, secp521r1};
+scheme_to_components(rsa_pss_rsae_sha256) -> {sha256, rsa_pss_rsae, undefined};
+scheme_to_components(rsa_pss_rsae_sha384) -> {sha384, rsa_pss_rsae, undefined};
+scheme_to_components(rsa_pss_rsae_sha512) -> {sha512, rsa_pss_rsae, undefined};
+scheme_to_components(ed25519) -> {undefined, undefined, undefined};
+scheme_to_components(ed448) -> {undefined, undefined, undefined};
+scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined};
+scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined};
+scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined};
+scheme_to_components(rsa_pkcs1_sha1) -> {sha1, rsa_pkcs1, undefined};
+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 = ?sha256WithRSAEncryption}) ->
+ rsa_pkcs1_sha256;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) ->
+ rsa_pkcs1_sha384;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha512WithRSAEncryption}) ->
+ rsa_pkcs1_sha512;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA256'}) ->
+ ecdsa_secp256r1_sha256;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA384'}) ->
+ ecdsa_secp384r1_sha384;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA512'}) ->
+ ecdsa_secp512r1_sha512;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'sha-1WithRSAEncryption'}) ->
+ rsa_pkcs1_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) ->
+ rsa_pkcs1_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA1'}) ->
+ ecdsa_sha1.
+
%% RFC 5246: 6.2.3.2. CBC Block Cipher
%%
@@ -1754,7 +1036,7 @@ generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
next_iv = IV};
generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
- when N == 2; N == 3 ->
+ when N == 2; N == 3; N == 4 ->
Sz1 = byte_size(T) - 1,
<<_:Sz1/binary, ?BYTE(PadLength)>> = T,
IVLength = byte_size(IV),
@@ -1784,21 +1066,51 @@ is_correct_padding(GenBlockCipher, {3, 1}, false) ->
%% Padding must be checked in TLS 1.1 and after
is_correct_padding(#generic_block_cipher{padding_length = Len,
padding = Padding}, _, _) ->
- Len == byte_size(Padding) andalso
- list_to_binary(lists:duplicate(Len, Len)) == Padding.
-
-get_padding(Length, BlockSize) ->
- get_padding_aux(BlockSize, Length rem BlockSize).
-
-get_padding_aux(_, 0) ->
- {0, <<>>};
-get_padding_aux(BlockSize, PadLength) ->
- N = BlockSize - PadLength,
- {N, list_to_binary(lists:duplicate(N, N))}.
+ (Len == byte_size(Padding)) andalso (padding(Len) == Padding).
+
+padding(PadLen) ->
+ case PadLen of
+ 0 -> <<>>;
+ 1 -> <<1>>;
+ 2 -> <<2,2>>;
+ 3 -> <<3,3,3>>;
+ 4 -> <<4,4,4,4>>;
+ 5 -> <<5,5,5,5,5>>;
+ 6 -> <<6,6,6,6,6,6>>;
+ 7 -> <<7,7,7,7,7,7,7>>;
+ 8 -> <<8,8,8,8,8,8,8,8>>;
+ 9 -> <<9,9,9,9,9,9,9,9,9>>;
+ 10 -> <<10,10,10,10,10,10,10,10,10,10>>;
+ 11 -> <<11,11,11,11,11,11,11,11,11,11,11>>;
+ 12 -> <<12,12,12,12,12,12,12,12,12,12,12,12>>;
+ 13 -> <<13,13,13,13,13,13,13,13,13,13,13,13,13>>;
+ 14 -> <<14,14,14,14,14,14,14,14,14,14,14,14,14,14>>;
+ 15 -> <<15,15,15,15,15,15,15,15,15,15,15,15,15,15,15>>;
+ _ ->
+ binary:copy(<<PadLen>>, PadLen)
+ end.
-random_iv(IV) ->
- IVSz = byte_size(IV),
- random_bytes(IVSz).
+padding_with_len(TextLen, BlockSize) ->
+ case BlockSize - (TextLen rem BlockSize) of
+ 0 -> <<0>>;
+ 1 -> <<1,1>>;
+ 2 -> <<2,2,2>>;
+ 3 -> <<3,3,3,3>>;
+ 4 -> <<4,4,4,4,4>>;
+ 5 -> <<5,5,5,5,5,5>>;
+ 6 -> <<6,6,6,6,6,6,6>>;
+ 7 -> <<7,7,7,7,7,7,7,7>>;
+ 8 -> <<8,8,8,8,8,8,8,8,8>>;
+ 9 -> <<9,9,9,9,9,9,9,9,9,9>>;
+ 10 -> <<10,10,10,10,10,10,10,10,10,10,10>>;
+ 11 -> <<11,11,11,11,11,11,11,11,11,11,11,11>>;
+ 12 -> <<12,12,12,12,12,12,12,12,12,12,12,12,12>>;
+ 13 -> <<13,13,13,13,13,13,13,13,13,13,13,13,13,13>>;
+ 14 -> <<14,14,14,14,14,14,14,14,14,14,14,14,14,14,14>>;
+ 15 -> <<15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15>>;
+ PadLen ->
+ binary:copy(<<PadLen>>, PadLen + 1)
+ end.
next_iv(Bin, IV) ->
BinSz = byte_size(Bin),
@@ -1807,143 +1119,214 @@ next_iv(Bin, IV) ->
<<_:FirstPart/binary, NextIV:IVSz/binary>> = Bin,
NextIV.
-rsa_signed_suites() ->
- dhe_rsa_suites() ++ rsa_suites() ++
- psk_rsa_suites() ++ srp_rsa_suites() ++
- ecdh_rsa_suites() ++ ecdhe_rsa_suites().
-
-rsa_keyed_suites() ->
- dhe_rsa_suites() ++ rsa_suites() ++
- psk_rsa_suites() ++ srp_rsa_suites() ++
- ecdhe_rsa_suites().
-
-dhe_rsa_suites() ->
- [?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_RSA_WITH_DES_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
- ].
-
-psk_rsa_suites() ->
- [?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
- ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_PSK_WITH_RC4_128_SHA].
-
-srp_rsa_suites() ->
- [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA].
-
-rsa_suites() ->
- [?TLS_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_WITH_RC4_128_SHA,
- ?TLS_RSA_WITH_RC4_128_MD5,
- ?TLS_RSA_WITH_DES_CBC_SHA,
- ?TLS_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_RSA_WITH_AES_256_GCM_SHA384].
-
-ecdh_rsa_suites() ->
- [?TLS_ECDH_RSA_WITH_NULL_SHA,
- ?TLS_ECDH_RSA_WITH_RC4_128_SHA,
- ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384].
-
-ecdhe_rsa_suites() ->
- [?TLS_ECDHE_RSA_WITH_NULL_SHA,
- ?TLS_ECDHE_RSA_WITH_RC4_128_SHA,
- ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256].
-
-dsa_signed_suites() ->
- dhe_dss_suites() ++ srp_dss_suites().
-
-dhe_dss_suites() ->
- [?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
- ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384].
-
-srp_dss_suites() ->
- [?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA].
+filter_suites_pubkey(rsa, CiphersSuites0, _Version, OtpCert) ->
+ KeyUses = key_uses(OtpCert),
+ NotECDSAKeyed = (CiphersSuites0 -- ec_keyed_suites(CiphersSuites0))
+ -- dss_keyed_suites(CiphersSuites0),
+ CiphersSuites = filter_keyuse_suites(keyEncipherment, KeyUses,
+ NotECDSAKeyed,
+ rsa_suites_encipher(CiphersSuites0)),
+ filter_keyuse_suites(digitalSignature, KeyUses, CiphersSuites,
+ rsa_ecdhe_dhe_suites(CiphersSuites));
+filter_suites_pubkey(dsa, Ciphers, _, OtpCert) ->
+ KeyUses = key_uses(OtpCert),
+ NotECRSAKeyed = (Ciphers -- rsa_keyed_suites(Ciphers)) -- ec_keyed_suites(Ciphers),
+ filter_keyuse_suites(digitalSignature, KeyUses, NotECRSAKeyed,
+ dss_dhe_suites(Ciphers));
+filter_suites_pubkey(ec, Ciphers, _, OtpCert) ->
+ Uses = key_uses(OtpCert),
+ NotRSADSAKeyed = (Ciphers -- rsa_keyed_suites(Ciphers)) -- dss_keyed_suites(Ciphers),
+ CiphersSuites = filter_keyuse_suites(digitalSignature, Uses, NotRSADSAKeyed,
+ ec_ecdhe_suites(Ciphers)),
+ filter_keyuse_suites(keyAgreement, Uses, CiphersSuites, ec_ecdh_suites(Ciphers)).
+
+filter_suites_signature(_, Ciphers, {3, N}) when N >= 3 ->
+ Ciphers;
+filter_suites_signature(rsa, Ciphers, Version) ->
+ (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version);
+filter_suites_signature(dsa, Ciphers, Version) ->
+ (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- rsa_signed_suites(Ciphers, Version);
+filter_suites_signature(ecdsa, Ciphers, Version) ->
+ (Ciphers -- rsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version).
+
+
+%% From RFC 5246 - Section 7.4.2. Server Certificate
+%% If the client provided a "signature_algorithms" extension, then all
+%% certificates provided by the server MUST be signed by a
+%% hash/signature algorithm pair that appears in that extension. Note
+%% that this implies that a certificate containing a key for one
+%% signature algorithm MAY be signed using a different signature
+%% algorithm (for instance, an RSA key signed with a DSA key). This is
+%% a departure from TLS 1.1, which required that the algorithms be the
+%% same.
+%% Note that this also implies that the DH_DSS, DH_RSA,
+%% ECDH_ECDSA, and ECDH_RSA key exchange algorithms do not restrict the
+%% algorithm used to sign the certificate. Fixed DH certificates MAY be
+%% signed with any hash/signature algorithm pair appearing in the
+%% extension. The names DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are
+%% historical.
+%% Note: DH_DSS and DH_RSA is not supported
+rsa_signed({3,N}) when N >= 3 ->
+ fun(rsa) -> true;
+ (dhe_rsa) -> true;
+ (ecdhe_rsa) -> true;
+ (rsa_psk) -> true;
+ (srp_rsa) -> true;
+ (_) -> false
+ end;
+rsa_signed(_) ->
+ fun(rsa) -> true;
+ (dhe_rsa) -> true;
+ (ecdhe_rsa) -> true;
+ (ecdh_rsa) -> true;
+ (rsa_psk) -> true;
+ (srp_rsa) -> true;
+ (_) -> false
+ end.
+%% Cert should be signed by RSA
+rsa_signed_suites(Ciphers, Version) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [rsa_signed(Version)],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+ecdsa_signed({3,N}) when N >= 3 ->
+ fun(ecdhe_ecdsa) -> true;
+ (_) -> false
+ end;
+ecdsa_signed(_) ->
+ fun(ecdhe_ecdsa) -> true;
+ (ecdh_ecdsa) -> true;
+ (_) -> false
+ end.
+
+%% Cert should be signed by ECDSA
+ecdsa_signed_suites(Ciphers, Version) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [ecdsa_signed(Version)],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+rsa_keyed(dhe_rsa) ->
+ true;
+rsa_keyed(ecdhe_rsa) ->
+ true;
+rsa_keyed(rsa) ->
+ true;
+rsa_keyed(rsa_psk) ->
+ true;
+rsa_keyed(srp_rsa) ->
+ true;
+rsa_keyed(_) ->
+ false.
-ec_keyed_suites() ->
- ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites()
- ++ ecdh_rsa_suites().
+%% Certs key is an RSA key
+rsa_keyed_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> rsa_keyed(Kex) end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+%% RSA Certs key can be used for encipherment
+rsa_suites_encipher(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(rsa) -> true;
+ (rsa_psk) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+dss_keyed(dhe_dss) ->
+ true;
+dss_keyed(spr_dss) ->
+ true;
+dss_keyed(_) ->
+ false.
+
+%% Cert should be have DSS key (DSA)
+dss_keyed_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> dss_keyed(Kex) end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+%% Cert should be signed by DSS (DSA)
+dsa_signed_suites(Ciphers, Version) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [dsa_signed(Version)],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+dsa_signed(_) ->
+ fun(dhe_dss) -> true;
+ (_) -> false
+ end.
-ecdsa_signed_suites() ->
- ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites().
+dss_dhe_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_dss) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
-ecdh_suites() ->
- ecdh_rsa_suites() ++ ecdh_ecdsa_suites().
+ec_keyed(ecdh_ecdsa) ->
+ true;
+ec_keyed(ecdh_rsa) ->
+ true;
+ec_keyed(ecdhe_ecdsa) ->
+ true;
+ec_keyed(_) ->
+ false.
-ecdh_ecdsa_suites() ->
- [?TLS_ECDH_ECDSA_WITH_NULL_SHA,
- ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
- ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384].
-
-ecdhe_ecdsa_suites() ->
- [?TLS_ECDHE_ECDSA_WITH_NULL_SHA,
- ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
- ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256].
-
-filter_keyuse(OtpCert, Ciphers, Suites, SignSuites) ->
+%% Certs key is an ECC key
+ec_keyed_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> ec_keyed(Kex) end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+%% EC Certs key usage keyAgreement
+ec_ecdh_suites(Ciphers)->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+%% EC Certs key usage digitalSignature
+ec_ecdhe_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdhe_ecdsa) -> true;
+ (ecdhe_rsa) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+%% RSA Certs key usage digitalSignature
+rsa_ecdhe_dhe_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_rsa) -> true;
+ (ecdhe_rsa) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+key_uses(OtpCert) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions,
Extensions = ssl_certificate:extensions_list(TBSExtensions),
case ssl_certificate:select_extension(?'id-ce-keyUsage', Extensions) of
undefined ->
- Ciphers;
- #'Extension'{extnValue = KeyUse} ->
- Result = filter_keyuse_suites(keyEncipherment,
- KeyUse, Ciphers, Suites),
- filter_keyuse_suites(digitalSignature,
- KeyUse, Result, SignSuites)
+ [];
+ #'Extension'{extnValue = KeyUses} ->
+ KeyUses
end.
+%% If no key-usage extension is defined all key-usages are allowed
+filter_keyuse_suites(_, [], CiphersSuites, _) ->
+ CiphersSuites;
filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) ->
case ssl_certificate:is_valid_key_usage(KeyUse, Use) of
true ->
@@ -1951,3 +1334,55 @@ filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) ->
false ->
CipherSuits -- Suites
end.
+
+generate_server_share(Group) ->
+ Key = generate_key_exchange(Group),
+ #key_share_server_hello{
+ server_share = #key_share_entry{
+ group = Group,
+ key_exchange = Key
+ }}.
+
+generate_client_shares([]) ->
+ #key_share_client_hello{client_shares = []};
+generate_client_shares(Groups) ->
+ generate_client_shares(Groups, []).
+%%
+generate_client_shares([], Acc) ->
+ #key_share_client_hello{client_shares = lists:reverse(Acc)};
+generate_client_shares([Group|Groups], Acc) ->
+ Key = generate_key_exchange(Group),
+ KeyShareEntry = #key_share_entry{
+ group = Group,
+ key_exchange = Key
+ },
+ generate_client_shares(Groups, [KeyShareEntry|Acc]).
+
+
+generate_key_exchange(secp256r1) ->
+ public_key:generate_key({namedCurve, secp256r1});
+generate_key_exchange(secp384r1) ->
+ public_key:generate_key({namedCurve, secp384r1});
+generate_key_exchange(secp521r1) ->
+ public_key:generate_key({namedCurve, secp521r1});
+generate_key_exchange(x25519) ->
+ crypto:generate_key(ecdh, x25519);
+generate_key_exchange(x448) ->
+ crypto:generate_key(ecdh, x448);
+generate_key_exchange(FFDHE) ->
+ public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)).
+
+
+%% TODO: Move this functionality to crypto!
+%% 7.4.1. Finite Field Diffie-Hellman
+%%
+%% For finite field groups, a conventional Diffie-Hellman [DH76]
+%% computation is performed. The negotiated key (Z) is converted to a
+%% byte string by encoding in big-endian form and left-padded with zeros
+%% up to the size of the prime. This byte string is used as the shared
+%% secret in the key schedule as specified above.
+add_zero_padding(Bin, PrimeSize)
+ when byte_size (Bin) =:= PrimeSize ->
+ Bin;
+add_zero_padding(Bin, PrimeSize) ->
+ add_zero_padding(<<0, Bin/binary>>, PrimeSize).
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 8e8f3d9c67..0fa5f66c49 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2015. All Rights Reserved.
+%% 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.
@@ -47,8 +47,10 @@
-record(cipher_state, {
iv,
key,
+ finished_key,
state,
- nonce
+ nonce,
+ tag_len
}).
%%% TLS_NULL_WITH_NULL_NULL is specified and is the initial state of a
@@ -399,6 +401,17 @@
%% TLS_RSA_PSK_WITH_AES_256_CBC_SHA = { 0x00, 0x95 };
-define(TLS_RSA_PSK_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#95)>>).
+%%% PSK NULL Cipher Suites RFC 4785
+
+%% TLS_PSK_WITH_NULL_SHA = { 0x00, 0x2C };
+-define(TLS_PSK_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#2C)>>).
+
+%% TLS_DHE_PSK_WITH_NULL_SHA = { 0x00, 0x2D };
+-define(TLS_DHE_PSK_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#2D)>>).
+
+%% TLS_RSA_PSK_WITH_NULL_SHA = { 0x00, 0x2E };
+-define(TLS_RSA_PSK_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#2E)>>).
+
%%% TLS 1.2 PSK Cipher Suites RFC 5487
%% TLS_PSK_WITH_AES_128_GCM_SHA256 = {0x00,0xA8};
@@ -455,6 +468,46 @@
%% TLS_RSA_PSK_WITH_NULL_SHA384 = {0x00,0xB9};
-define(TLS_RSA_PSK_WITH_NULL_SHA384, <<?BYTE(16#00), ?BYTE(16#B9)>>).
+%%% ECDHE PSK Cipher Suites RFC 5489
+
+%% TLS_ECDHE_PSK_WITH_RC4_128_SHA = {0xC0,0x33};
+-define(TLS_ECDHE_PSK_WITH_RC4_128_SHA, <<?BYTE(16#C0), ?BYTE(16#33)>>).
+
+%% TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = {0xC0,0x34};
+-define(TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#34)>>).
+
+%% TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = {0xC0,0x35};
+-define(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#35)>>).
+
+%% TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = {0xC0,0x36};
+-define(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#36)>>).
+
+%% TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = {0xC0,0x37};
+-define(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, <<?BYTE(16#C0), ?BYTE(16#37)>>).
+
+%% TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = {0xC0,0x38};
+-define(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384, <<?BYTE(16#C0), ?BYTE(16#38)>>).
+
+%% TLS_ECDHE_PSK_WITH_NULL_SHA256 = {0xC0,0x3A};
+-define(TLS_ECDHE_PSK_WITH_NULL_SHA256, <<?BYTE(16#C0), ?BYTE(16#3A)>>).
+
+%% TLS_ECDHE_PSK_WITH_NULL_SHA384 = {0xC0,0x3B};
+-define(TLS_ECDHE_PSK_WITH_NULL_SHA384, <<?BYTE(16#C0), ?BYTE(16#3B)>>).
+
+%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05
+
+%% TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 = {0xTBD; 0xTBD} {0xD0,0x01};
+-define(TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#D0), ?BYTE(16#01)>>).
+
+%% TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 = {0xTBD; 0xTBD} {0xD0,0x02};
+-define(TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#D0), ?BYTE(16#02)>>).
+
+%% TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256 = {0xTBD; 0xTBD} {0xD0,0x03};
+-define(TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256, <<?BYTE(16#D0), ?BYTE(16#03)>>).
+
+%% TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256 = {0xTBD; 0xTBD} {0xD0,0x05};
+-define(TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256, <<?BYTE(16#D0), ?BYTE(16#05)>>).
+
%%% SRP Cipher Suites RFC 5054
%% TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = { 0xC0,0x1A };
@@ -548,15 +601,98 @@
%% TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x32};
-define(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#32)>>).
-%%% Chacha20/Poly1305 Suites draft-agl-tls-chacha20poly1305-04
-%% TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x13}
--define(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#13)>>).
+%%% ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS) RFC7905
+
+%% TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xA8}
+-define(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#A8)>>).
+
+%% TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xA9}
+-define(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#A9)>>).
+
+%% TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAA}
+-define(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#AA)>>).
+
+%% TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAB}
+-define(TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#AB)>>).
+
+%% TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAC}
+-define(TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#AC)>>).
+
+%% TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAD}
+-define(TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#AD)>>).
+
+%% TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAE}
+-define(TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#AE)>>).
+
+
+
+%% RFC 6655 - TLS-1.2 cipher suites
+
+%% TLS_RSA_WITH_AES_128_CCM = {0xC0,0x9C}
+-define(TLS_RSA_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#9C)>>).
+
+%% TLS_RSA_WITH_AES_256_CCM = {0xC0,0x9D}
+-define(TLS_RSA_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#9D)>>).
+
+%% TLS_DHE_RSA_WITH_AES_256_CCM = {0xC0,0x9E}
+-define(TLS_DHE_RSA_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#9E)>>).
+
+%% TLS_DHE_RSA_WITH_AES_128_CCM = {0xC0,0x9F}
+-define(TLS_DHE_RSA_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#9F)>>).
+
+%% TLS_RSA_WITH_AES_256_CCM_8 = {0xC0,0x9A0}
+-define(TLS_RSA_WITH_AES_256_CCM_8, <<?BYTE(16#C0), ?BYTE(16#A0)>>).
+
+%% TLS_RSA_WITH_AES_128_CCM_8 = {0xC0,0xA1}
+-define(TLS_RSA_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#A1)>>).
+
+%% TLS_DHE_RSA_WITH_AES_128_CCM_8 = {0xC0,0xA2}
+-define(TLS_DHE_RSA_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#A2)>>).
+
+%% TLS_DHE_RSA_WITH_AES_256_CCM_8 = {0xC0,0xA3}
+-define(TLS_DHE_RSA_WITH_AES_256_CCM_8, <<?BYTE(16#C0), ?BYTE(16#A3)>>).
+
+%% TLS_PSK_WITH_AES_128_CCM = {0xC0,0xA4}
+-define(TLS_PSK_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#A4)>>).
+
+%% TLS_PSK_WITH_AES_256_CCM = {0xC0,0xA5)
+-define(TLS_PSK_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#A5)>>).
+
+%% TLS_DHE_PSK_WITH_AES_128_CCM = {0xC0,0xA6}
+-define(TLS_DHE_PSK_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#A6)>>).
+
+%% TLS_DHE_PSK_WITH_AES_256_CCM = {0xC0,0xA7}
+-define(TLS_DHE_PSK_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#A7)>>).
+
+%% TLS_PSK_WITH_AES_128_CCM_8 = {0xC0,0xA8}
+-define(TLS_PSK_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#A8)>>).
+
+%% TLS_PSK_WITH_AES_256_CCM_8 = {0xC0,0xA9)
+-define(TLS_PSK_WITH_AES_256_CCM_8, <<?BYTE(16#C0), ?BYTE(16#A9)>>).
+
+%% TLS_PSK_DHE_WITH_AES_128_CCM_8 = {0xC0,0xAA}
+-define(TLS_PSK_DHE_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AA)>>).
+
+%% TLS_PSK_DHE_WITH_AES_256_CCM_8 = << ?BYTE(0xC0,0xAB}
+-define(TLS_PSK_DHE_WITH_AES_256_CCM_8, <<?BYTE(16#C0),?BYTE(16#AB)>>).
+
+
+%%% TLS 1.3 cipher suites RFC8446
+
+%% TLS_AES_128_GCM_SHA256 = {0x13,0x01}
+-define(TLS_AES_128_GCM_SHA256, <<?BYTE(16#13), ?BYTE(16#01)>>).
+
+%% TLS_AES_256_GCM_SHA384 = {0x13,0x02}
+-define(TLS_AES_256_GCM_SHA384, <<?BYTE(16#13),?BYTE(16#02)>>).
+
+%% TLS_CHACHA20_POLY1305_SHA256 = {0x13,0x03}
+-define(TLS_CHACHA20_POLY1305_SHA256, <<?BYTE(16#13),?BYTE(16#03)>>).
-%% TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x14}
--define(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#14)>>).
+%% %% TLS_AES_128_CCM_SHA256 = {0x13,0x04}
+-define(TLS_AES_128_CCM_SHA256, <<?BYTE(16#13), ?BYTE(16#04)>>).
-%% TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x15}
--define(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#15)>>).
+%% %% TLS_AES_128_CCM_8_SHA256 = {0x13,0x05}
+-define(TLS_AES_128_CCM_8_SHA256, <<?BYTE(16#13),?BYTE(16#05)>>).
-endif. % -ifdef(ssl_cipher).
diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl
new file mode 100644
index 0000000000..bca1022b5f
--- /dev/null
+++ b/lib/ssl/src/ssl_cipher_format.erl
@@ -0,0 +1,1919 @@
+%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-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: Convert between diffrent cipher suite representations
+%%
+%%----------------------------------------------------------------------
+-module(ssl_cipher_format).
+
+-include("ssl_api.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-export_type([old_erl_cipher_suite/0, openssl_cipher_suite/0, cipher_suite/0]).
+
+-type internal_cipher() :: null | ssl:cipher().
+-type internal_hash() :: null | ssl:hash().
+-type internal_kex_algo() :: null | ssl:kex_algo().
+-type internal_erl_cipher_suite() :: #{key_exchange := internal_kex_algo(),
+ cipher := internal_cipher(),
+ mac := internal_hash() | aead,
+ prf := internal_hash() | default_prf %% Old cipher suites, version dependent
+ }.
+-type old_erl_cipher_suite() :: {ssl:kex_algo(), internal_cipher(), internal_hash()} % Pre TLS 1.2
+ %% TLS 1.2, internally PRE TLS 1.2 will use default_prf
+ | {ssl:kex_algo(), internal_cipher(), internal_hash(),
+ internal_hash() | default_prf}.
+-type cipher_suite() :: binary().
+-type openssl_cipher_suite() :: string().
+
+
+-export([suite_map_to_bin/1, %% Binary format
+ suite_bin_to_map/1, %% Erlang API format
+ suite_map_to_str/1, %% RFC string
+ suite_str_to_map/1,
+ suite_map_to_openssl_str/1, %% OpenSSL name
+ suite_openssl_str_to_map/1,
+ suite_legacy/1 %% Erlang legacy format
+ ]).
+
+%%--------------------------------------------------------------------
+-spec suite_map_to_str(internal_erl_cipher_suite()) -> string().
+%%
+%% Description: Return the string representation of a cipher suite.
+%%--------------------------------------------------------------------
+suite_map_to_str(#{key_exchange := null,
+ cipher := null,
+ mac := null,
+ prf := null}) ->
+ "TLS_EMPTY_RENEGOTIATION_INFO_SCSV";
+suite_map_to_str(#{key_exchange := any,
+ cipher := Cipher,
+ mac := aead,
+ prf := PRF}) ->
+ "TLS_" ++ string:to_upper(atom_to_list(Cipher)) ++
+ "_" ++ string:to_upper(atom_to_list(PRF));
+suite_map_to_str(#{key_exchange := Kex,
+ cipher := Cipher,
+ mac := aead,
+ prf := PRF}) ->
+ "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++
+ "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++
+ "_" ++ string:to_upper(atom_to_list(PRF));
+suite_map_to_str(#{key_exchange := Kex,
+ cipher := Cipher,
+ mac := Mac}) ->
+ "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++
+ "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++
+ "_" ++ string:to_upper(atom_to_list(Mac)).
+
+suite_str_to_map("TLS_EMPTY_RENEGOTIATION_INFO_SCSV") ->
+ #{key_exchange => null,
+ cipher => null,
+ mac => null,
+ prf => null};
+suite_str_to_map(SuiteStr)->
+ Str0 = string:prefix(SuiteStr, "TLS_"),
+ 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.
+
+suite_map_to_openssl_str(#{key_exchange := any,
+ mac := aead} = Suite) ->
+ %% TLS 1.3 OpenSSL finally use RFC names
+ suite_map_to_str(Suite);
+suite_map_to_openssl_str(#{key_exchange := null} = Suite) ->
+ %% TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+ 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" ->
+ 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)));
+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_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}) ->
+ openssl_suite_start(string:to_upper(atom_to_list(Kex)))
+ ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
+ "-" ++ string:to_upper(atom_to_list(Mac)).
+
+
+suite_openssl_str_to_map("TLS_" ++ _ = SuiteStr) ->
+ suite_str_to_map(SuiteStr);
+suite_openssl_str_to_map("DES-CBC-SHA") ->
+ suite_str_to_map("TLS_RSA_WITH_DES_CBC_SHA");
+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("DHE-RSA", Rest);
+suite_openssl_str_to_map("DHE-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("EDH-DSS-" ++ Rest) ->
+ suite_openssl_str_to_map("DHE-DSS", Rest);
+suite_openssl_str_to_map("DES" ++ _ = Rest) ->
+ suite_openssl_str_to_map("RSA", Rest);
+suite_openssl_str_to_map("AES" ++ _ = Rest) ->
+ suite_openssl_str_to_map("RSA", Rest);
+suite_openssl_str_to_map("RC4" ++ _ = Rest) ->
+ suite_openssl_str_to_map("RSA", Rest);
+suite_openssl_str_to_map("ECDH-RSA-" ++ Rest) ->
+ suite_openssl_str_to_map("ECDH-RSA", Rest);
+suite_openssl_str_to_map("ECDH-ECDSA-" ++ Rest) ->
+ suite_openssl_str_to_map("ECDH-ECDSA", Rest);
+suite_openssl_str_to_map("ECDHE-RSA-" ++ Rest) ->
+ suite_openssl_str_to_map("ECDHE-RSA", Rest);
+suite_openssl_str_to_map("ECDHE-ECDSA-" ++ Rest) ->
+ suite_openssl_str_to_map("ECDHE-ECDSA", Rest);
+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) ->
+ suite_openssl_str_to_map("PSK", Rest);
+suite_openssl_str_to_map("SRP-RSA-" ++ Rest) ->
+ suite_openssl_str_to_map("SRP-RSA", Rest);
+suite_openssl_str_to_map("SRP-DSS-" ++ Rest) ->
+ suite_openssl_str_to_map("SRP-DSS", Rest);
+suite_openssl_str_to_map("SRP-" ++ Rest) ->
+ suite_openssl_str_to_map("SRP", Rest).
+
+%%--------------------------------------------------------------------
+-spec suite_bin_to_map(cipher_suite()) -> internal_erl_cipher_suite().
+%%
+%% Description: Return erlang cipher suite definition.
+%% Note: Currently not supported suites are commented away.
+%% They should be supported or removed in the future.
+%%-------------------------------------------------------------------
+%% TLS v1.1 suites
+suite_bin_to_map(?TLS_NULL_WITH_NULL_NULL) ->
+ #{key_exchange => null,
+ cipher => null,
+ mac => null,
+ prf => null};
+%% RFC 5746 - Not a real cipher suite used to signal empty "renegotiation_info" extension
+%% to avoid handshake failure from old servers that do not ignore
+%% hello extension data as they should.
+suite_bin_to_map(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) ->
+ #{key_exchange => null,
+ cipher => null,
+ mac => null,
+ prf => null};
+suite_bin_to_map(?TLS_RSA_WITH_RC4_128_MD5) ->
+ #{key_exchange => rsa,
+ cipher => rc4_128,
+ mac => md5,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_WITH_RC4_128_SHA) ->
+ #{key_exchange => rsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_WITH_DES_CBC_SHA) ->
+ #{key_exchange => rsa,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_DES_CBC_SHA) ->
+ #{key_exchange => dhe_dss,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => dhe_dss,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
+ #{key_exchange => dhe_rsa,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => dhe_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+%%% TSL V1.1 AES suites
+suite_bin_to_map(?TLS_RSA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => dhe_dss,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => dhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => dhe_dss,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => dhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+%% TLS v1.2 suites
+%% suite_bin_to_map(?TLS_RSA_WITH_NULL_SHA) ->
+%% {rsa, null, sha, default_prf};
+suite_bin_to_map(?TLS_RSA_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_WITH_AES_256_CBC_SHA256) ->
+ #{key_exchange => rsa,
+ cipher => aes_256_cbc,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => dhe_dss,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => dhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) ->
+ #{key_exchange => dhe_dss,
+ cipher => aes_256_cbc,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) ->
+ #{key_exchange => dhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha256,
+ prf => default_prf};
+%% not defined YET:
+%% TLS_DH_DSS_WITH_AES_128_CBC_SHA256 DH_DSS AES_128_CBC SHA256
+%% TLS_DH_RSA_WITH_AES_128_CBC_SHA256 DH_RSA AES_128_CBC SHA256
+%% TLS_DH_DSS_WITH_AES_256_CBC_SHA256 DH_DSS AES_256_CBC SHA256
+%% TLS_DH_RSA_WITH_AES_256_CBC_SHA256 DH_RSA AES_256_CBC SHA256
+%%% DH-ANON deprecated by TLS spec and not available
+%%% by default, but good for testing purposes.
+suite_bin_to_map(?TLS_DH_anon_WITH_RC4_128_MD5) ->
+ #{key_exchange => dh_anon,
+ cipher => rc4_128,
+ mac => md5,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DH_anon_WITH_DES_CBC_SHA) ->
+ #{key_exchange => dh_anon,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => dh_anon,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DH_anon_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => dh_anon,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DH_anon_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => dh_anon,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+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};
+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};
+%%% PSK Cipher Suites RFC 4279
+suite_bin_to_map(?TLS_PSK_WITH_RC4_128_SHA) ->
+ #{key_exchange => psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_PSK_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_PSK_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_RC4_128_SHA) ->
+ #{key_exchange => dhe_psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => dhe_psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => dhe_psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => dhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_RC4_128_SHA) ->
+ #{key_exchange => rsa_psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => rsa_psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => rsa_psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => rsa_psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+%%% PSK NULL Cipher Suites RFC 4785
+suite_bin_to_map(?TLS_PSK_WITH_NULL_SHA) ->
+ #{key_exchange => psk,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA) ->
+ #{key_exchange => dhe_psk,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA) ->
+ #{key_exchange => rsa_psk,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+%%% TLS 1.2 PSK Cipher Suites RFC 5487
+suite_bin_to_map(?TLS_PSK_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => psk,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_PSK_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => psk,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => dhe_psk,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => dhe_psk,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => rsa_psk,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => rsa_psk,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_PSK_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => psk,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_PSK_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+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};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => dhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => rsa_psk,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => rsa_psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_PSK_WITH_NULL_SHA256) ->
+ #{key_exchange => psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_PSK_WITH_NULL_SHA384) ->
+ #{key_exchange => psk,
+ cipher => null,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA256) ->
+ #{key_exchange => dhe_psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA384) ->
+ #{key_exchange => dhe_psk,
+ cipher => null,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA256) ->
+ #{key_exchange => rsa_psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA384) ->
+ #{key_exchange => rsa_psk,
+ cipher => null,
+ mac => sha384,
+ prf => sha384};
+%%% ECDHE PSK Cipher Suites RFC 5489
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_RC4_128_SHA) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+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};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA256) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => null, mac => sha384,
+ prf => sha384};
+%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) ->
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_128_ccm,
+ mac => null,
+ 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,
+ prf => sha256};
+%%% SRP Cipher Suites RFC 5054
+suite_bin_to_map(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => srp_anon,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => srp_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => srp_dss,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => srp_anon,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => srp_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => srp_dss,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => srp_anon,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => srp_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => srp_dss,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+%% RFC 4492 EC TLS suites
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_NULL_SHA) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_NULL_SHA) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_NULL_SHA) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_RC4_128_SHA) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_NULL_SHA) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_anon_WITH_NULL_SHA) ->
+ #{key_exchange => ecdh_anon,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_anon_WITH_RC4_128_SHA) ->
+ #{key_exchange => ecdh_anon,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA) ->
+ #{key_exchange => ecdh_anon,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_anon_WITH_AES_128_CBC_SHA) ->
+ #{key_exchange => ecdh_anon,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDH_anon_WITH_AES_256_CBC_SHA) ->
+ #{key_exchange => ecdh_anon,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
+%% RFC 5289 EC TLS suites
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
+%% RFC 5288 AES-GCM Cipher Suites
+suite_bin_to_map(?TLS_RSA_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => rsa,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_RSA_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => rsa,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => dhe_rsa,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => dhe_rsa,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => dh_rsa,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => dh_rsa,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => dhe_dss,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => dhe_dss,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => dh_dss,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
+suite_bin_to_map(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => dh_dss,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_DH_anon_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => dh_anon,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DH_anon_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => dh_anon,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+%% RFC 5289 ECC AES-GCM Cipher Suites
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) ->
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM) ->
+ #{key_exchange => psk,
+ cipher => aes_128_ccm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM) ->
+ #{key_exchange => psk,
+ cipher => aes_256_ccm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CCM) ->
+ #{key_exchange => dhe_psk,
+ cipher => aes_128_ccm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CCM) ->
+ #{key_exchange => dhe_psk,
+ cipher => aes_256_ccm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM_8) ->
+ #{key_exchange => psk,
+ cipher => aes_128_ccm_8,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM_8) ->
+ #{key_exchange => psk,
+ cipher => aes_256_ccm_8,
+ mac => aead,
+ prf => sha256};
+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};
+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};
+suite_bin_to_map(#{key_exchange := psk_dhe,
+ cipher := aes_256_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_PSK_DHE_WITH_AES_256_CCM_8;
+
+% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites
+suite_bin_to_map(?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
+ #{key_exchange => ecdhe_rsa,
+ cipher => chacha20_poly1305,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => chacha20_poly1305,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
+ #{key_exchange => dhe_rsa,
+ cipher => chacha20_poly1305,
+ mac => aead,
+ prf => sha256};
+%% TLS 1.3 Cipher Suites RFC8446
+suite_bin_to_map(?TLS_AES_128_GCM_SHA256) ->
+ #{key_exchange => any,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_AES_256_GCM_SHA384) ->
+ #{key_exchange => any,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_bin_to_map(?TLS_CHACHA20_POLY1305_SHA256) ->
+ #{key_exchange => any,
+ cipher => chacha20_poly1305,
+ mac => aead,
+ prf => sha256};
+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}.
+
+%%--------------------------------------------------------------------
+-spec suite_legacy(cipher_suite() | internal_erl_cipher_suite()) -> old_erl_cipher_suite().
+%%
+%% Description: Return erlang cipher suite definition. Filters last value
+%% for now (compatibility reasons).
+%%--------------------------------------------------------------------
+suite_legacy(Bin) when is_binary(Bin) ->
+ suite_legacy(suite_bin_to_map(Bin));
+suite_legacy(#{key_exchange := KeyExchange, cipher := Cipher,
+ mac := Hash, prf := Prf}) ->
+ case Prf of
+ default_prf ->
+ {KeyExchange, Cipher, Hash};
+ _ ->
+ {KeyExchange, Cipher, Hash, Prf}
+ end.
+
+%%--------------------------------------------------------------------
+-spec suite_map_to_bin(internal_erl_cipher_suite()) -> cipher_suite().
+%%
+%% Description: Return TLS cipher suite definition.
+%%--------------------------------------------------------------------
+%% TLS v1.1 suites
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := rc4_128,
+ mac := md5}) ->
+ ?TLS_RSA_WITH_RC4_128_MD5;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_RSA_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := des_cbc,
+ mac := sha}) ->
+ ?TLS_RSA_WITH_DES_CBC_SHA;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher :='3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_RSA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher:= des_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_DSS_WITH_DES_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher:= '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher:= des_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_RSA_WITH_DES_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher:= '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher:= rc4_128,
+ mac := md5}) ->
+ ?TLS_DH_anon_WITH_RC4_128_MD5;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher:= des_cbc,
+ mac := sha}) ->
+ ?TLS_DH_anon_WITH_DES_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher:= '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
+%%% TSL V1.1 AES suites
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_RSA_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_DH_anon_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_RSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_DH_anon_WITH_AES_256_CBC_SHA;
+%% TLS v1.2 suites
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_RSA_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
+ ?TLS_RSA_WITH_AES_256_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_DH_anon_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
+ ?TLS_DH_anon_WITH_AES_256_CBC_SHA256;
+%%% PSK Cipher Suites RFC 4279
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_PSK_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_PSK_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_PSK_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_PSK_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_DHE_PSK_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_RSA_PSK_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA;
+%%% PSK NULL Cipher Suites RFC 4785
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_PSK_WITH_NULL_SHA;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_DHE_PSK_WITH_NULL_SHA;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_RSA_PSK_WITH_NULL_SHA;
+%%% TLS 1.2 PSK Cipher Suites RFC 5487
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_PSK_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_PSK_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_PSK_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
+ ?TLS_PSK_WITH_AES_256_CBC_SHA384;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
+ ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
+ ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := null,
+ mac := sha256}) ->
+ ?TLS_PSK_WITH_NULL_SHA256;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := null,
+ mac := sha384}) ->
+ ?TLS_PSK_WITH_NULL_SHA384;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := null,
+ mac := sha256}) ->
+ ?TLS_DHE_PSK_WITH_NULL_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := null,
+ mac := sha384}) ->
+ ?TLS_DHE_PSK_WITH_NULL_SHA384;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := null,
+ mac := sha256}) ->
+ ?TLS_RSA_PSK_WITH_NULL_SHA256;
+suite_map_to_bin(#{key_exchange := rsa_psk,
+ cipher := null,
+ mac := sha384}) ->
+ ?TLS_RSA_PSK_WITH_NULL_SHA384;
+%%% ECDHE PSK Cipher Suites RFC 5489
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_ECDHE_PSK_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher :='3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
+ ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
+ ?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := null,
+ mac := sha256}) ->
+ ?TLS_ECDHE_PSK_WITH_NULL_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := null,
+ mac := sha384}) ->
+ ?TLS_ECDHE_PSK_WITH_NULL_SHA384;
+%%% 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,
+ prf := sha256}) ->
+ ?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_psk,
+ cipher := aes_256_gcm,
+ mac := null,
+ 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,
+ 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,
+ prf := sha256}) ->
+ ?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256;
+%%% SRP Cipher Suites RFC 5054
+suite_map_to_bin(#{key_exchange := srp_anon,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_rsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_dss,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_anon,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_dss,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_anon,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := srp_dss,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA;
+%%% RFC 4492 EC TLS suites
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_ECDH_ECDSA_WITH_NULL_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_ECDHE_ECDSA_WITH_NULL_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA;
+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 := ecdh_rsa,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_ECDH_RSA_WITH_NULL_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_ECDH_RSA_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := '3des_ede_cbc', mac := sha}) ->
+ ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_ECDHE_RSA_WITH_NULL_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_ECDHE_RSA_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_anon,
+ cipher := null,
+ mac := sha}) ->
+ ?TLS_ECDH_anon_WITH_NULL_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_anon,
+ cipher := rc4_128,
+ mac := sha}) ->
+ ?TLS_ECDH_anon_WITH_RC4_128_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_anon,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
+ ?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_anon,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
+ ?TLS_ECDH_anon_WITH_AES_128_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdh_anon,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
+ ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA;
+%%% RFC 5289 EC TLS suites
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_cbc,
+ mac:= sha256,
+ prf := sha256}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_128_cbc,
+ mac := sha256,
+ prf := sha256}) ->
+ ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
+ ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha256,
+ prf := sha256}) ->
+ ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
+ ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := aes_128_cbc,
+ mac := sha256,
+ prf := sha256}) ->
+ ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
+ ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384;
+%% RFC 5288 AES-GCM Cipher Suites
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_RSA_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_RSA_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := dh_rsa,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := dh_rsa,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_dss,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := dh_dss,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := dh_dss,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DH_anon_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := dh_anon,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_DH_anon_WITH_AES_256_GCM_SHA384;
+%% RFC 5289 ECC AES-GCM Cipher Suites
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := ecdh_rsa,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384;
+%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites
+suite_map_to_bin(#{key_exchange := ecdhe_rsa,
+ cipher := chacha20_poly1305,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := chacha20_poly1305,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := chacha20_poly1305,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+
+%% RFC 6655 - TLS-1.2 cipher suites
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_128_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_PSK_WITH_AES_128_CCM;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_256_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_PSK_WITH_AES_256_CCM;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_128_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_PSK_WITH_AES_128_CCM;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_256_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_PSK_WITH_AES_256_CCM;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_128_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_RSA_WITH_AES_128_CCM;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_256_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_RSA_WITH_AES_256_CCM;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_128_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_RSA_WITH_AES_128_CCM;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_256_ccm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_DHE_RSA_WITH_AES_256_CCM;
+
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_128_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_PSK_WITH_AES_128_CCM_8;
+suite_map_to_bin(#{key_exchange := psk,
+ cipher := aes_256_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_PSK_WITH_AES_256_CCM_8;
+suite_map_to_bin(#{key_exchange := dhe_psk,
+ cipher := aes_128_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?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}) ->
+ ?TLS_PSK_DHE_WITH_AES_256_CCM_8;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_128_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_RSA_WITH_AES_128_CCM_8;
+suite_map_to_bin(#{key_exchange := rsa,
+ cipher := aes_256_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_RSA_WITH_AES_256_CCM_8;
+suite_map_to_bin(#{key_exchange := dhe_rsa,
+ cipher := aes_128_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?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}) ->
+ ?TLS_DHE_RSA_WITH_AES_256_CCM_8;
+
+%% TLS 1.3 Cipher Suites RFC8446
+suite_map_to_bin(#{key_exchange := any,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_AES_128_GCM_SHA256;
+suite_map_to_bin(#{key_exchange := any,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_AES_256_GCM_SHA384;
+suite_map_to_bin(#{key_exchange := any,
+ cipher := chacha20_poly1305,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_CHACHA20_POLY1305_SHA256;
+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_1_3_suite_str_to_map(CipherStr) ->
+ {Cipher, Mac, Prf} = cipher_str_to_algs(any, CipherStr, ""),
+ #{key_exchange => any,
+ mac => Mac,
+ cipher => Cipher,
+ prf => Prf
+ }.
+
+pre_tls_1_3_suite_str_to_map(KexStr, Rest) ->
+ Kex = algo_str_to_atom(KexStr),
+ [CipherStr, AlgStr] = string:split(Rest, "_", trailing),
+ {Cipher, Mac, Prf} = cipher_str_to_algs(Kex, CipherStr, AlgStr),
+ #{key_exchange => Kex,
+ mac => Mac,
+ 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
+ [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, HashStr) -> %% PRE TLS 1.3
+ Hash = algo_str_to_atom(HashStr),
+ Cipher = algo_str_to_atom(CipherStr),
+ case is_aead_cipher(CipherStr) of
+ true ->
+ {Cipher, aead, Hash};
+ false ->
+ {Cipher, Hash, default_prf(Kex, Hash)}
+ end.
+
+default_prf(_, md5) ->
+ default_prf;
+default_prf(_, sha) ->
+ default_prf;
+default_prf(ecdhe_ecdsa, sha256) ->
+ sha256;
+default_prf(ecdhe_rsa, sha256) ->
+ sha256;
+default_prf(dhe_rsa, sha256) ->
+ default_prf;
+default_prf(dhe_dss, sha256) ->
+ default_prf;
+default_prf(rsa, sha256) ->
+ default_prf;
+default_prf(rsa_psk, sha256) ->
+ default_prf;
+default_prf(_, Hash) ->
+ Hash.
+
+%% PRE TLS 1.3
+is_aead_cipher("CHACHA20_POLY1305") ->
+ true;
+is_aead_cipher(CipherStr) ->
+ [_, Rest] = string:split(CipherStr, "_", trailing),
+ (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "8").
+
+openssl_is_aead_cipher("CHACHA20-POLY1305") ->
+ true;
+openssl_is_aead_cipher(CipherStr) ->
+ case string:split(CipherStr, "-", trailing) of
+ [_, Rest] ->
+ (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "8");
+ [_] ->
+ false
+ end.
+
+algo_str_to_atom("SRP_SHA_DSS") ->
+ srp_dss;
+algo_str_to_atom(AlgoStr) ->
+ erlang:list_to_existing_atom(string:to_lower(AlgoStr)).
+
+openssl_cipher_name(_, "3DES_EDE_CBC" ++ _) ->
+ "DES-CBC3";
+openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == rsa;
+ Kex == dhe_rsa;
+ Kex == ecdhe_rsa;
+ Kex == ecdhe_ecdsa ->
+ openssl_name_concat(CipherStr);
+openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == rsa;
+ Kex == dhe_rsa;
+ Kex == ecdhe_rsa;
+ Kex == ecdhe_ecdsa ->
+ openssl_name_concat(CipherStr);
+openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp;
+ Kex == srp_rsa ->
+ lists:append(string:replace(CipherStr, "_", "-", all));
+openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp;
+ 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" ++ _ = CipherStr) ->
+ openssl_name_concat(CipherStr) ++ "-GCM";
+openssl_cipher_name(_, "AES_256_GCM" ++ _ = CipherStr) ->
+ openssl_name_concat(CipherStr) ++ "-GCM";
+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
+ "" ->
+ "";
+ Name ->
+ Name ++ "-"
+ end.
+
+openssl_kex_name("RSA") ->
+ "";
+openssl_kex_name(Kex) ->
+ lists:append(string:replace(Kex, "_", "-", all)).
+
+kex_name_from_openssl(Kex) ->
+ lists:append(string:replace(Kex, "-", "_", all)).
+
+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("DES-CBC") ->
+ "DES_CBC";
+cipher_name_from_openssl("DES-CBC3") ->
+ "3DES_EDE_CBC";
+cipher_name_from_openssl("RC4") ->
+ "RC4_128";
+cipher_name_from_openssl(Str) ->
+ Str.
+
+openssl_name_concat(Str0) ->
+ [Str, _] = string:split(Str0, "_", trailing),
+ [Part1, Part2] = string:split(Str, "_", trailing),
+ Part1 ++ Part2.
+
+
+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),
+ #{key_exchange => Kex,
+ mac => Mac,
+ cipher => Cipher,
+ prf => Prf
+ }.
+
+%% 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),
+ {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
+ true ->
+ {Cipher, aead, Hash};
+ false ->
+ {Cipher, Hash, default_prf(Kex, Hash)}
+ end.
+
+
+
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index e4611995ec..1e6dab9276 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% 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.
@@ -91,7 +91,15 @@ init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server
end;
init_certificates(Cert, Config, _, _) ->
{ok, Config#{own_certificate => Cert}}.
-
+init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa;
+ Alg == rsa;
+ Alg == dss ->
+ case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of
+ true ->
+ Key;
+ false ->
+ throw({key, {invalid_key_id, Key}})
+ end;
init_private_key(_, undefined, <<>>, _Password, _Client) ->
undefined;
init_private_key(DbHandle, undefined, KeyFile, Password, _) ->
@@ -124,7 +132,13 @@ private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
#'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'},
privateKey = Key}) ->
public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key));
-
+private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
+ #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-ecPublicKey',
+ parameters = {asn1_OPENTYPE, Parameters}},
+ privateKey = Key}) ->
+ ECKey = public_key:der_decode('ECPrivateKey', iolist_to_binary(Key)),
+ ECParameters = public_key:der_decode('EcpkParameters', Parameters),
+ ECKey#'ECPrivateKey'{parameters = ECParameters};
private_key(Key) ->
Key.
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index ad220ae9de..2483509228 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. 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.
@@ -35,43 +35,50 @@
-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, ssl_accept/7, handshake/2, handshake/3,
- socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]).
+
+-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, session_info/1,
+ new_user/2, get_opts/2, set_opts/2,
peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5,
- connection_information/1, handle_common_event/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]).
+
+%% 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, hello/4, abbreviated/4, certify/4, cipher/4, connection/4, downgrade/4]).
+-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]).
-%%
--export([handle_info/3, handle_call/5, handle_session/7, ssl_config/3,
- prepare_connection/2, hibernate_after/3]).
-
-%% Alert and close handling
--export([handle_own_alert/4,handle_alert/3,
- handle_normal_shutdown/3
- ]).
-
-%% Data handling
--export([write_application_data/3, read_application_data/2]).
+%% Erlang Distribution export
+-export([dist_handshake_complete/2]).
%%====================================================================
-%% Internal application API
-%%====================================================================
+%% Setup
+%%====================================================================
%%--------------------------------------------------------------------
-spec connect(tls_connection | dtls_connection,
- host(), inet:port_number(),
+ ssl:host(), inet:port_number(),
port() | {tuple(), port()}, %% TLS | DTLS
{#ssl_options{}, #socket_options{},
%% Tracker only needed on server side
@@ -89,7 +96,7 @@ connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
{error, ssl_not_started}
end.
%%--------------------------------------------------------------------
--spec ssl_accept(tls_connection | dtls_connection,
+-spec handshake(tls_connection | dtls_connection,
inet:port_number(), port(),
{#ssl_options{}, #socket_options{}, undefined | pid()},
pid(), tuple(), timeout()) ->
@@ -98,7 +105,7 @@ connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
-ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
+handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
CbInfo, Timeout)
catch
@@ -107,34 +114,62 @@ ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
end.
%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}.
+-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {ok, #sslsocket{}, map()}| {error, reason()}.
%%
%% Description: Starts ssl handshake.
%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = Pid}, Timeout) ->
+handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
case call(Pid, {start, Timeout}) of
connected ->
- ok;
+ {ok, Socket};
+ {ok, Ext} ->
+ {ok, Socket, no_records(Ext)};
Error ->
Error
end.
%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, {#ssl_options{},#socket_options{}},
- timeout()) -> ok | {error, reason()}.
+-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}, SslOptions, Timeout) ->
+handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
case call(Pid, {start, SslOptions, Timeout}) of
connected ->
- ok;
+ {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()) ->
+-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Set the ssl process to own the accept socket
@@ -143,27 +178,33 @@ socket_control(Connection, Socket, Pid, Transport) ->
socket_control(Connection, Socket, Pid, Transport, undefined).
%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), pid(), atom(), pid()| undefined) ->
+-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), pid()| atom()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%--------------------------------------------------------------------
-socket_control(Connection, Socket, Pid, Transport, udp_listner) ->
- %% dtls listner process must have the socket control
- {ok, Connection:socket(Pid, Transport, Socket, Connection, undefined)};
+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, Transport, ListenTracker) ->
+socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, ListenTracker) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pid, Transport, Socket, Connection, ListenTracker)};
+ {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)};
{error, Reason} ->
{error, Reason}
end;
-socket_control(dtls_connection = Connection, {_, Socket}, Pid, Transport, ListenTracker) ->
+socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, ListenTracker) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pid, Transport, Socket, Connection, ListenTracker)};
+ {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)};
{error, Reason} ->
{error, Reason}
end.
+
+
+%%====================================================================
+%% User events
+%%====================================================================
+
%%--------------------------------------------------------------------
-spec send(pid(), iodata()) -> ok | {error, reason()}.
%%
@@ -171,9 +212,9 @@ socket_control(dtls_connection = Connection, {_, Socket}, Pid, Transport, Listen
%%--------------------------------------------------------------------
send(Pid, Data) ->
call(Pid, {application_data,
- %% iolist_to_binary should really
- %% be called iodata_to_binary()
- erlang:iolist_to_binary(Data)}).
+ %% iolist_to_iovec should really
+ %% be called iodata_to_iovec()
+ erlang:iolist_to_iovec(Data)}).
%%--------------------------------------------------------------------
-spec recv(pid(), integer(), timeout()) ->
@@ -185,12 +226,12 @@ recv(Pid, Length, Timeout) ->
call(Pid, {recv, Length, Timeout}).
%%--------------------------------------------------------------------
--spec connection_information(pid()) -> {ok, list()} | {error, reason()}.
+-spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}.
%%
%% Description: Get the SNI hostname
%%--------------------------------------------------------------------
-connection_information(Pid) when is_pid(Pid) ->
- call(Pid, connection_information).
+connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) ->
+ call(Pid, {connection_information, IncludeSecrityInfo}).
%%--------------------------------------------------------------------
-spec close(pid(), {close, Timeout::integer() |
@@ -247,14 +288,6 @@ set_opts(ConnectionPid, Options) ->
call(ConnectionPid, {set_opts, Options}).
%%--------------------------------------------------------------------
--spec session_info(pid()) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Returns info about the ssl session
-%%--------------------------------------------------------------------
-session_info(ConnectionPid) ->
- call(ConnectionPid, session_info).
-
-%%--------------------------------------------------------------------
-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
%%
%% Description: Returns the peer cert
@@ -271,8 +304,20 @@ 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()) ->
+ [binary() | ssl:prf_random()], non_neg_integer()) ->
{ok, binary()} | {error, reason()} | {'EXIT', term()}.
%%
%% Description: use a ssl sessions TLS PRF to generate key material
@@ -280,6 +325,413 @@ renegotiation(ConnectionPid) ->
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 = SslOpts} = 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(SslOpts#ssl_options.log_level, 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,
+ tracker = Tracker},
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Tracker,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,
+ tracker = Tracker},
+ 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, Tracker, 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,
+ tracker = Tracker,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ ssl_options = SslOpts,
+ 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(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(),
+ StateName, Alert),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Tracker, 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} = Alert,
+ StateName, State) ->
+ 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 = SslOpts} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(SslOpts#ssl_options.log_level, 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 = SslOpts
+ } = State0) ->
+ log_alert(SslOpts#ssl_options.log_level, 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 = SslOpts
+ } = State0) ->
+ log_alert(SslOpts#ssl_options.log_level, 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 = SslOpts} = State) ->
+ log_alert(SslOpts#ssl_options.log_level, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = opposite_role(Role)}),
+ Connection:next_event(StateName, no_record, State).
+
+%%====================================================================
+%% Data handling
+%%====================================================================
+passive_receive(State0 = #state{user_data_buffer = {_,BufferSize,_}}, StateName, Connection, StartTimerAction) ->
+ case BufferSize of
+ 0 ->
+ Connection:next_event(StateName, no_record, State0, StartTimerAction);
+ _ ->
+ case read_application_data(<<>>, State0) 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,
+ tracker = Tracker},
+ 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, Tracker, 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,
+ tracker = Tracker},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ SocketOpts =
+ deliver_app_data(
+ Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Tracker, 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{}) ->
@@ -289,26 +741,27 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression},
Version, NewId, ConnectionStates, ProtoExt, Protocol0,
#state{session = #session{session_id = OldId},
- negotiated_version = ReqVersion,
- negotiated_protocol = CurrentProtocol} = State0) ->
- {KeyAlgorithm, _, _, _} =
- ssl_cipher:suite_definition(CipherSuite),
+ 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{key_algorithm = KeyAlgorithm,
- negotiated_version = Version,
- connection_states = ConnectionStates,
- premaster_secret = PremasterSecret,
- expecting_next_protocol_negotiation = ExpectNPN,
- negotiated_protocol = Protocol},
+ 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 ->
@@ -322,7 +775,9 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
%%--------------------------------------------------------------------
-spec ssl_config(#ssl_options{}, client | server, #state{}) -> #state{}.
%%--------------------------------------------------------------------
-ssl_config(Opts, Role, 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,
@@ -332,23 +787,24 @@ ssl_config(Opts, Role, State) ->
dh_params := DHParams,
own_certificate := OwnCert}} =
ssl_config:init(Opts, Role),
- Handshake = ssl_handshake:init_handshake_history(),
TimeStamp = erlang:monotonic_time(),
- Session = State#state.session,
- State#state{tls_handshake_history = Handshake,
- session = Session#session{own_certificate = OwnCert,
- time_stamp = TimeStamp},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
- private_key = Key,
- diffie_hellman_params = DHParams,
- ssl_options = Opts}.
+ 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 state functions
+%% gen_statem general state functions with connection cb argument
%%====================================================================
%%--------------------------------------------------------------------
-spec init(gen_statem:event_type(),
@@ -358,40 +814,68 @@ ssl_config(Opts, Role, State) ->
%%--------------------------------------------------------------------
init({call, From}, {start, Timeout}, State0, Connection) ->
- Timer = start_or_recv_cancel_timer(Timeout, From),
- {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From,
- timer = Timer}),
- Connection:next_event(hello, Record, State);
+ 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{role = Role, ssl_options = OrigSSLOptions,
+ #state{static_env = #static_env{role = Role},
+ ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0, Connection) ->
try
SslOpts = ssl:handle_options(Opts, OrigSSLOptions),
State = ssl_config(SslOpts, Role, State0),
init({call, From}, {start, Timeout},
- State#state{ssl_options = SslOpts, socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
+ State#state{ssl_options = SslOpts,
+ socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
catch throw:Error ->
- {stop_and_reply, normal, {reply, From, {error, Error}}}
+ {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
end;
-init({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, init, State, Connection);
+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, hello, 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, hello, State);
+ handle_info(Msg, ?FUNCTION_NAME, State);
hello(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, hello, 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, Options0#ssl_options{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(),
@@ -400,70 +884,69 @@ hello(Type, Msg, State, Connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
abbreviated({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, abbreviated, State, Connection);
-
+ handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{role = server,
- negotiated_version = Version,
- expecting_finished = true,
- tls_handshake_history = Handshake,
+ #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, Handshake) of
+ MasterSecret, Hist) of
verified ->
ConnectionStates =
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
{Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates,
- expecting_finished = false}, Connection),
- Connection:next_event(connection, Record, State);
+ 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, abbreviated, State0)
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
-
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{role = client, tls_handshake_history = Handshake0,
+ #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},
- negotiated_version = Version,
connection_states = ConnectionStates0} = State0, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
get_pending_prf(ConnectionStates0, write),
- MasterSecret, Handshake0) of
+ MasterSecret, Hist0) of
verified ->
ConnectionStates1 =
ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
- {State1, Actions} =
+ {#state{handshake_env = HsEnv} = State1, Actions} =
finalize_handshake(State0#state{connection_states = ConnectionStates1},
- abbreviated, Connection),
- {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection),
- Connection:next_event(connection, Record, State, Actions);
+ ?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, abbreviated, State0)
+ 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{role = server, expecting_next_protocol_negotiation = true} = State0,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State,
Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
- Connection:next_event(abbreviated, Record,
- State#state{expecting_next_protocol_negotiation = false});
+ 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} =
- State0, Connection) ->
+ #change_cipher_spec{type = <<1>>},
+ #state{connection_states = ConnectionStates0,
+ handshake_env = HsEnv} = State, Connection) ->
ConnectionStates1 =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read),
- {Record, State} = Connection:next_record(State0#state{connection_states =
- ConnectionStates1}),
- Connection:next_event(abbreviated, Record, State#state{expecting_finished = true});
+ 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, abbreviated, State);
+ handle_info(Msg, ?FUNCTION_NAME, State);
abbreviated(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, abbreviated, State, Connection).
+ handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(),
@@ -473,166 +956,189 @@ abbreviated(Type, Msg, State, Connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
certify({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, certify, State, Connection);
+ handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
certify(info, Msg, State, _) ->
- handle_info(Msg, certify, State);
+ handle_info(Msg, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
- #state{role = server, negotiated_version = Version,
+ #state{static_env = #static_env{role = server},
+ connection_env = #connection_env{negotiated_version = Version},
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = true}} =
State, _) ->
Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
- handle_own_alert(Alert, Version, certify, State);
-
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
- #state{role = server,
+ #state{static_env = #static_env{role = server},
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = false}} =
State0, Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{client_certificate_requested = false}),
- Connection:next_event(certify, Record, State);
-
+ Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
certify(internal, #certificate{},
- #state{role = server,
- negotiated_version = Version,
+ #state{static_env = #static_env{role = server},
+ connection_env = #connection_env{negotiated_version = Version},
ssl_options = #ssl_options{verify = verify_none}} =
State, _) ->
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
- handle_own_alert(Alert, Version, certify, State);
-
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{} = Cert,
- #state{negotiated_version = Version,
- role = Role,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbInfo,
+ #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#ssl_options.depth,
- Opts#ssl_options.verify,
- Opts#ssl_options.verify_fun,
- Opts#ssl_options.partial_chain,
- Opts#ssl_options.crl_check,
- CRLDbInfo,
- Role) of
+ 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, certify, State)
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
-
certify(internal, #server_key_exchange{exchange_keys = Keys},
- #state{role = client, negotiated_version = Version,
- key_algorithm = Alg,
- public_key_info = PubKeyInfo,
+ #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 Alg == dhe_dss; Alg == dhe_rsa;
- Alg == ecdhe_rsa; Alg == ecdhe_ecdsa;
- Alg == dh_anon; Alg == ecdh_anon;
- Alg == psk; Alg == dhe_psk; Alg == rsa_psk;
- Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
-
- Params = ssl_handshake:decode_server_key(Keys, Alg, ssl:tls_version(Version)),
+ 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, Alg, PubKeyInfo, ssl:tls_version(Version)),
+ HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)),
- case is_anonymous(Alg) of
+ case is_anonymous(KexAlg) of
true ->
calculate_secret(Params#server_key_params.params,
- State#state{hashsign_algorithm = HashSign}, Connection);
+ 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{hashsign_algorithm = HashSign},
- Connection);
+ 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, certify, State)
+ 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{session = #session{own_certificate = Cert},
- role = client,
- ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
- negotiated_version = Version} = State0, Connection) ->
- case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of
+ #state{static_env = #static_env{role = client},
+ handshake_env = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{own_certificate = Cert},
+ ssl_options = #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, certify, State0);
- NegotiatedHashSign ->
- {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
- Connection:next_event(certify, Record,
- State#state{cert_hashsign_algorithm = NegotiatedHashSign})
+ 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{session = #session{master_secret = undefined},
- negotiated_version = Version,
- psk_identity = PSKIdentity,
- ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
- premaster_secret = undefined,
- role = client,
- key_algorithm = Alg} = State0, Connection)
- when Alg == psk ->
- case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of
+ #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 = #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, certify, State0);
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
PremasterSecret ->
State = master_secret(PremasterSecret,
- State0#state{premaster_secret = PremasterSecret}),
- client_certify_and_key_exchange(State, Connection)
+ State0#state{handshake_env =
+ HsEnv#handshake_env{premaster_secret = PremasterSecret}}),
+ client_certify_and_key_exchange(State, Connection)
end;
-
certify(internal, #server_hello_done{},
- #state{session = #session{master_secret = undefined},
- ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
- negotiated_version = {Major, Minor} = Version,
- psk_identity = PSKIdentity,
- premaster_secret = undefined,
- role = client,
- key_algorithm = Alg} = State0, Connection)
- when Alg == rsa_psk ->
+ #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 = #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({Alg, PSKIdentity}, PSKLookup,
+ case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
RSAPremasterSecret) of
#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0);
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
PremasterSecret ->
State = master_secret(PremasterSecret,
- State0#state{premaster_secret = RSAPremasterSecret}),
+ 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{session = #session{master_secret = MasterSecret} = Session,
- connection_states = ConnectionStates0,
- negotiated_version = Version,
- premaster_secret = undefined,
- role = client} = State0, Connection) ->
+ #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, certify, State0)
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
-
%% Master secret is calculated from premaster_secret
certify(internal, #server_hello_done{},
- #state{session = Session0,
- connection_states = ConnectionStates0,
- negotiated_version = Version,
- premaster_secret = PremasterSecret,
- role = client} = State0, Connection) ->
+ #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} ->
@@ -641,29 +1147,27 @@ certify(internal, #server_hello_done{},
session = Session},
client_certify_and_key_exchange(State, Connection);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
-
certify(internal = Type, #client_key_exchange{} = Msg,
- #state{role = server,
+ #state{static_env = #static_env{role = server},
client_certificate_requested = true,
ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State,
Connection) ->
%% We expect a certificate here
- handle_common_event(Type, Msg, certify, State, Connection);
-
+ handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection);
certify(internal, #client_key_exchange{exchange_keys = Keys},
- State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) ->
+ 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, certify, State)
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
-
certify(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, certify, State, Connection).
+ handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(),
@@ -672,234 +1176,239 @@ certify(Type, Msg, State, Connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
cipher({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, cipher, State, Connection);
-
+ handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
cipher(info, Msg, State, _) ->
- handle_info(Msg, cipher, State);
-
+ handle_info(Msg, ?FUNCTION_NAME, State);
cipher(internal, #certificate_verify{signature = Signature,
hashsign_algorithm = CertHashSign},
- #state{role = server,
- key_algorithm = KexAlg,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- session = #session{master_secret = MasterSecret},
- tls_handshake_history = Handshake
- } = State0, Connection) ->
+ #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, PublicKeyInfo, Version),
- case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
- ssl:tls_version(Version), HashSign, MasterSecret, Handshake) of
+ HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion),
+ case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
+ TLSVersion, HashSign, MasterSecret, Hist) of
valid ->
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(cipher, Record,
- State#state{cert_hashsign_algorithm = HashSign});
+ 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, cipher, State0)
+ 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{role = server, expecting_next_protocol_negotiation = true,
- negotiated_protocol = undefined, negotiated_version = Version} = State0,
+ #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, cipher, State0);
-
+ handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
cipher(internal, #finished{verify_data = Data} = Finished,
- #state{negotiated_version = Version,
- host = Host,
- port = Port,
- role = Role,
- expecting_finished = true,
+ #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,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ 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, Handshake0) of
+ MasterSecret, Hist) of
verified ->
- Session = register_session(Role, Host, Port, Session0),
+ Session = handle_session(Role, SslOpts, Host, Port, Session0),
cipher_role(Role, Data, Session,
- State#state{expecting_finished = false}, Connection);
+ State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, cipher, State)
+ 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{role = server, expecting_next_protocol_negotiation = true,
- expecting_finished = true} = State0, Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
- Connection:next_event(cipher, Record,
- State#state{expecting_next_protocol_negotiation = false});
-cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} =
- State0, Connection) ->
- ConnectionStates1 =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read),
- {Record, State} = Connection:next_record(State0#state{connection_states =
- ConnectionStates1}),
- Connection:next_event(cipher, Record, State#state{expecting_finished = true});
+ #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, cipher, 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, From}, {application_data, Data},
- #state{protocol_cb = Connection} = State, Connection) ->
- %% We should look into having a worker process to do this to
- %% parallize send and receive decoding and not block the receiver
- %% if sending is overloading the socket.
- try
- write_application_data(Data, From, State)
- catch throw:Error ->
- hibernate_after(connection, State, [{reply, From, Error}])
- end;
connection({call, RecvFrom}, {recv, N, Timeout},
- #state{protocol_cb = Connection, socket_options =
- #socket_options{active = false}} = State0, Connection) ->
- Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
- Connection:passive_receive(State0#state{bytes_to_read = N,
- start_or_recv_from = RecvFrom,
- timer = Timer}, connection);
-connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State,
+ #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{renegotiation = {true, From}}, []);
+ 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(connection, State, [{reply, From, {ok, Cert}}]);
-connection({call, From}, connection_information, 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(connection, State, [{reply, From, {ok, Info}}]);
-connection({call, From}, session_info, #state{session = #session{session_id = Id,
- cipher_suite = Suite}} = State, _) ->
- SessionInfo = [{session_id, Id},
- {cipher_suite, ssl_cipher:erl_suite_definition(Suite)}],
- hibernate_after(connection, State, [{reply, From, SessionInfo}]);
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
connection({call, From}, negotiated_protocol,
- #state{negotiated_protocol = undefined} = State, _) ->
- hibernate_after(connection, State, [{reply, From, {error, protocol_not_negotiated}}]);
+ #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{negotiated_protocol = SelectedProtocol} = State, _) ->
- hibernate_after(connection, State,
+ #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, connection, 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 = #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, connection, State);
-connection(internal, {recv, _}, State, Connection) ->
- Connection:passive_receive(State, connection);
+ handle_info(Msg, ?FUNCTION_NAME, State);
+connection(internal, {recv, Timeout}, State, Connection) ->
+ passive_receive(State, ?FUNCTION_NAME, Connection, [{{timeout, recv}, Timeout, timeout}]);
connection(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, connection, 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(internal, #alert{description = ?CLOSE_NOTIFY},
- #state{transport_cb = Transport, socket = Socket,
- downgrade = {Pid, From}} = State, _) ->
- tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
- Transport:controlling_process(Socket, Pid),
- gen_statem:reply(From, {ok, Socket}),
- {stop, normal, State};
-downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) ->
- gen_statem:reply(From, {error, timeout}),
- {stop, normal, State};
downgrade(Type, Event, State, Connection) ->
- handle_common_event(Type, Event, downgrade, 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{role = client} = State, _) ->
+ #state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, _) ->
%% Should not be included in handshake history
- {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]};
-handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _)
+ {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{tls_handshake_history = Hs0,
- ssl_options = #ssl_options{v2_hello_compatible = V2HComp}} = State0,
+ keep_state_and_data;
+handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist0}} = 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
- State = handle_sni_extension(PossibleSNI, State0),
- HsHist = ssl_handshake:update_handshake_history(Hs0, iolist_to_binary(Raw), V2HComp),
- {next_state, StateName, State#state{tls_handshake_history = HsHist},
+ State = #state{handshake_env = HsEnv} = handle_sni_extension(PossibleSNI, State0),
+
+ 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}]};
handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) ->
- Connection:handle_common_event(internal, TLSorDTLSRecord, StateName, State);
+ Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
handle_common_event(timeout, hibernate, _, _, _) ->
{keep_state_and_data, [hibernate]};
-handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) ->
- case read_application_data(Data, State0) of
- {stop, Reason, State} ->
- {stop, Reason, State};
- {Record, State} ->
- Connection:next_event(StateName, Record, State)
- end;
handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
- #state{negotiated_version = Version} = State, _) ->
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version,
StateName, State);
-handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} = 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(Type, Msg, StateName, #state{connection_env =
+ #connection_env{negotiated_version = Version}} = State,
_) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE),
- handle_own_alert(Alert, Version, {StateName, Msg}, 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, {Pid, Timeout}}, From, StateName, State0, Connection) when is_pid(Pid) ->
- %% terminate will send close alert to peer
- State = State0#state{downgrade = {Pid, From}},
- Connection:terminate(downgrade, StateName, State),
- %% 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.
- {next_state, downgrade, State#state{terminated = true}, [{timeout, Timeout, downgrade}]};
-handle_call({close, _} = Close, From, StateName, State, Connection) ->
+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 = Connection:terminate(Close, StateName, State#state{terminated = true}),
- {stop_and_reply, {shutdown, normal},
- {reply, From, Result}, State};
-handle_call({shutdown, How0}, From, _,
- #state{transport_cb = Transport,
- negotiated_version = Version,
- connection_states = ConnectionStates,
- socket = Socket}, Connection) ->
- case How0 of
- How when How == write; How == both ->
- Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- {BinMsg, _} =
- Connection:encode_alert(Alert, Version, ConnectionStates),
- Connection:send(Transport, Socket, BinMsg);
- _ ->
- ok
- end,
-
+ 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 ->
- {keep_state_and_data, [{reply, From, ok}]};
+ {next_state, StateName, State, [{reply, From, ok}]};
Error ->
- gen_statem:reply(From, {error, Error}),
- {stop, normal}
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
end;
handle_call({recv, _N, _Timeout}, From, _,
#state{socket_options =
@@ -908,35 +1417,47 @@ handle_call({recv, _N, _Timeout}, From, _,
handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) ->
%% Doing renegotiate wait with handling request until renegotiate is
%% finished.
- Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
- {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom,
- timer = Timer},
- [{next_event, internal, {recv, RecvFrom}}]};
+ {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{user_application = {OldMon, _}}, _) ->
+ State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) ->
NewMon = erlang:monitor(process, User),
erlang:demonitor(OldMon, [flush]),
- {next_state, StateName, State#state{user_application = {NewMon,User}},
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
[{reply, From, ok}]};
handle_call({get_opts, OptTags}, From, _,
- #state{socket = Socket,
- transport_cb = Transport,
- socket_options = SockOpts}, _) ->
- OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []),
+ #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{socket_options = Opts1,
- socket = Socket,
- transport_cb = Transport} = State0, _) ->
- {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []),
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport,
+ tracker = Tracker},
+ 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, Tracker, 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,
- negotiated_version = Version}, _) ->
+ connection_env = #connection_env{negotiated_version = Version}}, _) ->
#{security_parameters := SecParams} =
ssl_record:current_connection_state(ConnectionStates, read),
#security_parameters{master_secret = MasterSecret,
@@ -963,76 +1484,76 @@ handle_call(_,_,_,_,_) ->
{keep_state_and_data, [postpone]}.
handle_info({ErrorTag, Socket, econnaborted}, StateName,
- #state{socket = Socket, transport_cb = Transport,
- protocol_cb = Connection,
- start_or_recv_from = StartFrom, role = Role,
- error_tag = ErrorTag,
- tracker = Tracker} = State) when StateName =/= connection ->
- alert_user(Transport, Tracker,Socket,
- StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection),
- {stop, normal, State};
-
-handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
- error_tag = ErrorTag} = State) ->
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ error_tag = ErrorTag,
+ tracker = Tracker,
+ protocol_cb = Connection},
+ start_or_recv_from = StartFrom
+ } = State) when StateName =/= connection ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Tracker,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{socket = Socket,
+ error_tag = ErrorTag}} = State) ->
Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
- error_logger:info_report(Report),
+ ?LOG_ERROR(Report),
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- {stop, normal, State};
-
-handle_info({'DOWN', MonitorRef, _, _, _}, _,
- State = #state{user_application={MonitorRef,_Pid}}) ->
- {stop, normal, State};
-
+ {stop, {shutdown,normal}, State};
+
+handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
+ ssl_options = #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{socket = Socket} = 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(allow_renegotiate, StateName, State) ->
- {next_state, StateName, State#state{allow_renegotiate = true}};
-
-handle_info({cancel_start_or_recv, StartFrom}, StateName,
- #state{renegotiation = {false, first}} = State) when StateName =/= connection ->
- {stop_and_reply, {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}}, State#state{timer = undefined}};
-
-handle_info({cancel_start_or_recv, RecvFrom}, StateName,
- #state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined ->
- {next_state, StateName, State#state{start_or_recv_from = undefined,
- bytes_to_read = undefined,
- timer = undefined}, [{reply, RecvFrom, {error, timeout}}]};
+ {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({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
- {next_state, StateName, State#state{timer = undefined}};
+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{socket = Socket, error_tag = Tag} = State) ->
+handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) ->
Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]),
- error_logger:info_report(Report),
+ ?LOG_NOTICE(Report),
{next_state, StateName, State}.
-%%--------------------------------------------------------------------
-%% gen_statem callbacks
-%%--------------------------------------------------------------------
-terminate(_, _, #state{terminated = true}) ->
+%%====================================================================
+%% 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 recived before
+ %% 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{protocol_cb = Connection,
- socket = Socket, transport_cb = Transport} = State) ->
+ _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{%%send_queue = SendQueue,
- protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport} = State) ->
+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) ->
@@ -1040,19 +1561,27 @@ terminate({shutdown, own_alert}, _StateName, #state{%%send_queue = SendQueue,
_ ->
Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
end;
-terminate(Reason, connection, #state{negotiated_version = Version,
- protocol_cb = Connection,
- connection_states = ConnectionStates0,
- ssl_options = #ssl_options{padding_check = Check},
- transport_cb = Transport, socket = Socket
- } = State) ->
+terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket}
+ } = State) ->
handle_trusted_certs_db(State),
- {BinAlert, ConnectionStates} = terminate_alert(Reason, Version, ConnectionStates0, Connection),
- Connection:send(Transport, Socket, BinAlert),
- Connection:close(Reason, Socket, Transport, ConnectionStates, Check);
-
-terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection,
- socket = Socket
+ 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 = #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).
@@ -1071,137 +1600,31 @@ format_status(terminate, [_, StateName, State]) ->
[{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
protocol_buffers = ?SECRET_PRINTOUT,
user_data_buffer = ?SECRET_PRINTOUT,
- tls_handshake_history = ?SECRET_PRINTOUT,
+ handshake_env = ?SECRET_PRINTOUT,
+ connection_env = ?SECRET_PRINTOUT,
session = ?SECRET_PRINTOUT,
- private_key = ?SECRET_PRINTOUT,
- diffie_hellman_params = ?SECRET_PRINTOUT,
- diffie_hellman_keys = ?SECRET_PRINTOUT,
- srp_params = ?SECRET_PRINTOUT,
- srp_keys = ?SECRET_PRINTOUT,
- premaster_secret = ?SECRET_PRINTOUT,
ssl_options = NewOptions,
flight_buffer = ?SECRET_PRINTOUT}
}}]}].
%%--------------------------------------------------------------------
-%%%
-%%--------------------------------------------------------------------
-write_application_data(Data0, From,
- #state{socket = Socket,
- negotiated_version = Version,
- protocol_cb = Connection,
- transport_cb = Transport,
- connection_states = ConnectionStates0,
- socket_options = SockOpts,
- ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) ->
- Data = encode_packet(Data0, SockOpts),
-
- case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
- true ->
- Connection:renegotiate(State#state{renegotiation = {true, internal}},
- [{next_event, {call, From}, {application_data, Data0}}]);
- false ->
- {Msgs, ConnectionStates} = Connection:encode_data(Data, Version, ConnectionStates0),
- Result = Connection:send(Transport, Socket, Msgs),
- ssl_connection:hibernate_after(connection, State#state{connection_states = ConnectionStates},
- [{reply, From, Result}])
- end.
-
-read_application_data(Data, #state{user_application = {_Mon, Pid},
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- socket_options = SOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- timer = Timer,
- user_data_buffer = Buffer0,
- tracker = Tracker} = State0) ->
- Buffer1 = if
- Buffer0 =:= <<>> -> Data;
- Data =:= <<>> -> Buffer0;
- true -> <<Buffer0/binary, Data/binary>>
- end,
- case get_data(SOpts, BytesToRead, Buffer1) of
- {ok, ClientData, Buffer} -> % Send data
- SocketOpt = deliver_app_data(Transport, Socket, SOpts,
- ClientData, Pid, RecvFrom, Tracker, Connection),
- cancel_timer(Timer),
- State = State0#state{user_data_buffer = Buffer,
- start_or_recv_from = undefined,
- timer = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpt
- },
- if
- SocketOpt#socket_options.active =:= false; Buffer =:= <<>> ->
- %% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- Connection:next_record_if_active(State);
- true -> %% We have more data
- read_application_data(<<>>, State)
- end;
- {more, Buffer} -> % no reply, we need more data
- Connection:next_record(State0#state{user_data_buffer = Buffer});
- {passive, Buffer} ->
- Connection:next_record_if_active(State0#state{user_data_buffer = Buffer});
- {error,_Reason} -> %% Invalid packet in packet mode
- deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection),
- {stop, normal, State0}
- end.
-%%--------------------------------------------------------------------
-%%%
-%%--------------------------------------------------------------------
-handle_alert(#alert{level = ?FATAL} = Alert, StateName,
- #state{socket = Socket, transport_cb = Transport,
- protocol_cb = Connection,
- ssl_options = SslOpts, start_or_recv_from = From, host = Host,
- port = Port, session = Session, user_application = {_Mon, Pid},
- role = Role, socket_options = Opts, tracker = Tracker}) ->
- invalidate_session(Role, Host, Port, Session),
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection),
- {stop, normal};
-
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- StateName, State) ->
- handle_normal_shutdown(Alert, StateName, State),
- {stop, {shutdown, peer_close}};
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{ssl_options = SslOpts, renegotiation = {true, internal}} = State) ->
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- handle_normal_shutdown(Alert, StateName, State),
- {stop, {shutdown, peer_close}};
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{ssl_options = SslOpts, renegotiation = {true, From},
- protocol_cb = Connection} = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- {Record, State} = Connection:next_record(State0),
- %% Go back to connection!
- Connection:next_event(connection, Record, State);
-
-%% Gracefully log and ignore all other warning alerts
-handle_alert(#alert{level = ?WARNING} = Alert, StateName,
- #state{ssl_options = SslOpts, protocol_cb = Connection} = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(StateName, Record, State).
-
-%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-connection_info(#state{sni_hostname = SNIHostname,
- session = #session{cipher_suite = CipherSuite, ecc = ECCCurve},
- protocol_cb = Connection,
- negotiated_version = {_,_} = Version,
+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},
+ 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 = ssl_cipher:erl_suite_definition(CipherSuite),
- IsNamedCurveSuite = lists:member(element(1,CipherSuiteDef),
- [ecdh_ecdsa, ecdhe_ecdsa, ecdh_anon]),
+ 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)}}];
@@ -1209,21 +1632,37 @@ connection_info(#state{sni_hostname = SNIHostname,
[]
end,
[{protocol, RecordCB:protocol_version(Version)},
- {cipher_suite, CipherSuiteDef},
+ {session_id, SessionId},
+ {cipher_suite, ssl_cipher_format:suite_legacy(CipherSuiteDef)},
+ {selected_cipher_suite, CipherSuiteDef},
{sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts).
-do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} =
+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{negotiated_version = Version,
+ #state{connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = HsEnv,
session = #session{session_id = SessId},
- connection_states = ConnectionStates0}
+ connection_states = ConnectionStates0,
+ ssl_options = #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), ConnectionStates0, ServerHelloExt),
+ ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
+ ConnectionStates1, ServerHelloExt),
State = server_hello(ServerHello,
- State0#state{expecting_next_protocol_negotiation =
- NextProtocols =/= undefined}, Connection),
+ State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
+ NextProtocols =/= undefined}}, Connection),
case Type of
new ->
new_server_hello(ServerHello, State, Connection);
@@ -1231,20 +1670,73 @@ do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocol
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,
- negotiated_version = Version} = State0, Connection) ->
+ #state{session = Session0,
+ connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
try server_certify_and_key_exchange(State0, Connection) of
#state{} = State1 ->
- {State2, Actions} = server_hello_done(State1, Connection),
+ {State, Actions} = server_hello_done(State1, Connection),
Session =
Session0#session{session_id = SessionId,
cipher_suite = CipherSuite,
compression_method = Compression},
- {Record, State} = Connection:next_record(State2#state{session = Session}),
- Connection:next_event(certify, Record, State, Actions)
+ Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
catch
#alert{} = Alert ->
handle_own_alert(Alert, Version, hello, State0)
@@ -1252,85 +1744,76 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite,
resumed_server_hello(#state{session = Session,
connection_states = ConnectionStates0,
- negotiated_version = Version} = State0, 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},
- {State2, Actions} =
+ {State, Actions} =
finalize_handshake(State1, abbreviated, Connection),
- {Record, State} = Connection:next_record(State2),
- Connection:next_event(abbreviated, Record, State, Actions);
+ 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,
- {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
- State = Connection:queue_handshake(ServerHello, State0),
- State#state{key_algorithm = KeyAlgorithm}.
+ #{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{session = #session{cipher_suite = CipherSuite} = Session} = State0,
+ #state{handshake_env = HsEnv,
+ session = #session{cipher_suite = CipherSuite} = Session} = State0,
Connection) ->
- State1 = State0#state{session =
- Session#session{peer_certificate = PeerCert},
- public_key_info = PublicKeyInfo},
- {KeyAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
- State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1),
-
- {Record, State} = Connection:next_record(State2),
- Connection:next_event(certify, Record, State).
+ 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) when KeyAlg == ecdh_rsa;
- KeyAlg == ecdh_ecdsa ->
+ 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{diffie_hellman_keys = ECDHKey});
-
-%% We do currently not support cipher suites that use fixed DH.
-%% If we want to implement that the following clause can be used
-%% to extract DH parameters form cert.
-%% handle_peer_cert_key(client, _PeerCert, {?dhpublicnumber, PublicKey, PublicKeyParams},
-%% {_,SignAlg},
-%% #state{diffie_hellman_keys = {_, MyPrivatKey}} = State) when
-%% SignAlg == dh_rsa;
-%% SignAlg == dh_dss ->
-%% dh_master_secret(PublicKeyParams, PublicKey, MyPrivatKey, State);
+ 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{client_certificate_requested = true, role = client,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+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{client_certificate_requested = true, role = client,
- negotiated_version = Version,
- private_key = PrivateKey,
+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},
- cert_hashsign_algorithm = HashSign,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ own_certificate = OwnCert}} = State, Connection) ->
case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
- ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of
+ ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
#certificate_verify{} = Verified ->
Connection:queue_handshake(Verified, State);
ignore ->
@@ -1341,16 +1824,15 @@ verify_client_cert(#state{client_certificate_requested = true, role = client,
verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
State.
-client_certify_and_key_exchange(#state{negotiated_version = Version} =
+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),
- State3 = State2#state{
- %% Reinitialize
- client_certificate_requested = false},
- {Record, State} = Connection:next_record(State3),
- Connection:next_event(cipher, Record, State, Actions)
+ 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)
@@ -1367,62 +1849,87 @@ server_certify_and_key_exchange(State0, Connection) ->
request_client_cert(State2, Connection).
certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
- #state{private_key = Key} = State, Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(EncPMS, Key),
+ #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{diffie_hellman_params = #'DHParameter'{} = Params,
- diffie_hellman_keys = {_, ServerDhPrivateKey}} = State,
+ #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{diffie_hellman_keys = ECDHKey} = State, Connection) ->
+ #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 =
#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{diffie_hellman_params = #'DHParameter'{} = Params,
- diffie_hellman_keys = {_, ServerDhPrivateKey},
+ #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
+ kex_keys = {_, ServerDhPrivateKey}},
ssl_options =
#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 =
+ #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{private_key = Key,
+ #state{connection_env = #connection_env{private_key = Key},
ssl_options =
#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{srp_params = Params,
- srp_keys = Key
+ #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{key_algorithm = Algo} = State, _) when Algo == dh_anon;
- Algo == ecdh_anon;
- Algo == psk;
- Algo == dhe_psk;
- Algo == srp_anon ->
+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{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+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{} ->
@@ -1431,18 +1938,19 @@ certify_server(#state{cert_db = CertDbHandle,
throw(Alert)
end.
-key_exchange(#state{role = server, key_algorithm = rsa} = State,_) ->
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) ->
State;
-key_exchange(#state{role = server, key_algorithm = Algo,
- hashsign_algorithm = HashSignAlgo,
- diffie_hellman_params = #'DHParameter'{} = Params,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
- } = State0, Connection)
- when Algo == dhe_dss;
- Algo == dhe_rsa;
- Algo == dh_anon ->
+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),
@@ -1452,21 +1960,26 @@ key_exchange(#state{role = server, key_algorithm = Algo,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{diffie_hellman_keys = DHKeys};
-
-key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State, _)
- when Algo == ecdh_ecdsa; Algo == ecdh_rsa ->
- State#state{diffie_hellman_keys = Key};
-key_exchange(#state{role = server, key_algorithm = Algo,
- hashsign_algorithm = HashSignAlgo,
- private_key = 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,
- negotiated_version = Version
- } = State0, Connection)
- when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa;
- Algo == ecdh_anon ->
+ connection_states = ConnectionStates0} = State0, Connection)
+ when KexAlg == ecdhe_ecdsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdh_anon ->
ECDHKeys = public_key:generate_key(ECCCurve),
#{security_parameters := SecParams} =
@@ -1478,19 +1991,19 @@ key_exchange(#state{role = server, key_algorithm = Algo,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{diffie_hellman_keys = ECDHKeys};
-
-key_exchange(#state{role = server, key_algorithm = psk,
+ #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 = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{role = server, key_algorithm = psk,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
- hashsign_algorithm = HashSignAlgo,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
- } = State0, Connection) ->
+ 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,
@@ -1499,16 +2012,16 @@ key_exchange(#state{role = server, key_algorithm = psk,
{psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
- PrivateKey}),
+ PrivateKey}),
Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{role = server, key_algorithm = dhe_psk,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
- hashsign_algorithm = HashSignAlgo,
- diffie_hellman_params = #'DHParameter'{} = Params,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
+ 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} =
@@ -1521,18 +2034,41 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{diffie_hellman_keys = DHKeys};
-
-key_exchange(#state{role = server, key_algorithm = rsa_psk,
+ #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 = #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 = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{role = server, key_algorithm = rsa_psk,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
- hashsign_algorithm = HashSignAlgo,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
+ 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),
@@ -1544,18 +2080,18 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk,
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{role = server, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{user_lookup_fun = LookupFun},
- hashsign_algorithm = HashSignAlgo,
+ 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},
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
+ connection_states = ConnectionStates0
} = State0, Connection)
- when Algo == srp_dss;
- Algo == srp_rsa;
- Algo == srp_anon ->
+ 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{} ->
@@ -1572,75 +2108,86 @@ key_exchange(#state{role = server, key_algorithm = Algo,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{srp_params = SrpParams,
- srp_keys = Keys};
-
-key_exchange(#state{role = client,
- key_algorithm = rsa,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- premaster_secret = PremasterSecret} = State0, Connection) ->
+ #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{role = client,
- key_algorithm = Algorithm,
- negotiated_version = Version,
- diffie_hellman_keys = {DhPubKey, _}
- } = State0, Connection)
- when Algorithm == dhe_dss;
- Algorithm == dhe_rsa;
- Algorithm == dh_anon ->
+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{role = client,
- key_algorithm = Algorithm,
- negotiated_version = Version,
- diffie_hellman_keys = Keys} = State0, Connection)
- when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa;
- Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa;
- Algorithm == ecdh_anon ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Keys}),
- Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{role = client,
- ssl_options = SslOpts,
- key_algorithm = psk,
- negotiated_version = Version} = State0, Connection) ->
+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 = SslOpts} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{psk, SslOpts#ssl_options.psk_identity}),
Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{role = client,
- ssl_options = SslOpts,
- key_algorithm = dhe_psk,
- negotiated_version = Version,
- diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) ->
+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 = SslOpts} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{dhe_psk,
SslOpts#ssl_options.psk_identity, DhPubKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
- ssl_options = SslOpts,
- key_algorithm = rsa_psk,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- premaster_secret = PremasterSecret}
+
+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 = SslOpts} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {ecdhe_psk,
+ SslOpts#ssl_options.psk_identity, 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 = SslOpts}
= State0, Connection) ->
Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity,
PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{role = client,
- key_algorithm = Algorithm,
- negotiated_version = Version,
- srp_keys = {ClientPubKey, _}}
+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 Algorithm == srp_dss;
- Algorithm == srp_rsa;
- Algorithm == srp_anon ->
+ 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).
@@ -1677,18 +2224,30 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
rsa_psk_key_exchange(_, _, _, _) ->
throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
-request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer,
- signature_algs = SupportedHashSigns},
- connection_states = ConnectionStates0,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- negotiated_version = Version} = State0, Connection) ->
+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 = #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, [TLSVersion]),
+ TLSVersion),
Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
HashSigns, TLSVersion),
State = Connection:queue_handshake(Msg, State0),
@@ -1699,7 +2258,7 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
State.
calculate_master_secret(PremasterSecret,
- #state{negotiated_version = Version,
+ #state{connection_env = #connection_env{negotiated_version = Version},
connection_states = ConnectionStates0,
session = Session0} = State0, Connection,
_Current, Next) ->
@@ -1707,10 +2266,9 @@ calculate_master_secret(PremasterSecret,
ConnectionStates0, server) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
- State1 = State0#state{connection_states = ConnectionStates,
+ State = State0#state{connection_states = ConnectionStates,
session = Session},
- {Record, State} = Connection:next_record(State1),
- Connection:next_event(Next, Record, State);
+ Connection:next_event(Next, no_record, State);
#alert{} = Alert ->
handle_own_alert(Alert, Version, certify, State0)
end.
@@ -1721,33 +2279,35 @@ finalize_handshake(State0, StateName, Connection) ->
ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0,
- write),
+ write, Connection),
State2 = State1#state{connection_states = ConnectionStates},
State = next_protocol(State2, Connection),
finished(State, StateName, Connection).
-next_protocol(#state{role = server} = State, _) ->
+next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
State;
-next_protocol(#state{negotiated_protocol = undefined} = State, _) ->
+next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
State;
-next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) ->
+next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) ->
State;
-next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) ->
+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{role = Role, negotiated_version = Version,
+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,
- tls_handshake_history = Handshake0} = State0, StateName, Connection) ->
+ 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, Handshake0),
+ MasterSecret, Hist),
ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
Connection:send_handshake(Finished, State0#state{connection_states =
ConnectionStates}).
@@ -1763,53 +2323,71 @@ save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbrev
calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base,
dh_y = ServerPublicDhKey} = Params,
- State, Connection) ->
+ #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{diffie_hellman_keys = Keys},
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
Connection, certify, certify);
calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
- State=#state{session=Session}, Connection) ->
+ #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{diffie_hellman_keys = ECDHKeys,
+ 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},
- State0, Connection) ->
+ #state{handshake_env = HsEnv} = State, Connection) ->
%% store for later use
- {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}),
- Connection:next_event(certify, Record, State);
+ 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{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} =
+ #state{handshake_env = HsEnv,
+ ssl_options = #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{diffie_hellman_keys = Keys},
+ 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 = #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{ssl_options = #ssl_options{srp_identity = SRPId}} = State,
+ #state{handshake_env = HsEnv,
+ ssl_options = #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{srp_keys = Keys}, Connection,
+ 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{session = Session,
- negotiated_version = Version, role = Role,
+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
@@ -1827,22 +2405,24 @@ generate_srp_server_keys(_SrpParams, 10) ->
generate_srp_server_keys(SrpParams =
#srp_user{generator = Generator, prime = Prime,
verifier = Verifier}, N) ->
- case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
- error ->
- generate_srp_server_keys(SrpParams, N+1);
+ 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) ->
- case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
- error ->
- generate_srp_client_keys(Generator, Prime, N+1);
+ 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}) ->
@@ -1867,7 +2447,7 @@ cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0}
{Record, State} = prepare_connection(State0#state{session = Session,
connection_states = ConnectionStates},
Connection),
- Connection:next_event(connection, Record, State);
+ 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,
@@ -1876,14 +2456,15 @@ cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0
finalize_handshake(State0#state{connection_states = ConnectionStates1,
session = Session}, cipher, Connection),
{Record, State} = prepare_connection(State1, Connection),
- Connection:next_event(connection, Record, State, Actions).
-
-is_anonymous(Algo) when Algo == dh_anon;
- Algo == ecdh_anon;
- Algo == psk;
- Algo == dhe_psk;
- Algo == rsa_psk;
- Algo == srp_anon ->
+ 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.
@@ -1916,42 +2497,39 @@ call(FsmPid, Event) ->
{error, closed}
end.
-get_socket_opts(_,_,[], _, Acc) ->
+get_socket_opts(_, _,_,[], _, Acc) ->
{ok, Acc};
-get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) ->
- get_socket_opts(Transport, Socket, Tags, SockOpts,
+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(Transport, Socket, [packet | Tags], SockOpts, Acc) ->
+get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) ->
case SockOpts#socket_options.packet of
{Type, headers} ->
- get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
Type ->
- get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
end;
-get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) ->
- get_socket_opts(Transport, Socket, Tags, SockOpts,
+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(Transport, Socket, [active | Tags], SockOpts, Acc) ->
- get_socket_opts(Transport, Socket, Tags, SockOpts,
+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(Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
- try tls_socket:getopts(Transport, Socket, [Tag]) of
- {ok, [Opt]} ->
- get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]);
- {error, Error} ->
- {error, {options, {socket_options, Tag, Error}}}
- catch
- %% So that inet behavior does not crash our process
- _:Error -> {error, {options, {socket_options, Tag, Error}}}
+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, _,_) ->
+get_socket_opts(_,_, _,Opts, _,_) ->
{error, {options, {socket_options, Opts, function_clause}}}.
-set_socket_opts(_,_, [], SockOpts, []) ->
+set_socket_opts(_,_,_, [], SockOpts, []) ->
{ok, SockOpts};
-set_socket_opts(Transport, Socket, [], SockOpts, Other) ->
+set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) ->
%% Set non emulated options
- try tls_socket:setopts(Transport, Socket, Other) of
+ try ConnectionCb:setopts(Transport, Socket, Other) of
ok ->
{ok, SockOpts};
{error, InetError} ->
@@ -1962,13 +2540,13 @@ set_socket_opts(Transport, Socket, [], SockOpts, Other) ->
{{error, {options, {socket_options, Other, Error}}}, SockOpts}
end;
-set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
+set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
when Mode == list; Mode == binary ->
- set_socket_opts(Transport, Socket, Opts,
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
SockOpts#socket_options{mode = Mode}, Other);
-set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) ->
+set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) ->
{{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
+set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
when Packet == raw;
Packet == 0;
Packet == 1;
@@ -1984,31 +2562,52 @@ set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
Packet == httph;
Packet == http_bin;
Packet == httph_bin ->
- set_socket_opts(Transport, Socket, Opts,
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
SockOpts#socket_options{packet = Packet}, Other);
-set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) ->
+set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) ->
{{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
+set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
when is_integer(Header) ->
- set_socket_opts(Transport, Socket, Opts,
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
SockOpts#socket_options{header = Header}, Other);
-set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) ->
+set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) ->
{{error,{options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
when Active == once;
Active == true;
Active == false ->
- set_socket_opts(Transport, Socket, Opts,
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
SockOpts#socket_options{active = Active}, Other);
-set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) ->
+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(Transport, Socket, [Opt | Opts], SockOpts, Other) ->
- set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]).
+set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]).
+
-start_or_recv_cancel_timer(infinity, _RecvFrom) ->
- undefined;
-start_or_recv_cancel_timer(Timeout, RecvFrom) ->
- erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}).
hibernate_after(connection = StateName,
#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}} = State,
@@ -2016,36 +2615,32 @@ hibernate_after(connection = StateName,
{next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
-
-terminate_alert(normal, Version, ConnectionStates, Connection) ->
- Connection:encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- Version, ConnectionStates);
-terminate_alert({Reason, _}, Version, ConnectionStates, Connection) when Reason == close;
- Reason == shutdown ->
- Connection:encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- Version, ConnectionStates);
-
-terminate_alert(_, Version, ConnectionStates, Connection) ->
- {BinAlert, _} = Connection:encode_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR),
- Version, ConnectionStates),
- BinAlert.
+
+
+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 =
#ssl_options{cacertfile = <<>>, cacerts = []}}) ->
%% No trusted certs specified
ok;
-handle_trusted_certs_db(#state{cert_db_ref = Ref,
- cert_db = CertDb,
- ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined ->
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ cert_db = CertDb},
+ ssl_options = #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{file_ref_db = undefined}) ->
+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{cert_db_ref = Ref,
- file_ref_db = RefDb,
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ file_ref_db = RefDb},
ssl_options = #ssl_options{cacertfile = File}}) ->
case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
0 ->
@@ -2054,77 +2649,96 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref,
ok
end.
-prepare_connection(#state{renegotiation = Renegotiate,
+prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
start_or_recv_from = RecvFrom} = State0, Connection)
when Renegotiate =/= {false, first},
RecvFrom =/= undefined ->
- State1 = Connection:reinit_handshake_data(State0),
- {Record, State} = Connection:next_record(State1),
- {Record, ack_connection(State)};
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)};
prepare_connection(State0, Connection) ->
- State = Connection:reinit_handshake_data(State0),
+ State = Connection:reinit(State0),
{no_record, ack_connection(State)}.
-ack_connection(#state{renegotiation = {true, Initiater}} = State)
- when Initiater == internal;
- Initiater == peer ->
- State#state{renegotiation = undefined};
-ack_connection(#state{renegotiation = {true, From}} = 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{renegotiation = undefined};
-ack_connection(#state{renegotiation = {false, first},
- start_or_recv_from = StartFrom,
- timer = Timer} = State) when StartFrom =/= undefined ->
+ 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),
- cancel_timer(Timer),
- State#state{renegotiation = undefined,
- start_or_recv_from = undefined, timer = undefined};
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
+ start_or_recv_from = undefined};
ack_connection(State) ->
State.
-cancel_timer(undefined) ->
- ok;
-cancel_timer(Timer) ->
- erlang:cancel_timer(Timer),
- ok.
+session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
+ Session#session{ecc = ECCurve};
+session_handle_params(_, Session) ->
+ Session.
+
+handle_session(Role = server, #ssl_options{reuse_sessions = true} = SslOpts,
+ Host, Port, Session0) ->
+ register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true);
+handle_session(Role = client, #ssl_options{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.
-register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
+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),
+ ssl_manager:register_session(Host, Port, Session, Save),
Session;
-register_session(server, _, Port, #session{is_resumable = new} = Session0) ->
+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) ->
+register_session(_, _, _, Session, _) ->
Session. %% Already registered
+host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) ->
+ Hostname;
+host_id(_, Host, _) ->
+ Host.
+
handle_new_session(NewId, CipherSuite, Compression,
- #state{session = Session0,
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{protocol_cb = Connection},
+ session = Session0
+ } = State0) ->
Session = Session0#session{session_id = NewId,
cipher_suite = CipherSuite,
compression_method = Compression},
- {Record, State} = Connection:next_record(State0#state{session = Session}),
- Connection:next_event(certify, Record, State).
-
-handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
- negotiated_version = Version,
- host = Host, port = Port,
- protocol_cb = Connection,
- session_cache = Cache,
- session_cache_cb = CacheCb} = State0) ->
+ 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} ->
- {Record, State} =
- Connection:next_record(State0#state{
- connection_states = ConnectionStates,
- session = Session}),
- Connection:next_event(abbreviated, Record, State);
+ Connection:next_event(abbreviated, no_record, State#state{
+ connection_states = ConnectionStates,
+ session = Session});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
+ handle_own_alert(Alert, Version, hello, State)
end.
make_premaster_secret({MajVer, MinVer}, rsa) ->
@@ -2163,7 +2777,7 @@ ssl_options_list([ciphers = Key | Keys], [Value | Values], Acc) ->
ssl_options_list(Keys, Values,
[{Key, lists:map(
fun(Suite) ->
- ssl_cipher:erl_suite_definition(Suite)
+ ssl_cipher_format:suite_bin_to_map(Suite)
end, Value)}
| Acc]);
ssl_options_list([Key | Keys], [Value | Values], Acc) ->
@@ -2172,28 +2786,31 @@ ssl_options_list([Key | Keys], [Value | Values], Acc) ->
handle_active_option(false, connection = StateName, To, Reply, State) ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
-handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection,
- user_data_buffer = <<>>} = State0) ->
- %% Need data, set active once
- {Record, State1} = Connection:next_record_if_active(State0),
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State1) of
+handle_active_option(_, connection = StateName, To, _Reply, #state{connection_env = #connection_env{terminated = true},
+ user_data_buffer = {_,0,_}} = State) ->
+ handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd), 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, Reason, State} ->
- {stop, Reason, State}
+ {stop, _, _} = Stop ->
+ Stop
end;
-handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = State) ->
+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 =/= <<>>
-handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) ->
+%% 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, Reason, State} ->
- {stop, Reason, State};
+ {stop, _, _} = Stop ->
+ Stop;
{Record, State1} ->
%% Note: Renogotiation may cause StateName0 =/= StateName
case Connection:next_event(StateName0, Record, State1) of
@@ -2206,64 +2823,27 @@ handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection}
end
end.
-encode_packet(Data, #socket_options{packet=Packet}) ->
- case Packet of
- 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1);
- 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1);
- 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1);
- _ -> Data
- end.
-
-encode_size_packet(Bin, Size, Max) ->
- Len = erlang:byte_size(Bin),
- case Len > Max of
- true -> throw({error, {badarg, {packet_to_large, Len, Max}}});
- false -> <<Len:Size, Bin/binary>>
- end.
-
-time_to_renegotiate(_Data,
- #{current_write := #{sequence_number := Num}},
- RenegotiateAt) ->
-
- %% We could do test:
- %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt),
- %% but we chose to have a some what lower renegotiateAt and a much cheaper test
- is_time_to_renegotiate(Num, RenegotiateAt).
-
-is_time_to_renegotiate(N, M) when N < M->
- false;
-is_time_to_renegotiate(_,_) ->
- true.
-
%% Picks ClientData
-get_data(_, _, <<>>) ->
- {more, <<>>};
-%% Recv timed out save buffer data until next recv
-get_data(#socket_options{active=false}, undefined, Buffer) ->
- {passive, Buffer};
-get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
+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
- if
- Active =/= false orelse BytesToRead =:= 0 ->
+ case Bin of
+ <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
%% Active true or once, or passive mode recv(0)
- {ok, Buffer, <<>>};
- byte_size(Buffer) >= BytesToRead ->
+ {ok, Bin, <<>>};
+ <<Data:BytesToRead/binary, Rest/binary>> ->
%% Passive Mode, recv(Bytes)
- <<Data:BytesToRead/binary, Rest/binary>> = Buffer,
- {ok, Data, Rest};
- true ->
+ {ok, Data, Rest};
+ <<_/binary>> ->
%% Passive Mode not enough data
- {more, Buffer}
+ {more, BytesToRead}
end;
-get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) ->
+get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
PacketOpts = [{packet_size, Size}],
- case decode_packet(Type, Buffer, PacketOpts) of
- {more, _} ->
- {more, Buffer};
- Decoded ->
- Decoded
- end.
+ decode_packet(Type, Bin, PacketOpts).
decode_packet({http, headers}, Buffer, PacketOpts) ->
decode_packet(httph, Buffer, PacketOpts);
@@ -2281,42 +2861,61 @@ decode_packet(Type, Buffer, PacketOpts) ->
%% 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(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type},
- Data, Pid, From, Tracker, Connection) ->
- send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker, 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,
+deliver_app_data(
+ CPids, Transport, Socket,
+ #socket_options{active=Active, packet=Type} = SOpts,
+ Data, Pid, From, Tracker, Connection) ->
+ %%
+ send_or_reply(
+ Active, Pid, From,
+ format_reply(
+ CPids, Transport, Socket, SOpts, Data, Tracker, 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, Tracker, 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,
+format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet,
header = Header}, Data, _, _) ->
{ok, do_format_reply(Mode, Packet, Header, Data)};
-format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
+format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
header = Header}, Data, Tracker, Connection) ->
- {ssl, Connection:socket(self(), Transport, Socket, Connection, Tracker),
+ {ssl, Connection:socket(CPids, Transport, Socket, Tracker),
do_format_reply(Mode, Packet, Header, Data)}.
-deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker, Connection) ->
- send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker, Connection)).
+deliver_packet_error(CPids, Transport, Socket,
+ SO= #socket_options{active = Active}, Data, Pid, From, Tracker, Connection) ->
+ send_or_reply(Active, Pid, From, format_packet_error(CPids,
+ Transport, Socket, SO, Data, Tracker, Connection)).
-format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
+format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
{error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
-format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker, Connection) ->
- {ssl_error, Connection:socket(self(), Transport, Socket, Connection, Tracker),
+format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
+ Data, Tracker, Connection) ->
+ {ssl_error, Connection:socket(CPids, Transport, Socket, Tracker),
{invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
@@ -2331,6 +2930,9 @@ do_format_reply(list, Packet, _, Data)
do_format_reply(list, _,_, Data) ->
binary_to_list(Data).
+format_passive(CPids, Transport, Socket, Tracker, Connection) ->
+ {ssl_passive, Connection:socket(CPids, Transport, Socket, Tracker)}.
+
header(0, <<>>) ->
<<>>;
header(_, <<>>) ->
@@ -2351,77 +2953,41 @@ send_or_reply(_, Pid, _From, Data) ->
send_user(Pid, Data).
send_user(Pid, Msg) ->
- Pid ! Msg.
+ Pid ! Msg,
+ ok.
-alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) ->
- alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection);
-alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role, Connection) ->
- alert_user(Transport, Tracker, Socket, From, Alert, Role, Connection).
+alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
+alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection).
-alert_user(Transport, Tracker, Socket, From, Alert, Role, Connection) ->
- alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role, Connection).
+alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
-alert_user(_, _, _, false = Active, Pid, From, Alert, Role, _) when From =/= undefined ->
+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),
+ ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connection) ->
- case ssl_alert:reason_code(Alert, Role) of
+alert_user(Pids, Transport, Tracker, 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(self(),
- Transport, Socket, Connection, Tracker)});
+ {ssl_closed, Connection:socket(Pids, Transport, Socket, Tracker)});
ReasonCode ->
send_or_reply(Active, Pid, From,
- {ssl_error, Connection:socket(self(),
- Transport, Socket, Connection, Tracker), ReasonCode})
+ {ssl_error, Connection:socket(Pids, Transport, Socket, Tracker), ReasonCode})
end.
-log_alert(true, Info, Alert) ->
+log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
+ Txt = ssl_alert:own_alert_txt(Alert),
+ Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt),
+ ssl_logger:notice(Level, Report);
+log_alert(Level, Role, ProtocolName, StateName, Alert) ->
Txt = ssl_alert:alert_txt(Alert),
- error_logger:format("SSL: ~p: ~s\n", [Info, Txt]);
-log_alert(false, _, _) ->
- ok.
-
-handle_own_alert(Alert, Version, StateName,
- #state{transport_cb = Transport,
- socket = Socket,
- protocol_cb = Connection,
- connection_states = ConnectionStates,
- ssl_options = SslOpts} = State) ->
- try %% Try to tell the other side
- {BinMsg, _} =
- Connection:encode_alert(Alert, Version, ConnectionStates),
- Connection:send(Transport, Socket, BinMsg)
- catch _:_ -> %% Can crash if we are in a uninitialized state
- ignore
- end,
- try %% Try to tell the local user
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- handle_normal_shutdown(Alert,StateName, State)
- catch _:_ ->
- ok
- end,
- {stop, {shutdown, own_alert}}.
-
-handle_normal_shutdown(Alert, _, #state{socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- start_or_recv_from = StartFrom,
- tracker = Tracker,
- role = Role, renegotiation = {false, first}}) ->
- alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role, Connection);
-
-handle_normal_shutdown(Alert, StateName, #state{socket = Socket,
- socket_options = Opts,
- transport_cb = Transport,
- protocol_cb = Connection,
- user_application = {_Mon, Pid},
- tracker = Tracker,
- start_or_recv_from = RecvFrom, role = Role}) ->
- alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection).
+ Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt),
+ ssl_logger:notice(Level, Report).
invalidate_session(client, Host, Port, Session) ->
ssl_manager:invalidate_session(Host, Port, Session);
@@ -2430,7 +2996,9 @@ invalidate_session(server, _, Port, Session) ->
handle_sni_extension(undefined, State) ->
State;
-handle_sni_extension(#sni{hostname = Hostname}, State0) ->
+handle_sni_extension(#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 ->
@@ -2444,19 +3012,21 @@ handle_sni_extension(#sni{hostname = Hostname}, State0) ->
private_key := Key,
dh_params := DHParams,
own_certificate := OwnCert}} =
- ssl_config:init(NewOptions, State0#state.role),
+ ssl_config:init(NewOptions, Role),
State0#state{
session = State0#state.session#session{own_certificate = OwnCert},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
- private_key = Key,
- diffie_hellman_params = DHParams,
- ssl_options = NewOptions,
- sni_hostname = Hostname
- }
+ 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(OrigSSLOptions, SNIHostname) ->
@@ -2479,3 +3049,8 @@ new_emulated([], EmOpts) ->
EmOpts;
new_emulated(NewEmOpts, _) ->
NewEmOpts.
+
+no_records(Extensions) ->
+ maps:map(fun(_, Value) ->
+ ssl_handshake:extension_value(Value)
+ end, Extensions).
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index b597c059af..844368c761 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. 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.
@@ -33,69 +33,152 @@
-include("ssl_cipher.hrl").
-include_lib("public_key/include/public_key.hrl").
+-record(static_env, {
+ role :: client | server,
+ transport_cb :: atom(), % callback module
+ protocol_cb :: tls_connection | dtls_connection,
+ data_tag :: atom(), % ex tcp.
+ close_tag :: atom(), % ex tcp_closed
+ error_tag :: atom(), % ex tcp_error
+ passive_tag :: atom(), % ex tcp_passive
+ host :: string() | inet:ip_address(),
+ port :: integer(),
+ socket :: port() | tuple(), %% TODO: dtls socket
+ cert_db :: reference() | 'undefined',
+ session_cache :: db_handle(),
+ session_cache_cb :: atom(),
+ crl_db :: term(),
+ file_ref_db :: db_handle(),
+ cert_db_ref :: certdb_ref() | 'undefined',
+ tracker :: pid() | 'undefined' %% Tracker process for listen socket
+ }).
+
+-record(handshake_env, {
+ client_hello_version :: ssl_record:ssl_version() | 'undefined',
+ unprocessed_handshake_events = 0 :: integer(),
+ tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
+ | 'undefined',
+ expecting_finished = false ::boolean(),
+ renegotiation :: undefined | {boolean(), From::term() | internal | peer},
+ allow_renegotiate = true ::boolean(),
+ %% Ext handling
+ hello, %%:: #client_hello{} | #server_hello{}
+ sni_hostname = undefined,
+ expecting_next_protocol_negotiation = false ::boolean(),
+ next_protocol = undefined :: undefined | binary(),
+ alpn = undefined, %% Used in TLS 1.3
+ negotiated_protocol,
+ hashsign_algorithm = {undefined, undefined},
+ cert_hashsign_algorithm = {undefined, undefined},
+ %% key exchange
+ kex_algorithm :: ssl:kex_algo(),
+ kex_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
+ diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
+ srp_params :: #srp_user{} | secret_printout() | 'undefined',
+ public_key_info :: ssl_handshake:public_key_info() | 'undefined',
+ premaster_secret :: binary() | secret_printout() | 'undefined',
+ server_psk_identity :: binary() | 'undefined' % server psk identity hint
+ }).
+
+-record(connection_env, {
+ user_application :: {Monitor::reference(), User::pid()},
+ downgrade,
+ terminated = false ::boolean() | closed,
+ negotiated_version :: ssl_record:ssl_version() | 'undefined',
+ erl_dist_handle = undefined :: erlang:dist_handle() | 'undefined',
+ private_key :: public_key:private_key() | secret_printout() | 'undefined'
+ }).
+
-record(state, {
- role :: client | server,
- user_application :: {Monitor::reference(), User::pid()},
- transport_cb :: atom(), % callback module
- protocol_cb :: tls_connection | dtls_connection,
- data_tag :: atom(), % ex tcp.
- close_tag :: atom(), % ex tcp_closed
- error_tag :: atom(), % ex tcp_error
- host :: string() | inet:ip_address(),
- port :: integer(),
- socket :: port() | tuple(), %% TODO: dtls socket
- ssl_options :: #ssl_options{},
- socket_options :: #socket_options{},
- connection_states :: ssl_record:connection_states() | secret_printout(),
- protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl
- unprocessed_handshake_events = 0 :: integer(),
- tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
- | 'undefined',
- cert_db :: reference() | 'undefined',
- session :: #session{} | secret_printout(),
- session_cache :: db_handle(),
- session_cache_cb :: atom(),
- crl_db :: term(),
- negotiated_version :: ssl_record:ssl_version() | 'undefined',
- client_certificate_requested = false :: boolean(),
- key_algorithm :: ssl_cipher:key_algo(),
- hashsign_algorithm = {undefined, undefined},
- cert_hashsign_algorithm,
- public_key_info :: ssl_handshake:public_key_info() | 'undefined',
- private_key :: public_key:private_key() | secret_printout() | 'undefined',
- diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
- diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
- psk_identity :: binary() | 'undefined', % server psk identity hint
- srp_params :: #srp_user{} | secret_printout() | 'undefined',
- srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined',
- premaster_secret :: binary() | secret_printout() | 'undefined',
- file_ref_db :: db_handle(),
- cert_db_ref :: certdb_ref() | 'undefined',
- bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
- user_data_buffer :: undefined | binary() | secret_printout(),
- renegotiation :: undefined | {boolean(), From::term() | internal | peer},
- start_or_recv_from :: term(),
- timer :: undefined | reference(), % start_or_recive_timer
- %%send_queue :: queue:queue(),
- terminated = false ::boolean(),
- allow_renegotiate = true ::boolean(),
- expecting_next_protocol_negotiation = false ::boolean(),
- expecting_finished = false ::boolean(),
- next_protocol = undefined :: undefined | binary(),
- negotiated_protocol,
- tracker :: pid() | 'undefined', %% Tracker process for listen socket
- sni_hostname = undefined,
- downgrade,
- flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake
- %% to when possible pack more than on TLS record into the
- %% underlaying packet format. Introduced by DTLS - RFC 4347.
- %% The mecahnism is also usefull in TLS although we do not
- %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
- flight_state = reliable %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
- }).
+ static_env :: #static_env{},
+ connection_env :: #connection_env{} | secret_printout(),
+ ssl_options :: #ssl_options{},
+ socket_options :: #socket_options{},
+
+ %% Hanshake %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ handshake_env :: #handshake_env{} | secret_printout(),
+ %% Buffer of TLS/DTLS records, used during the TLS
+ %% handshake to when possible pack more than one TLS
+ %% record into the underlaying packet
+ %% format. Introduced by DTLS - RFC 4347. The
+ %% mecahnism is also usefull in TLS although we do not
+ %% need to worry about packet loss in TLS. In DTLS we
+ %% need to track DTLS handshake seqnr
+ flight_buffer = [] :: list() | map(),
+ client_certificate_requested = false :: boolean(),
+ protocol_specific = #{} :: map(),
+ session :: #session{} | secret_printout(),
+ key_share,
+ %% Data shuffling %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ connection_states :: ssl_record:connection_states() | secret_printout(),
+ protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr
+ user_data_buffer :: undefined | {[binary()],non_neg_integer(),[binary()]} | secret_printout(),
+ bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
+ %% recv and start handling
+ start_or_recv_from :: term(),
+ log_level
+ }).
+
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
#'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME,
base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}).
-define(WAIT_TO_ALLOW_RENEGOTIATION, 12000).
+
+%%----------------------------------------------------------------------
+%% TLS 1.3
+%%----------------------------------------------------------------------
+
+%% TLS 1.3 uses the same state record with the following differences:
+%%
+%% state :: record()
+%%
+%% session_cache - not implemented
+%% session_cache_cb - not implemented
+%% crl_db - not implemented
+%% client_hello_version - Bleichenbacher mitigation in TLS 1.2
+%% client_certificate_requested - Built into TLS 1.3 state machine
+%% key_algorithm - not used
+%% diffie_hellman_params - used in TLS 1.2 ECDH key exchange
+%% diffie_hellman_keys - used in TLS 1.2 ECDH key exchange
+%% psk_identity - not used
+%% srp_params - not used, no srp extension in TLS 1.3
+%% srp_keys - not used, no srp extension in TLS 1.3
+%% premaster_secret - not used
+%% renegotiation - TLS 1.3 forbids renegotiation
+%% hello - used in user_hello, handshake continue
+%% allow_renegotiate - TLS 1.3 forbids renegotiation
+%% expecting_next_protocol_negotiation - ALPN replaced NPN, depricated in TLS 1.3
+%% expecting_finished - not implemented, used by abbreviated
+%% next_protocol - ALPN replaced NPN, depricated in TLS 1.3
+%%
+%% connection_state :: map()
+%%
+%% compression_state - not used
+%% mac_secret - not used
+%% sequence_number - not used
+%% secure_renegotiation - not used, no renegotiation_info in TLS 1.3
+%% client_verify_data - not used, no renegotiation_info in TLS 1.3
+%% server_verify_data - not used, no renegotiation_info in TLS 1.3
+%% beast_mitigation - not used
+%%
+%% security_parameters :: map()
+%%
+%% cipher_type - TLS 1.3 uses only AEAD ciphers
+%% iv_size - not used
+%% key_size - not used
+%% key_material_length - not used
+%% expanded_key_material_length - used in SSL 3.0
+%% mac_algorithm - not used
+%% prf_algorithm - not used
+%% hash_size - not used
+%% compression_algorithm - not used
+%% master_secret - used for multiple secret types in TLS 1.3
+%% client_random - not used
+%% server_random - not used
+%% exportable - not used
+%%
+%% cipher_state :: record()
+%% nonce - used for sequence_number
+
-endif. % -ifdef(ssl_connection).
diff --git a/lib/ssl/src/ssl_connection_sup.erl b/lib/ssl/src/ssl_connection_sup.erl
index 1a1f43e683..934dd39df5 100644
--- a/lib/ssl/src/ssl_connection_sup.erl
+++ b/lib/ssl/src/ssl_connection_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1998-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.
@@ -51,12 +51,12 @@ init([]) ->
ListenOptionsTracker = listen_options_tracker_child_spec(),
DTLSConnetionManager = dtls_connection_manager_child_spec(),
- DTLSUdpListeners = dtls_udp_listeners_spec(),
+ DTLSListeners = dtls_listeners_spec(),
{ok, {{one_for_one, 10, 3600}, [TLSConnetionManager,
ListenOptionsTracker,
DTLSConnetionManager,
- DTLSUdpListeners
+ DTLSListeners
]}}.
@@ -91,9 +91,9 @@ listen_options_tracker_child_spec() ->
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-dtls_udp_listeners_spec() ->
- Name = dtls_udp_listener,
- StartFunc = {dtls_udp_sup, start_link, []},
+dtls_listeners_spec() ->
+ Name = dtls_listener,
+ StartFunc = {dtls_listener_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
Modules = [],
diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl
index 86c0207515..841620ce57 100644
--- a/lib/ssl/src/ssl_crl_cache.erl
+++ b/lib/ssl/src/ssl_crl_cache.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2015-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.
@@ -28,6 +28,10 @@
-behaviour(ssl_crl_cache_api).
+-export_type([crl_src/0, uri/0]).
+-type crl_src() :: {file, file:filename()} | {der, public_key:der_encoded()}.
+-type uri() :: uri_string:uri_string().
+
-export([lookup/3, select/2, fresh_crl/2]).
-export([insert/1, insert/2, delete/1]).
@@ -92,9 +96,9 @@ delete({der, CRLs}) ->
ssl_manager:delete_crls({?NO_DIST_POINT, CRLs});
delete(URI) ->
- case http_uri:parse(URI) of
- {ok, {http, _, _ , _, Path,_}} ->
- ssl_manager:delete_crls(string:strip(Path, left, $/));
+ case uri_string:normalize(URI, [return_map]) of
+ #{scheme := "http", path := Path} ->
+ ssl_manager:delete_crls(string:trim(Path, leading, "/"));
_ ->
{error, {only_http_distribution_points_supported, URI}}
end.
@@ -103,9 +107,9 @@ delete(URI) ->
%%% Internal functions
%%--------------------------------------------------------------------
do_insert(URI, CRLs) ->
- case http_uri:parse(URI) of
- {ok, {http, _, _ , _, Path,_}} ->
- ssl_manager:insert_crls(string:strip(Path, left, $/), CRLs);
+ case uri_string:normalize(URI, [return_map]) of
+ #{scheme := "http", path := Path} ->
+ ssl_manager:insert_crls(string:trim(Path, leading, "/"), CRLs);
_ ->
{error, {only_http_distribution_points_supported, URI}}
end.
@@ -161,8 +165,8 @@ http_get(URL, Rest, CRLDbInfo, Timeout) ->
cache_lookup(_, undefined) ->
[];
cache_lookup(URL, {{Cache, _}, _}) ->
- {ok, {_, _, _ , _, Path,_}} = http_uri:parse(URL),
- case ssl_pkix_db:lookup(string:strip(Path, left, $/), Cache) of
+ #{path := Path} = uri_string:normalize(URL, [return_map]),
+ case ssl_pkix_db:lookup(string:trim(Path, leading, "/"), Cache) of
undefined ->
[];
CRLs ->
diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl
index d5380583e7..8a750b3929 100644
--- a/lib/ssl/src/ssl_crl_cache_api.erl
+++ b/lib/ssl/src/ssl_crl_cache_api.erl
@@ -21,12 +21,15 @@
%%
-module(ssl_crl_cache_api).
-
-include_lib("public_key/include/public_key.hrl").
--type db_handle() :: term().
--type issuer_name() :: {rdnSequence, [#'AttributeTypeAndValue'{}]}.
+-export_type([dist_point/0, crl_cache_ref/0]).
+
+-type crl_cache_ref() :: any().
+-type issuer_name() :: {rdnSequence,[#'AttributeTypeAndValue'{}]}.
+-type dist_point() :: #'DistributionPoint'{}.
--callback lookup(#'DistributionPoint'{}, issuer_name(), db_handle()) -> not_available | [public_key:der_encoded()].
--callback select(issuer_name(), db_handle()) -> [public_key:der_encoded()].
--callback fresh_crl(#'DistributionPoint'{}, public_key:der_encoded()) -> public_key:der_encoded().
+
+-callback lookup(dist_point(), issuer_name(), crl_cache_ref()) -> not_available | [public_key:der_encoded()].
+-callback select(issuer_name(), crl_cache_ref()) -> [public_key:der_encoded()].
+-callback fresh_crl(dist_point(), public_key:der_encoded()) -> public_key:der_encoded().
diff --git a/lib/ssl/src/ssl_crl_hash_dir.erl b/lib/ssl/src/ssl_crl_hash_dir.erl
index bb62737232..9478ff9b78 100644
--- a/lib/ssl/src/ssl_crl_hash_dir.erl
+++ b/lib/ssl/src/ssl_crl_hash_dir.erl
@@ -20,6 +20,7 @@
-module(ssl_crl_hash_dir).
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
-behaviour(ssl_crl_cache_api).
@@ -55,7 +56,7 @@ select(Issuer, {_DbHandle, [{dir, Dir}]}) ->
%% is happy with that, but if it's true, this is an error.
[];
{error, Error} ->
- error_logger:error_report(
+ ?LOG_ERROR(
[{cannot_find_crl, Error},
{dir, Dir},
{module, ?MODULE},
@@ -86,7 +87,7 @@ find_crls(Issuer, Hash, Dir, N, Acc) ->
error:Error ->
%% Something is wrong with the file. Report
%% it, and try the next one.
- error_logger:error_report(
+ ?LOG_ERROR(
[{crl_parse_error, Error},
{filename, Filename},
{module, ?MODULE},
diff --git a/lib/ssl/src/ssl_dh_groups.erl b/lib/ssl/src/ssl_dh_groups.erl
new file mode 100644
index 0000000000..20d53de430
--- /dev/null
+++ b/lib/ssl/src/ssl_dh_groups.erl
@@ -0,0 +1,467 @@
+%%
+%% %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%
+%%
+
+-module(ssl_dh_groups).
+
+-include_lib("public_key/include/public_key.hrl").
+
+-export([modp2048_generator/0, modp2048_prime/0,
+ ffdhe2048_generator/0, ffdhe2048_prime/0,
+ ffdhe3072_generator/0, ffdhe3072_prime/0,
+ ffdhe4096_generator/0, ffdhe4096_prime/0,
+ ffdhe6144_generator/0, ffdhe6144_prime/0,
+ ffdhe8192_generator/0, ffdhe8192_prime/0,
+ dh_params/1]).
+
+%% RFC3526 - 2048-bit MODP Group
+%% This group is assigned id 14.
+%%
+%% This prime is: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
+%%
+%% Its hexadecimal value is:
+%%
+%% FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
+%% 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
+%% EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
+%% E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
+%% EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
+%% C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
+%% 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
+%% 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
+%% E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
+%% DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
+%% 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: 2.
+modp2048_generator() ->
+ 2.
+
+modp2048_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" "C4C6628B" "80DC1CD1"
+ "29024E08" "8A67CC74" "020BBEA6" "3B139B22" "514A0879" "8E3404DD"
+ "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" "4FE1356D" "6D51C245"
+ "E485B576" "625E7EC6" "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED"
+ "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" "49286651" "ECE45B3D"
+ "C2007CB8" "A163BF05" "98DA4836" "1C55D39A" "69163FA8" "FD24CF5F"
+ "83655D23" "DCA3AD96" "1C62F356" "208552BB" "9ED52907" "7096966D"
+ "670C354E" "4ABC9804" "F1746C08" "CA18217C" "32905E46" "2E36CE3B"
+ "E39E772C" "180E8603" "9B2783A2" "EC07A28F" "B5C55DF0" "6F4C52C9"
+ "DE2BCBF6" "95581718" "3995497C" "EA956AE5" "15D22618" "98FA0510"
+ "15728E5A" "8AACAA68" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% RFC8446 - TLS 1.3
+%%% RFC7919 - Negotiated FFDHE for TLS
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% ffdhe2048
+%% ---------
+%% The 2048-bit group has registry value 256 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^2048 - 2^1984 + {[2^1918 * e] + 560316 } * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 61285C97 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 103
+%% bits.
+ffdhe2048_generator() ->
+ 2.
+
+ffdhe2048_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "61285C97" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe3072
+%% ---------
+%% The 3072-bit prime has registry value 257 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^3072 - 2^3008 + {[2^2942 * e] + 2625351} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 66C62E37 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 125
+%% bits.
+ffdhe3072_generator() ->
+ 2.
+
+ffdhe3072_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "66C62E37" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe4096
+%% ---------
+%% The 4096-bit group has registry value 258 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^4096 - 2^4032 + {[2^3966 * e] + 5736041} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+%% 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+%% 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+%% A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+%% 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+%% 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E655F6A
+%% FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 150
+%% bits.
+ffdhe4096_generator() ->
+ 2.
+
+ffdhe4096_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "669E1EF1" "6E6F52C3" "164DF4FB"
+ "7930E9E4" "E58857B6" "AC7D5F42" "D69F6D18" "7763CF1D" "55034004"
+ "87F55BA5" "7E31CC7A" "7135C886" "EFB4318A" "ED6A1E01" "2D9E6832"
+ "A907600A" "918130C4" "6DC778F9" "71AD0038" "092999A3" "33CB8B7A"
+ "1A1DB93D" "7140003C" "2A4ECEA9" "F98D0ACC" "0A8291CD" "CEC97DCF"
+ "8EC9B55A" "7F88A46B" "4DB5A851" "F44182E1" "C68A007E" "5E655F6A"
+ "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe6144
+%% ---------
+%% The 6144-bit group has registry value 259 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^6144 - 2^6080 + {[2^6014 * e] + 15705020} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+%% 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+%% 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+%% A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+%% 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+%% 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902
+%% 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6
+%% 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A
+%% CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477
+%% A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3
+%% 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4
+%% 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6
+%% B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C
+%% D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A
+%% E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04
+%% 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1
+%% A41D570D 7938DAD4 A40E329C D0E40E65 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 175
+%% bits.
+ffdhe6144_generator() ->
+ 2.
+
+ffdhe6144_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "669E1EF1" "6E6F52C3" "164DF4FB"
+ "7930E9E4" "E58857B6" "AC7D5F42" "D69F6D18" "7763CF1D" "55034004"
+ "87F55BA5" "7E31CC7A" "7135C886" "EFB4318A" "ED6A1E01" "2D9E6832"
+ "A907600A" "918130C4" "6DC778F9" "71AD0038" "092999A3" "33CB8B7A"
+ "1A1DB93D" "7140003C" "2A4ECEA9" "F98D0ACC" "0A8291CD" "CEC97DCF"
+ "8EC9B55A" "7F88A46B" "4DB5A851" "F44182E1" "C68A007E" "5E0DD902"
+ "0BFD64B6" "45036C7A" "4E677D2C" "38532A3A" "23BA4442" "CAF53EA6"
+ "3BB45432" "9B7624C8" "917BDD64" "B1C0FD4C" "B38E8C33" "4C701C3A"
+ "CDAD0657" "FCCFEC71" "9B1F5C3E" "4E46041F" "388147FB" "4CFDB477"
+ "A52471F7" "A9A96910" "B855322E" "DB6340D8" "A00EF092" "350511E3"
+ "0ABEC1FF" "F9E3A26E" "7FB29F8C" "183023C3" "587E38DA" "0077D9B4"
+ "763E4E4B" "94B2BBC1" "94C6651E" "77CAF992" "EEAAC023" "2A281BF6"
+ "B3A739C1" "22611682" "0AE8DB58" "47A67CBE" "F9C9091B" "462D538C"
+ "D72B0374" "6AE77F5E" "62292C31" "1562A846" "505DC82D" "B854338A"
+ "E49F5235" "C95B9117" "8CCF2DD5" "CACEF403" "EC9D1810" "C6272B04"
+ "5B3B71F9" "DC6B80D6" "3FDD4A8E" "9ADB1E69" "62A69526" "D43161C1"
+ "A41D570D" "7938DAD4" "A40E329C" "D0E40E65" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe8192
+%% ---------
+%% The 8192-bit group has registry value 260 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^8192 - 2^8128 + {[2^8062 * e] + 10965728} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+%% 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+%% 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+%% A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+%% 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+%% 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902
+%% 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6
+%% 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A
+%% CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477
+%% A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3
+%% 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4
+%% 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6
+%% B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C
+%% D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A
+%% E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04
+%% 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1
+%% A41D570D 7938DAD4 A40E329C CFF46AAA 36AD004C F600C838
+%% 1E425A31 D951AE64 FDB23FCE C9509D43 687FEB69 EDD1CC5E
+%% 0B8CC3BD F64B10EF 86B63142 A3AB8829 555B2F74 7C932665
+%% CB2C0F1C C01BD702 29388839 D2AF05E4 54504AC7 8B758282
+%% 2846C0BA 35C35F5C 59160CC0 46FD8251 541FC68C 9C86B022
+%% BB709987 6A460E74 51A8A931 09703FEE 1C217E6C 3826E52C
+%% 51AA691E 0E423CFC 99E9E316 50C1217B 624816CD AD9A95F9
+%% D5B80194 88D9C0A0 A1FE3075 A577E231 83F81D4A 3F2FA457
+%% 1EFC8CE0 BA8A4FE8 B6855DFE 72B0A66E DED2FBAB FBE58A30
+%% FAFABE1C 5D71A87E 2F741EF8 C1FE86FE A6BBFDE5 30677F0D
+%% 97D11D49 F7A8443D 0822E506 A9F4614E 011E2A94 838FF88C
+%% D68C8BB7 C5C6424C FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 192
+%% bits.
+ffdhe8192_generator() ->
+ 2.
+
+ffdhe8192_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "669E1EF1" "6E6F52C3" "164DF4FB"
+ "7930E9E4" "E58857B6" "AC7D5F42" "D69F6D18" "7763CF1D" "55034004"
+ "87F55BA5" "7E31CC7A" "7135C886" "EFB4318A" "ED6A1E01" "2D9E6832"
+ "A907600A" "918130C4" "6DC778F9" "71AD0038" "092999A3" "33CB8B7A"
+ "1A1DB93D" "7140003C" "2A4ECEA9" "F98D0ACC" "0A8291CD" "CEC97DCF"
+ "8EC9B55A" "7F88A46B" "4DB5A851" "F44182E1" "C68A007E" "5E0DD902"
+ "0BFD64B6" "45036C7A" "4E677D2C" "38532A3A" "23BA4442" "CAF53EA6"
+ "3BB45432" "9B7624C8" "917BDD64" "B1C0FD4C" "B38E8C33" "4C701C3A"
+ "CDAD0657" "FCCFEC71" "9B1F5C3E" "4E46041F" "388147FB" "4CFDB477"
+ "A52471F7" "A9A96910" "B855322E" "DB6340D8" "A00EF092" "350511E3"
+ "0ABEC1FF" "F9E3A26E" "7FB29F8C" "183023C3" "587E38DA" "0077D9B4"
+ "763E4E4B" "94B2BBC1" "94C6651E" "77CAF992" "EEAAC023" "2A281BF6"
+ "B3A739C1" "22611682" "0AE8DB58" "47A67CBE" "F9C9091B" "462D538C"
+ "D72B0374" "6AE77F5E" "62292C31" "1562A846" "505DC82D" "B854338A"
+ "E49F5235" "C95B9117" "8CCF2DD5" "CACEF403" "EC9D1810" "C6272B04"
+ "5B3B71F9" "DC6B80D6" "3FDD4A8E" "9ADB1E69" "62A69526" "D43161C1"
+ "A41D570D" "7938DAD4" "A40E329C" "CFF46AAA" "36AD004C" "F600C838"
+ "1E425A31" "D951AE64" "FDB23FCE" "C9509D43" "687FEB69" "EDD1CC5E"
+ "0B8CC3BD" "F64B10EF" "86B63142" "A3AB8829" "555B2F74" "7C932665"
+ "CB2C0F1C" "C01BD702" "29388839" "D2AF05E4" "54504AC7" "8B758282"
+ "2846C0BA" "35C35F5C" "59160CC0" "46FD8251" "541FC68C" "9C86B022"
+ "BB709987" "6A460E74" "51A8A931" "09703FEE" "1C217E6C" "3826E52C"
+ "51AA691E" "0E423CFC" "99E9E316" "50C1217B" "624816CD" "AD9A95F9"
+ "D5B80194" "88D9C0A0" "A1FE3075" "A577E231" "83F81D4A" "3F2FA457"
+ "1EFC8CE0" "BA8A4FE8" "B6855DFE" "72B0A66E" "DED2FBAB" "FBE58A30"
+ "FAFABE1C" "5D71A87E" "2F741EF8" "C1FE86FE" "A6BBFDE5" "30677F0D"
+ "97D11D49" "F7A8443D" "0822E506" "A9F4614E" "011E2A94" "838FF88C"
+ "D68C8BB7" "C5C6424C" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+dh_params(ffdhe2048) ->
+ #'DHParameter'{
+ prime = ffdhe2048_prime(),
+ base = ffdhe2048_generator()};
+dh_params(ffdhe3072) ->
+ #'DHParameter'{
+ prime = ffdhe3072_prime(),
+ base = ffdhe3072_generator()};
+dh_params(ffdhe4096) ->
+ #'DHParameter'{
+ prime = ffdhe4096_prime(),
+ base = ffdhe4096_generator()};
+dh_params(ffdhe6144) ->
+ #'DHParameter'{
+ prime = ffdhe6144_prime(),
+ base = ffdhe6144_generator()};
+dh_params(ffdhe8192) ->
+ #'DHParameter'{
+ prime = ffdhe8192_prime(),
+ base = ffdhe8192_generator()}.
diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl
index 690b896919..bea67935d8 100644
--- a/lib/ssl/src/ssl_dist_sup.erl
+++ b/lib/ssl/src/ssl_dist_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2017. 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.
@@ -30,6 +30,9 @@
%% Supervisor callback
-export([init/1]).
+%% Debug
+-export([consult/1]).
+
%%%=========================================================================
%%% API
%%%=========================================================================
@@ -37,7 +40,18 @@
-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() ->
- supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+ case init:get_argument(ssl_dist_optfile) of
+ {ok, [File]} ->
+ DistOpts = consult(File),
+ TabOpts = [set, protected, named_table],
+ Tab = ets:new(ssl_dist_opts, TabOpts),
+ true = ets:insert(Tab, DistOpts),
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []);
+ {ok, BadArg} ->
+ error({bad_ssl_dist_optfile, BadArg});
+ error ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [])
+ end.
%%%=========================================================================
%%% Supervisor callback
@@ -46,8 +60,7 @@ start_link() ->
init([]) ->
AdminSup = ssl_admin_child_spec(),
ConnectionSup = ssl_connection_sup(),
- ProxyServer = proxy_server_child_spec(),
- {ok, {{one_for_all, 10, 3600}, [AdminSup, ProxyServer, ConnectionSup]}}.
+ {ok, {{one_for_all, 10, 3600}, [AdminSup, ConnectionSup]}}.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -70,11 +83,51 @@ ssl_connection_sup() ->
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-proxy_server_child_spec() ->
- Name = ssl_tls_dist_proxy,
- StartFunc = {ssl_tls_dist_proxy, start_link, []},
- Restart = permanent,
- Shutdown = 4000,
- Modules = [ssl_tls_dist_proxy],
- Type = worker,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+consult(File) ->
+ case erl_prim_loader:get_file(File) of
+ {ok, Binary, _FullName} ->
+ Encoding =
+ case epp:read_encoding_from_binary(Binary) of
+ none -> latin1;
+ Enc -> Enc
+ end,
+ case unicode:characters_to_list(Binary, Encoding) of
+ {error, _String, Rest} ->
+ error(
+ {bad_ssl_dist_optfile, {encoding_error, Rest}});
+ {incomplete, _String, Rest} ->
+ error(
+ {bad_ssl_dist_optfile, {encoding_incomplete, Rest}});
+ String when is_list(String) ->
+ consult_string(String)
+ end;
+ error ->
+ error({bad_ssl_dist_optfile, File})
+ end.
+
+consult_string(String) ->
+ case erl_scan:string(String) of
+ {error, Info, Location} ->
+ error({bad_ssl_dist_optfile, {scan_error, Info, Location}});
+ {ok, Tokens, _EndLocation} ->
+ consult_tokens(Tokens)
+ end.
+
+consult_tokens(Tokens) ->
+ case erl_parse:parse_exprs(Tokens) of
+ {error, Info} ->
+ error({bad_ssl_dist_optfile, {parse_error, Info}});
+ {ok, [Expr]} ->
+ consult_expr(Expr);
+ {ok, Other} ->
+ error({bad_ssl_dist_optfile, {parse_error, Other}})
+ end.
+
+consult_expr(Expr) ->
+ {value, Value, Bs} = erl_eval:expr(Expr, erl_eval:new_bindings()),
+ case erl_eval:bindings(Bs) of
+ [] ->
+ Value;
+ Other ->
+ error({bad_ssl_dist_optfile, {bindings, Other}})
+ end.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index e84473f215..bd2efa9fbb 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-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.
@@ -30,6 +30,7 @@
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
+-include("tls_handshake_1_3.hrl").
-include_lib("public_key/include/public_key.hrl").
-export_type([ssl_handshake/0, ssl_handshake_history/0,
@@ -38,52 +39,56 @@
-type oid() :: tuple().
-type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term().
-type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}.
--type ssl_handshake_history() :: {[binary()], [binary()]}.
+-type ssl_handshake_history() :: {iodata(), iodata()}.
-type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} |
#client_key_exchange{} | #finished{} | #certificate_verify{} |
#hello_request{} | #next_protocol{}.
-%% Handshake messages
+%% Create handshake messages
-export([hello_request/0, server_hello/4, server_hello_done/0,
- certificate/4, certificate_request/5, key_exchange/3,
+ certificate/4, client_certificate_verify/6, certificate_request/5, key_exchange/3,
finished/5, next_protocol/1]).
%% Handle handshake messages
--export([certify/10, client_certificate_verify/6, certificate_verify/6, verify_signature/5,
+-export([certify/7, certificate_verify/6, verify_signature/5,
master_secret/4, server_key_exchange_hash/2, verify_connection/6,
- init_handshake_history/0, update_handshake_history/3, verify_server_key/5
+ init_handshake_history/0, update_handshake_history/2, verify_server_key/5,
+ select_version/3, select_supported_version/2, extension_value/1
]).
-%% Encode/Decode
--export([encode_handshake/2, encode_hello_extensions/1,
- encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1,
- decode_handshake/3, decode_hello_extensions/1,
+%% Encode
+-export([encode_handshake/2, encode_hello_extensions/2, encode_extensions/1, encode_extensions/2,
+ encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]).
+%% Decode
+-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3,
decode_server_key/3, decode_client_key/3,
decode_suites/2
]).
%% Cipher suites handling
--export([available_suites/2, available_signature_algs/3, cipher_suites/2,
- select_session/11, supported_ecc/1, available_signature_algs/4]).
+-export([available_suites/2, available_signature_algs/2, available_signature_algs/4,
+ cipher_suites/3, prf/6, select_session/11, supported_ecc/1,
+ premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%% Extensions handling
-export([client_hello_extensions/6,
handle_client_hello_extensions/9, %% Returns server hello extensions
- handle_server_hello_extensions/9, select_curve/2, select_curve/3
+ handle_server_hello_extensions/9, 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
]).
-%% MISC
--export([select_version/3, prf/6, select_hashsign/4, select_hashsign/5,
- select_hashsign_algs/3,
- premaster_secret/2, premaster_secret/3, premaster_secret/4]).
+-export([get_cert_params/1,
+ server_name/3,
+ validation_fun_and_state/9,
+ handle_path_validation_error/7]).
%%====================================================================
-%% Internal application API
+%% Create handshake messages
%%====================================================================
-%% ---------- Create handshake messages ----------
-
%%--------------------------------------------------------------------
-spec hello_request() -> #hello_request{}.
%%
@@ -94,8 +99,8 @@ hello_request() ->
#hello_request{}.
%%--------------------------------------------------------------------
--spec server_hello(#session{}, ssl_record:ssl_version(), ssl_record:connection_states(),
- #hello_extensions{}) -> #server_hello{}.
+%%-spec server_hello(binary(), ssl_record:ssl_version(), ssl_record:connection_states(),
+%% Extension::map()) -> #server_hello{}.
%%
%% Description: Creates a server hello message.
%%--------------------------------------------------------------------
@@ -119,32 +124,6 @@ server_hello(SessionId, Version, ConnectionStates, Extensions) ->
server_hello_done() ->
#server_hello_done{}.
-client_hello_extensions(Host, Version, CipherSuites,
- #ssl_options{signature_algs = SupportedHashSigns,
- eccs = SupportedECCs,
- versions = AllVersions} = SslOpts, ConnectionStates, Renegotiation) ->
- {EcPointFormats, EllipticCurves} =
- case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of
- true ->
- client_ecc_extensions(SupportedECCs);
- false ->
- {undefined, undefined}
- end,
- SRP = srp_user(SslOpts),
-
- #hello_extensions{
- renegotiation_info = renegotiation_info(tls_record, client,
- ConnectionStates, Renegotiation),
- srp = SRP,
- signature_algs = available_signature_algs(SupportedHashSigns, Version, AllVersions),
- ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves,
- alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation),
- next_protocol_negotiation =
- encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
- Renegotiation),
- sni = sni(Host, SslOpts#ssl_options.server_name_indication)}.
-
%%--------------------------------------------------------------------
-spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}.
%%
@@ -167,19 +146,11 @@ certificate(OwnCert, CertDbHandle, CertDbRef, server) ->
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, Chain} ->
#certificate{asn1_certificates = Chain};
- {error, _} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, server_has_no_suitable_certificates)
+ {error, Error} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
end.
%%--------------------------------------------------------------------
--spec next_protocol(binary()) -> #next_protocol{}.
-%%
-%% Description: Creates a next protocol message
-%%-------------------------------------------------------------------
-next_protocol(SelectedProtocol) ->
- #next_protocol{selected_protocol = SelectedProtocol}.
-
-%%--------------------------------------------------------------------
-spec client_certificate_verify(undefined | der_cert(), binary(),
ssl_record:ssl_version(), term(), public_key:private_key(),
ssl_handshake_history()) ->
@@ -205,14 +176,14 @@ client_certificate_verify(OwnCert, MasterSecret, Version,
end.
%%--------------------------------------------------------------------
--spec certificate_request(ssl_cipher:cipher_suite(), db_handle(),
+-spec certificate_request(ssl_cipher_format:cipher_suite(), db_handle(),
certdb_ref(), #hash_sign_algos{}, ssl_record:ssl_version()) ->
#certificate_request{}.
%%
%% Description: Creates a certificate_request message, called by the server.
%%--------------------------------------------------------------------
certificate_request(CipherSuite, CertDbHandle, CertDbRef, HashSigns, Version) ->
- Types = certificate_types(ssl_cipher:suite_definition(CipherSuite), Version),
+ Types = certificate_types(ssl_cipher_format:suite_bin_to_map(CipherSuite), Version),
Authorities = certificate_authorities(CertDbHandle, CertDbRef),
#certificate_request{
certificate_types = Types,
@@ -225,11 +196,18 @@ certificate_request(CipherSuite, CertDbHandle, CertDbRef, HashSigns, Version) ->
{dh, binary()} |
{dh, {binary(), binary()}, #'DHParameter'{}, {HashAlgo::atom(), SignAlgo::atom()},
binary(), binary(), public_key:private_key()} |
+ {ecdh, _, _, _, _, _} |
{ecdh, #'ECPrivateKey'{}} |
+ {psk, _, _, _, _, _} |
{psk, binary()} |
+ {dhe_psk, _, _, _, _, _, _, _} |
{dhe_psk, binary(), binary()} |
+ {ecdhe_psk, _, _, _, _, _, _} |
+ {ecdhe_psk, binary(), #'ECPrivateKey'{}} |
{srp, {binary(), binary()}, #srp_user{}, {HashAlgo::atom(), SignAlgo::atom()},
- binary(), binary(), public_key:private_key()}) ->
+ binary(), binary(), public_key:private_key()} |
+ {srp, _} |
+ {psk_premaster_secret, _, _, _}) ->
#client_key_exchange{} | #server_key_exchange{}.
%%
@@ -265,6 +243,13 @@ key_exchange(client, _Version, {dhe_psk, Identity, PublicKey}) ->
dh_public = PublicKey}
};
+key_exchange(client, _Version, {ecdhe_psk, Identity, #'ECPrivateKey'{publicKey = ECPublicKey}}) ->
+ #client_key_exchange{
+ exchange_keys = #client_ecdhe_psk_identity{
+ identity = Identity,
+ dh_public = ECPublicKey}
+ };
+
key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, PublicKey, _}}) ->
EncPremasterSecret =
encrypted_premaster_secret(Secret, PublicKey),
@@ -311,6 +296,16 @@ key_exchange(server, Version, {dhe_psk, PskIdentityHint, {PublicKey, _},
enc_server_key_exchange(Version, ServerEDHPSKParams,
HashSign, ClientRandom, ServerRandom, PrivateKey);
+key_exchange(server, Version, {ecdhe_psk, PskIdentityHint,
+ #'ECPrivateKey'{publicKey = ECPublicKey,
+ parameters = ECCurve},
+ HashSign, ClientRandom, ServerRandom, PrivateKey}) ->
+ ServerECDHEPSKParams = #server_ecdhe_psk_params{
+ hint = PskIdentityHint,
+ dh_params = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}},
+ enc_server_key_exchange(Version, ServerECDHEPSKParams, HashSign,
+ ClientRandom, ServerRandom, PrivateKey);
+
key_exchange(server, Version, {srp, {PublicKey, _},
#srp_user{generator = Generator, prime = Prime,
salt = Salt},
@@ -329,23 +324,52 @@ key_exchange(server, Version, {srp, {PublicKey, _},
finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the current handshake
#finished{verify_data =
calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake)}.
+%%--------------------------------------------------------------------
+-spec next_protocol(binary()) -> #next_protocol{}.
+%%
+%% Description: Creates a next protocol message
+%%-------------------------------------------------------------------
+next_protocol(SelectedProtocol) ->
+ #next_protocol{selected_protocol = SelectedProtocol}.
-%% ---------- Handle handshake messages ----------
-
-verify_server_key(#server_key_params{params_bin = EncParams,
- signature = Signature},
- HashSign = {HashAlgo, _},
- ConnectionStates, Version, PubKeyInfo) ->
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Hash = server_key_exchange_hash(HashAlgo,
- <<ClientRandom/binary,
- ServerRandom/binary,
- EncParams/binary>>),
- verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo).
+%%====================================================================
+%% Handle handshake messages
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec certify(#certificate{}, db_handle(), certdb_ref(), #ssl_options{}, term(),
+ client | server, inet:hostname() | inet:ip_address()) -> {der_cert(), public_key_info()} | #alert{}.
+%%
+%% Description: Handles a certificate handshake message
+%%--------------------------------------------------------------------
+certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
+ Opts, CRLDbHandle, Role, Host) ->
+ ServerName = server_name(Opts#ssl_options.server_name_indication, Host, Role),
+ [PeerCert | ChainCerts ] = ASN1Certs,
+ try
+ {TrustedCert, CertPath} =
+ ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef,
+ Opts#ssl_options.partial_chain),
+ ValidationFunAndState = validation_fun_and_state(Opts#ssl_options.verify_fun, Role,
+ CertDbHandle, CertDbRef, ServerName,
+ Opts#ssl_options.customize_hostname_check,
+ Opts#ssl_options.crl_check, CRLDbHandle, CertPath),
+ Options = [{max_path_length, Opts#ssl_options.depth},
+ {verify_fun, ValidationFunAndState}],
+ case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
+ {ok, {PublicKeyInfo,_}} ->
+ {PeerCert, PublicKeyInfo};
+ {error, Reason} ->
+ handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options,
+ CertDbHandle, CertDbRef)
+ end
+ catch
+ error:{badmatch,{error, {asn1, Asn1Reason}}} ->
+ %% ASN-1 decode of certificate somehow failed
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason});
+ error:OtherReason ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})
+ end.
%%--------------------------------------------------------------------
-spec certificate_verify(binary(), public_key_info(), ssl_record:ssl_version(), term(),
binary(), ssl_handshake_history()) -> valid | #alert{}.
@@ -387,39 +411,55 @@ verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature,
{?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}).
-
%%--------------------------------------------------------------------
--spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit,
- verify_peer | verify_none, {fun(), term}, fun(), term(), term(),
- client | server) -> {der_cert(), public_key_info()} | #alert{}.
+-spec master_secret(ssl_record:ssl_version(), #session{} | binary(), ssl_record:connection_states(),
+ client | server) -> {binary(), ssl_record:connection_states()} | #alert{}.
%%
-%% Description: Handles a certificate handshake message
-%%--------------------------------------------------------------------
-certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
- MaxPathLen, _Verify, ValidationFunAndState0, PartialChain, CRLCheck, CRLDbHandle, Role) ->
- [PeerCert | _] = ASN1Certs,
- try
- {TrustedCert, CertPath} =
- ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, PartialChain),
- ValidationFunAndState = validation_fun_and_state(ValidationFunAndState0, Role,
- CertDbHandle, CertDbRef,
- CRLCheck, CRLDbHandle, CertPath),
- case public_key:pkix_path_validation(TrustedCert,
- CertPath,
- [{max_path_length, MaxPathLen},
- {verify_fun, ValidationFunAndState}]) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
- {error, Reason} ->
- path_validation_alert(Reason)
- end
+%% Description: Sets or calculates the master secret and calculate keys,
+%% updating the pending connection states. The Mastersecret and the update
+%% connection states are returned or an alert if the calculation fails.
+%%-------------------------------------------------------------------
+master_secret(Version, #session{master_secret = Mastersecret},
+ ConnectionStates, Role) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ try master_secret(Version, Mastersecret, SecParams,
+ ConnectionStates, Role)
catch
- error:_ ->
- %% ASN-1 decode of certificate somehow failed
- ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, failed_to_decode_certificate)
+ exit:_ ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure)
+ end;
+
+master_secret(Version, PremasterSecret, ConnectionStates, Role) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+
+ #security_parameters{prf_algorithm = PrfAlgo,
+ client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ try master_secret(Version,
+ calc_master_secret(Version,PrfAlgo,PremasterSecret,
+ ClientRandom, ServerRandom),
+ SecParams, ConnectionStates, Role)
+ catch
+ exit:_ ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure)
end.
%%--------------------------------------------------------------------
+-spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary().
+%%
+%% Description: Calculate server key exchange hash
+%%--------------------------------------------------------------------
+server_key_exchange_hash(md5sha, Value) ->
+ MD5 = crypto:hash(md5, Value),
+ SHA = crypto:hash(sha, Value),
+ <<MD5/binary, SHA/binary>>;
+
+server_key_exchange_hash(Hash, Value) ->
+ crypto:hash(Hash, Value).
+
+%%--------------------------------------------------------------------
-spec verify_connection(ssl_record:ssl_version(), #finished{}, client | server, integer(), binary(),
ssl_handshake_history()) -> verified | #alert{}.
%%
@@ -446,303 +486,62 @@ init_handshake_history() ->
{[], []}.
%%--------------------------------------------------------------------
--spec update_handshake_history(ssl_handshake:ssl_handshake_history(), Data ::term(), boolean()) ->
+-spec update_handshake_history(ssl_handshake:ssl_handshake_history(), Data ::term()) ->
ssl_handshake:ssl_handshake_history().
%%
%% Description: Update the handshake history buffer with Data.
%%--------------------------------------------------------------------
-update_handshake_history(Handshake, % special-case SSL2 client hello
- <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor),
- ?UINT16(CSLength), ?UINT16(0),
- ?UINT16(CDLength),
- CipherSuites:CSLength/binary,
- ChallengeData:CDLength/binary>>, true) ->
- update_handshake_history(Handshake,
- <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor),
- ?UINT16(CSLength), ?UINT16(0),
- ?UINT16(CDLength),
- CipherSuites:CSLength/binary,
- ChallengeData:CDLength/binary>>, true);
-update_handshake_history({Handshake0, _Prev}, Data, _) ->
+update_handshake_history({Handshake0, _Prev}, Data) ->
{[Data|Handshake0], Handshake0}.
-%% %%--------------------------------------------------------------------
-%% -spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary().
+verify_server_key(#server_key_params{params_bin = EncParams,
+ signature = Signature},
+ HashSign = {HashAlgo, _},
+ ConnectionStates, Version, PubKeyInfo) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Hash = server_key_exchange_hash(HashAlgo,
+ <<ClientRandom/binary,
+ ServerRandom/binary,
+ EncParams/binary>>),
+ verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo).
-%% %%
-%% %% Description: Public key decryption using the private key.
-%% %%--------------------------------------------------------------------
-%% decrypt_premaster_secret(Secret, RSAPrivateKey) ->
-%% try public_key:decrypt_private(Secret, RSAPrivateKey,
-%% [{rsa_pad, rsa_pkcs1_padding}])
-%% catch
-%% _:_ ->
-%% throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
-%% end.
+select_version(RecordCB, ClientVersion, Versions) ->
+ do_select_version(RecordCB, ClientVersion, Versions).
-premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) ->
- try
- public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params)
- catch
- error:computation_failed ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
- end;
-premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) ->
- try
- crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base])
- catch
- error:computation_failed ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
- end;
-premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime,
- verifier = Verifier}) ->
- case crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of
- error ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
- PremasterSecret ->
- PremasterSecret
- end;
-premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public},
- ClientKeys, {Username, Password}) ->
- case ssl_srp_primes:check_srp_params(Generator, Prime) of
- ok ->
- DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]),
- case crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of
- error ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
- PremasterSecret ->
- PremasterSecret
- end;
- _ ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
- end;
-premaster_secret(#client_rsa_psk_identity{
- identity = PSKIdentity,
- exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS}
- }, #'RSAPrivateKey'{} = Key, PSKLookup) ->
- PremasterSecret = premaster_secret(EncPMS, Key),
- psk_secret(PSKIdentity, PSKLookup, PremasterSecret);
-premaster_secret(#server_dhe_psk_params{
- hint = IdentityHint,
- dh_params = #server_dh_params{dh_y = PublicDhKey} = Params},
- PrivateDhKey,
- LookupFun) ->
- PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params),
- psk_secret(IdentityHint, LookupFun, PremasterSecret);
-premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) ->
- psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret).
-premaster_secret(#client_dhe_psk_identity{
- identity = PSKIdentity,
- dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) ->
- PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params),
- psk_secret(PSKIdentity, PSKLookup, PremasterSecret).
-premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) ->
- psk_secret(PSKIdentity, PSKLookup);
-premaster_secret({psk, PSKIdentity}, PSKLookup) ->
- psk_secret(PSKIdentity, PSKLookup);
-premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) ->
- public_key:compute_key(ECPoint, ECDHKeys);
-premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) ->
- try public_key:decrypt_private(EncSecret, RSAPrivateKey,
- [{rsa_pad, rsa_pkcs1_padding}])
- catch
- _:_ ->
- throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
+%% Called by TLS 1.2/1.3 Server when "supported_versions" is present
+%% in ClientHello.
+%% Input lists are ordered (highest first)
+select_supported_version([], _ServerVersions) ->
+ undefined;
+select_supported_version([ClientVersion|T], ServerVersions) ->
+ case lists:member(ClientVersion, ServerVersions) of
+ true ->
+ ClientVersion;
+ false ->
+ select_supported_version(T, ServerVersions)
end.
-%%--------------------------------------------------------------------
--spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary().
-%%
-%% Description: Calculate server key exchange hash
-%%--------------------------------------------------------------------
-server_key_exchange_hash(md5sha, Value) ->
- MD5 = crypto:hash(md5, Value),
- SHA = crypto:hash(sha, Value),
- <<MD5/binary, SHA/binary>>;
-server_key_exchange_hash(Hash, Value) ->
- crypto:hash(Hash, Value).
-%%--------------------------------------------------------------------
--spec prf(ssl_record:ssl_version(), non_neg_integer(), binary(), binary(), [binary()], non_neg_integer()) ->
- {ok, binary()} | {error, undefined}.
-%%
-%% 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)}.
-
-
-%%--------------------------------------------------------------------
--spec select_hashsign(#hash_sign_algos{} | undefined, undefined | binary(),
- atom(), [atom()], ssl_record:ssl_version()) ->
- {atom(), atom()} | undefined | #alert{}.
-
-%%
-%% Description: Handles signature_algorithms hello extension (server)
-%%--------------------------------------------------------------------
-select_hashsign(_, undefined, _, _, _Version) ->
- {null, anon};
-%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have
-%% negotiated a lower version.
-select_hashsign(HashSigns, Cert, KeyExAlgo,
- undefined, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3->
- select_hashsign(HashSigns, Cert, KeyExAlgo, tls_v1:default_signature_algs(Version), Version);
-select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns,
- {Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
- #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPCertificate'{tbsCertificate = TBSCert,
- signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} =
- TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
-
- Sign = sign_algo(SignAlgo),
- SubSing = sign_algo(SubjAlgo),
-
- case lists:filter(fun({_, S} = Algos) when S == Sign ->
- is_acceptable_hash_sign(Algos, Sign,
- SubSing, KeyExAlgo, SupportedHashSigns);
- (_) ->
- false
- end, HashSigns) of
- [] ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
- [HashSign | _] ->
- HashSign
- end;
-select_hashsign(_, Cert, _, _, Version) ->
- #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- select_hashsign_algs(undefined, Algo, Version).
-%%--------------------------------------------------------------------
--spec select_hashsign(#certificate_request{}, binary(),
- [atom()], ssl_record:ssl_version()) ->
- {atom(), atom()} | #alert{}.
-
-%%
-%% Description: Handles signature algorithms selection for certificate requests (client)
-%%--------------------------------------------------------------------
-select_hashsign(#certificate_request{}, undefined, _, {Major, Minor}) when Major >= 3 andalso Minor >= 3->
- %% There 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.
- {undefined, undefined};
-
-select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSigns},
- certificate_types = Types}, Cert, SupportedHashSigns,
- {Major, Minor}) when Major >= 3 andalso Minor >= 3->
- #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPCertificate'{tbsCertificate = TBSCert,
- signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} =
- TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- Sign = sign_algo(SignAlgo),
- SubSign = sign_algo(SubjAlgo),
-
- case is_acceptable_cert_type(SubSign, HashSigns, Types) andalso is_supported_sign(Sign, HashSigns) of
- true ->
- case lists:filter(fun({_, S} = Algos) when S == SubSign ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
- (_) ->
- false
- end, HashSigns) of
- [] ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
- [HashSign | _] ->
- HashSign
- end;
- false ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
- end;
-select_hashsign(#certificate_request{}, Cert, _, Version) ->
- select_hashsign(undefined, Cert, undefined, [], Version).
-
-%%--------------------------------------------------------------------
--spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) ->
- {atom(), atom()}.
-
-%% Description: For TLS 1.2 hash function and signature algorithm pairs can be
-%% negotiated with the signature_algorithms extension,
-%% for previous versions always use appropriate defaults.
-%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms
-%% If the client does not send the signature_algorithms extension, the
-%% server MUST do the following: (e.i defaults for TLS 1.2)
-%%
-%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA,
-%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had
-%% sent the value {sha1,rsa}.
-%%
-%% - If the negotiated key exchange algorithm is one of (DHE_DSS,
-%% DH_DSS), behave as if the client had sent the value {sha1,dsa}.
-%%
-%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA,
-%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
-
-%%--------------------------------------------------------------------
-select_hashsign_algs(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso
- Major >= 3 andalso Minor >= 3 ->
- HashSign;
-select_hashsign_algs(undefined, ?rsaEncryption, {Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
- {sha, rsa};
-select_hashsign_algs(undefined,?'id-ecPublicKey', _) ->
- {sha, ecdsa};
-select_hashsign_algs(undefined, ?rsaEncryption, _) ->
- {md5sha, rsa};
-select_hashsign_algs(undefined, ?'id-dsa', _) ->
- {sha, dsa}.
-
-
-%%--------------------------------------------------------------------
--spec master_secret(ssl_record:ssl_version(), #session{} | binary(), ssl_record:connection_states(),
- client | server) -> {binary(), ssl_record:connection_states()} | #alert{}.
-%%
-%% Description: Sets or calculates the master secret and calculate keys,
-%% updating the pending connection states. The Mastersecret and the update
-%% connection states are returned or an alert if the calculation fails.
-%%-------------------------------------------------------------------
-master_secret(Version, #session{master_secret = Mastersecret},
- ConnectionStates, Role) ->
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates, read),
- try master_secret(Version, Mastersecret, SecParams,
- ConnectionStates, Role)
- catch
- exit:_ ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure)
- end;
-
-master_secret(Version, PremasterSecret, ConnectionStates, Role) ->
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates, read),
-
- #security_parameters{prf_algorithm = PrfAlgo,
- client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- try master_secret(Version,
- calc_master_secret(Version,PrfAlgo,PremasterSecret,
- ClientRandom, ServerRandom),
- SecParams, ConnectionStates, Role)
- catch
- exit:_ ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure)
- end.
+%%====================================================================
+%% Encode handshake
+%%====================================================================
-%%-------------Encode/Decode --------------------------------
encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version) ->
PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32),
{?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary,
?BYTE(PaddingLength), 0:(PaddingLength * 8)>>};
-
-encode_handshake(#server_hello{server_version = {Major, Minor},
+encode_handshake(#server_hello{server_version = {Major, Minor} = Version,
random = Random,
session_id = Session_ID,
cipher_suite = CipherSuite,
compression_method = Comp_method,
- extensions = #hello_extensions{} = Extensions}, _Version) ->
+ extensions = Extensions}, _Version) ->
SID_length = byte_size(Session_ID),
- ExtensionsBin = encode_hello_extensions(Extensions),
+ ExtensionsBin = encode_hello_extensions(Extensions, Version),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID/binary,
CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>};
@@ -790,135 +589,146 @@ encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = Ha
encode_handshake(#finished{verify_data = VerifyData}, _Version) ->
{?FINISHED, VerifyData}.
-encode_hello_extensions(#hello_extensions{} = Extensions) ->
- encode_hello_extensions(hello_extensions_list(Extensions), <<>>).
-encode_hello_extensions([], <<>>) ->
+encode_hello_extensions(_, {3, 0}) ->
<<>>;
-encode_hello_extensions([], Acc) ->
+encode_hello_extensions(Extensions, _) ->
+ encode_extensions(hello_extensions_list(Extensions), <<>>).
+
+encode_extensions(Exts) ->
+ encode_extensions(Exts, <<>>).
+
+encode_extensions([], <<>>) ->
+ <<?UINT16(0)>>;
+encode_extensions([], Acc) ->
Size = byte_size(Acc),
<<?UINT16(Size), Acc/binary>>;
-
-encode_hello_extensions([#alpn{extension_data = ExtensionData} | Rest], Acc) ->
- Len = byte_size(ExtensionData),
+encode_extensions([#alpn{extension_data = ExtensionData} | Rest], Acc) ->
+ Len = byte_size(ExtensionData),
ExtLen = Len + 2,
- encode_hello_extensions(Rest, <<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
- ExtensionData/binary, Acc/binary>>);
-encode_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) ->
+ encode_extensions(Rest, <<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
+ ExtensionData/binary, Acc/binary>>);
+encode_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) ->
Len = byte_size(ExtensionData),
- encode_hello_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len),
+ encode_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len),
ExtensionData/binary, Acc/binary>>);
-encode_hello_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) ->
- encode_hello_extensions(Rest, Acc);
-encode_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) ->
+encode_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) ->
+ encode_extensions(Rest, Acc);
+encode_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) ->
Len = byte_size(Info),
- encode_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>);
+ encode_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>);
-encode_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) ->
+encode_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) ->
InfoLen = byte_size(Info),
Len = InfoLen +1,
- encode_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen),
+ encode_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen),
Info/binary, Acc/binary>>);
-encode_hello_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) ->
+encode_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) ->
EllipticCurveList = << <<(tls_v1:oid_to_enum(X)):16>> || X <- EllipticCurves>>,
ListLen = byte_size(EllipticCurveList),
Len = ListLen + 2,
- encode_hello_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT),
+ encode_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT),
?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary, Acc/binary>>);
-encode_hello_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) ->
+encode_extensions([#supported_groups{supported_groups = SupportedGroups} | Rest], Acc) ->
+
+ SupportedGroupList = << <<(tls_v1:group_to_enum(X)):16>> || X <- SupportedGroups>>,
+ ListLen = byte_size(SupportedGroupList),
+ Len = ListLen + 2,
+ encode_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT),
+ ?UINT16(Len), ?UINT16(ListLen),
+ SupportedGroupList/binary, Acc/binary>>);
+encode_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) ->
ECPointFormatList = list_to_binary(ECPointFormats),
ListLen = byte_size(ECPointFormatList),
Len = ListLen + 1,
- encode_hello_extensions(Rest, <<?UINT16(?EC_POINT_FORMATS_EXT),
+ encode_extensions(Rest, <<?UINT16(?EC_POINT_FORMATS_EXT),
?UINT16(Len), ?BYTE(ListLen), ECPointFormatList/binary, Acc/binary>>);
-encode_hello_extensions([#srp{username = UserName} | Rest], Acc) ->
+encode_extensions([#srp{username = UserName} | Rest], Acc) ->
SRPLen = byte_size(UserName),
- Len = SRPLen + 2,
- encode_hello_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
+ Len = SRPLen + 1,
+ encode_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
UserName/binary, Acc/binary>>);
-encode_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) ->
+encode_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) ->
SignAlgoList = << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> ||
{Hash, Sign} <- HashSignAlgos >>,
ListLen = byte_size(SignAlgoList),
Len = ListLen + 2,
- encode_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
+ encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>);
-encode_hello_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
+encode_extensions([#signature_algorithms{
+ signature_scheme_list = SignatureSchemes} | Rest], Acc) ->
+ SignSchemeList = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> ||
+ SignatureScheme <- SignatureSchemes >>,
+ ListLen = byte_size(SignSchemeList),
+ Len = ListLen + 2,
+ encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
+ ?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>);
+encode_extensions([#signature_algorithms_cert{
+ signature_scheme_list = SignatureSchemes} | Rest], Acc) ->
+ SignSchemeList = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> ||
+ SignatureScheme <- SignatureSchemes >>,
+ ListLen = byte_size(SignSchemeList),
+ Len = ListLen + 2,
+ encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT),
+ ?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>);
+encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
HostLen = length(Hostname),
HostnameBin = list_to_binary(Hostname),
% Hostname type (1 byte) + Hostname length (2 bytes) + Hostname (HostLen bytes)
ServerNameLength = 1 + 2 + HostLen,
% ServerNameListSize (2 bytes) + ServerNameLength
ExtLength = 2 + ServerNameLength,
- encode_hello_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength),
- ?UINT16(ServerNameLength),
- ?BYTE(?SNI_NAMETYPE_HOST_NAME),
- ?UINT16(HostLen), HostnameBin/binary,
- Acc/binary>>).
-
-enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo},
- ClientRandom, ServerRandom, PrivateKey) ->
- EncParams = encode_server_key(Params),
- case HashAlgo of
- null ->
- #server_key_params{params = Params,
- params_bin = EncParams,
- hashsign = {null, anon},
- signature = <<>>};
- _ ->
- Hash =
- server_key_exchange_hash(HashAlgo, <<ClientRandom/binary,
- ServerRandom/binary,
- EncParams/binary>>),
- Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey),
- #server_key_params{params = Params,
- params_bin = EncParams,
- hashsign = {HashAlgo, SignAlgo},
- signature = Signature}
- end.
-
-%%--------------------------------------------------------------------
--spec decode_client_key(binary(), ssl_cipher:key_algo(), ssl_record:ssl_version()) ->
- #encrypted_premaster_secret{}
- | #client_diffie_hellman_public{}
- | #client_ec_diffie_hellman_public{}
- | #client_psk_identity{}
- | #client_dhe_psk_identity{}
- | #client_rsa_psk_identity{}
- | #client_srp_public{}.
-%%
-%% Description: Decode client_key data and return appropriate type
-%%--------------------------------------------------------------------
-decode_client_key(ClientKey, Type, Version) ->
- dec_client_key(ClientKey, key_exchange_alg(Type), Version).
+ encode_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength),
+ ?UINT16(ServerNameLength),
+ ?BYTE(?SNI_NAMETYPE_HOST_NAME),
+ ?UINT16(HostLen), HostnameBin/binary,
+ Acc/binary>>);
+encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) ->
+ Versions = encode_versions(Versions0),
+ VerLen = byte_size(Versions),
+ Len = VerLen + 1,
+ encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT),
+ ?UINT16(Len), ?BYTE(VerLen), Versions/binary, Acc/binary>>);
+encode_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) ->
+ Version = encode_versions([Version0]),
+ Len = byte_size(Version), %% 2
+ encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT),
+ ?UINT16(Len), Version/binary, Acc/binary>>);
+encode_extensions([#key_share_client_hello{client_shares = ClientShares0} | Rest], Acc) ->
+ ClientShares = encode_client_shares(ClientShares0),
+ ClientSharesLen = byte_size(ClientShares),
+ Len = ClientSharesLen + 2,
+ encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT),
+ ?UINT16(Len), ?UINT16(ClientSharesLen),
+ ClientShares/binary, Acc/binary>>);
+encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], Acc) ->
+ ServerShare = encode_key_share_entry(ServerShare0),
+ Len = byte_size(ServerShare),
+ encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT),
+ ?UINT16(Len), ServerShare/binary, Acc/binary>>);
+encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) ->
+ Group = tls_v1:group_to_enum(Group0),
+ encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT),
+ ?UINT16(2), ?UINT16(Group), Acc/binary>>);
+encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) ->
+ KEModes = encode_psk_key_exchange_modes(KEModes0),
+ KEModesLen = byte_size(KEModes),
+ ExtLen = KEModesLen + 1,
+ encode_extensions(Rest, <<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT),
+ ?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>);
+encode_extensions([#pre_shared_key_client_hello{
+ offered_psks = #offered_psks{
+ identities = Identities0,
+ binders = Binders0} = PSKs} | Rest], Acc) ->
+ Identities = encode_psk_identities(Identities0),
+ Binders = encode_psk_binders(Binders0),
+ Len = byte_size(Identities) + byte_size(Binders),
+ encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT),
+ ?UINT16(Len), Identities/binary, Binders/binary, Acc/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>>).
-%%--------------------------------------------------------------------
--spec decode_server_key(binary(), ssl_cipher:key_algo(), ssl_record:ssl_version()) ->
- #server_key_params{}.
-%%
-%% Description: Decode server_key data and return appropriate type
-%%--------------------------------------------------------------------
-decode_server_key(ServerKey, Type, Version) ->
- dec_server_key(ServerKey, key_exchange_alg(Type), Version).
-
-%%
-%% Description: Encode and decode functions for ALPN extension data.
-%%--------------------------------------------------------------------
-
-%% While the RFC opens the door to allow ALPN during renegotiation, in practice
-%% this does not work and it is recommended to ignore any ALPN extension during
-%% renegotiation, as done here.
-encode_alpn(_, true) ->
- undefined;
-encode_alpn(undefined, _) ->
- undefined;
-encode_alpn(Protocols, _) ->
- #alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
-
-decode_alpn(undefined) ->
- undefined;
-decode_alpn(#alpn{extension_data=Data}) ->
- decode_protocols(Data, []).
encode_client_protocol_negotiation(undefined, _) ->
undefined;
@@ -933,6 +743,10 @@ encode_protocols_advertised_on_server(undefined) ->
encode_protocols_advertised_on_server(Protocols) ->
#next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
+%%====================================================================
+%% Decode handshake
+%%====================================================================
+
decode_handshake(_, ?HELLO_REQUEST, <<>>) ->
#hello_request{};
decode_handshake(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength),
@@ -940,7 +754,7 @@ decode_handshake(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength),
?BYTE(PaddingLength), _Padding:PaddingLength/binary>>) ->
#next_protocol{selected_protocol = SelectedProtocol};
-decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method)>>) ->
#server_hello{
@@ -949,14 +763,13 @@ decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:3
session_id = Session_ID,
cipher_suite = Cipher_suite,
compression_method = Comp_method,
- extensions = #hello_extensions{}};
+ extensions = empty_extensions(Version, server_hello)};
-decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method),
?UINT16(ExtLen), Extensions:ExtLen/binary>>) ->
-
- HelloExtensions = decode_hello_extensions(Extensions),
+ HelloExtensions = decode_hello_extensions(Extensions, Version, {Major, Minor}, server_hello),
#server_hello{
server_version = {Major,Minor},
@@ -965,7 +778,6 @@ decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:3
cipher_suite = Cipher_suite,
compression_method = Comp_method,
extensions = HelloExtensions};
-
decode_handshake(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) ->
#certificate{asn1_certificates = certs_to_list(ASN1Certs)};
decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) ->
@@ -1000,78 +812,74 @@ decode_handshake(_Version, ?FINISHED, VerifyData) ->
decode_handshake(_, Message, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_handshake, Message})).
+
+%%--------------------------------------------------------------------
+-spec decode_vector(binary()) -> binary().
+%%
+%% Description: Remove length tag from TLS Vector type. Needed
+%% for client hello when extensions in older versions may be empty.
+%%
+%%--------------------------------------------------------------------
+decode_vector(<<>>) ->
+ <<>>;
+decode_vector(<<?UINT16(Len), Vector:Len/binary>>) ->
+ Vector.
+
%%--------------------------------------------------------------------
--spec decode_hello_extensions({client, binary()} | binary()) -> #hello_extensions{}.
+-spec decode_hello_extensions(binary(), ssl_record:ssl_version(),
+ ssl_record:ssl_version(), atom()) -> map().
%%
%% Description: Decodes TLS hello extensions
%%--------------------------------------------------------------------
-decode_hello_extensions({client, <<>>}) ->
- #hello_extensions{};
-decode_hello_extensions({client, <<?UINT16(ExtLen), Extensions:ExtLen/binary>>}) ->
- decode_hello_extensions(Extensions);
-decode_hello_extensions(Extensions) ->
- dec_hello_extensions(Extensions, #hello_extensions{}).
+decode_hello_extensions(Extensions, LocalVersion, LegacyVersion, MessageType0) ->
+ %% Convert legacy atoms
+ MessageType =
+ case MessageType0 of
+ client -> client_hello;
+ server -> server_hello;
+ T -> T
+ end,
+ %% RFC 8446 - 4.2.1
+ %% Servers MUST be prepared to receive ClientHellos that include this extension but
+ %% do not include 0x0304 in the list of versions.
+ %% Clients MUST check for this extension prior to processing the rest of the
+ %% ServerHello (although they will have to parse the ServerHello in order to read
+ %% the extension).
+ Version = process_supported_versions_extension(Extensions, LocalVersion, LegacyVersion),
+ decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)).
-dec_server_key(<<?UINT16(PLen), P:PLen/binary,
- ?UINT16(GLen), G:GLen/binary,
- ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct,
- ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) ->
- Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y},
- {BinMsg, HashSign, Signature} = dec_server_key_params(PLen + GLen + YLen + 6, KeyStruct, Version),
- #server_key_params{params = Params,
- params_bin = BinMsg,
- hashsign = HashSign,
- signature = Signature};
-%% ECParameters with named_curve
-%% TODO: explicit curve
-dec_server_key(<<?BYTE(?NAMED_CURVE), ?UINT16(CurveID),
- ?BYTE(PointLen), ECPoint:PointLen/binary,
- _/binary>> = KeyStruct,
- ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) ->
- Params = #server_ecdh_params{curve = {namedCurve, tls_v1:enum_to_oid(CurveID)},
- public = ECPoint},
- {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version),
- #server_key_params{params = Params,
- params_bin = BinMsg,
- hashsign = HashSign,
- signature = Signature};
-dec_server_key(<<?UINT16(Len), PskIdentityHint:Len/binary, _/binary>> = KeyStruct,
- KeyExchange, Version)
- when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK ->
- Params = #server_psk_params{
- hint = PskIdentityHint},
- {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version),
- #server_key_params{params = Params,
- params_bin = BinMsg,
- hashsign = HashSign,
- signature = Signature};
-dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary,
- ?UINT16(PLen), P:PLen/binary,
- ?UINT16(GLen), G:GLen/binary,
- ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct,
- ?KEY_EXCHANGE_DHE_PSK, Version) ->
- DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y},
- Params = #server_dhe_psk_params{
- hint = IdentityHint,
- dh_params = DHParams},
- {BinMsg, HashSign, Signature} = dec_server_key_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version),
- #server_key_params{params = Params,
- params_bin = BinMsg,
- hashsign = HashSign,
- signature = Signature};
-dec_server_key(<<?UINT16(NLen), N:NLen/binary,
- ?UINT16(GLen), G:GLen/binary,
- ?BYTE(SLen), S:SLen/binary,
- ?UINT16(BLen), B:BLen/binary, _/binary>> = KeyStruct,
- ?KEY_EXCHANGE_SRP, Version) ->
- Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B},
- {BinMsg, HashSign, Signature} = dec_server_key_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version),
- #server_key_params{params = Params,
- params_bin = BinMsg,
- hashsign = HashSign,
- signature = Signature};
-dec_server_key(_, KeyExchange, _) ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})).
+%%--------------------------------------------------------------------
+-spec decode_extensions(binary(),tuple(), atom()) -> map().
+%%
+%% Description: Decodes TLS hello extensions
+%%--------------------------------------------------------------------
+decode_extensions(Extensions, Version, MessageType) ->
+ decode_extensions(Extensions, Version, MessageType, empty_extensions()).
+
+%%--------------------------------------------------------------------
+-spec decode_server_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) ->
+ #server_key_params{}.
+%%
+%% Description: Decode server_key data and return appropriate type
+%%--------------------------------------------------------------------
+decode_server_key(ServerKey, Type, Version) ->
+ dec_server_key(ServerKey, key_exchange_alg(Type), Version).
+
+%%--------------------------------------------------------------------
+-spec decode_client_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) ->
+ #encrypted_premaster_secret{}
+ | #client_diffie_hellman_public{}
+ | #client_ec_diffie_hellman_public{}
+ | #client_psk_identity{}
+ | #client_dhe_psk_identity{}
+ | #client_ecdhe_psk_identity{}
+ | #client_rsa_psk_identity{}
+ | #client_srp_public{}.
+%%
+%% Description: Decode client_key data and return appropriate type
+%%--------------------------------------------------------------------
+decode_client_key(ClientKey, Type, Version) ->
+ dec_client_key(ClientKey, key_exchange_alg(Type), Version).
%%--------------------------------------------------------------------
-spec decode_suites('2_bytes'|'3_bytes', binary()) -> list().
@@ -1083,75 +891,59 @@ decode_suites('2_bytes', Dec) ->
decode_suites('3_bytes', Dec) ->
from_3bytes(Dec).
-%%-------------Cipeher suite handling --------------------------------
+%%====================================================================
+%% Cipher suite handling
+%%====================================================================
available_suites(UserSuites, Version) ->
- lists:filtermap(fun(Suite) ->
- lists:member(Suite, ssl_cipher:all_suites(Version))
- end, UserSuites).
+ VersionSuites = ssl_cipher:all_suites(Version) ++ ssl_cipher:anonymous_suites(Version),
+ lists:filtermap(fun(Suite) -> lists:member(Suite, VersionSuites) end, UserSuites).
available_suites(ServerCert, UserSuites, Version, undefined, Curve) ->
- ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version))
- -- unavailable_ecc_suites(Curve);
+ Suites = ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version), Version),
+ filter_unavailable_ecc_suites(Curve, Suites);
available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) ->
Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve),
- filter_hashsigns(Suites, [ssl_cipher:suite_definition(Suite) || Suite <- Suites], HashSigns, []).
-filter_hashsigns([], [], _, Acc) ->
- lists:reverse(Acc);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns,
- Acc) when KeyExchange == dhe_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
- do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Acc);
-
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns,
- Acc) when KeyExchange == rsa;
- KeyExchange == dhe_rsa;
- KeyExchange == ecdhe_rsa;
- KeyExchange == srp_rsa;
- KeyExchange == rsa_psk ->
- do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
- KeyExchange == dhe_dss;
- KeyExchange == srp_dss ->
- do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
- KeyExchange == dh_dss;
- KeyExchange == dh_rsa;
- KeyExchange == dh_ecdsa;
- KeyExchange == ecdh_rsa;
- KeyExchange == ecdh_ecdsa ->
- %% Fixed DH certificates MAY be signed with any hash/signature
- %% algorithm pair appearing in the hash_sign extension. The names
- %% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical.
- filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
- KeyExchange == dh_anon;
- KeyExchange == ecdh_anon;
- KeyExchange == srp_anon;
- KeyExchange == psk;
- KeyExchange == dhe_psk ->
- %% In this case hashsigns is not used as the kexchange is anonaymous
- filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]).
-
-do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Acc) ->
- case lists:keymember(SignAlgo, 2, HashSigns) of
- true ->
- filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]);
- false ->
- filter_hashsigns(Suites, Algos, HashSigns, Acc)
- end.
+ filter_hashsigns(Suites, [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- Suites], HashSigns,
+ Version, []).
-unavailable_ecc_suites(no_curve) ->
- ssl_cipher:ec_keyed_suites();
-unavailable_ecc_suites(_) ->
- [].
+available_signature_algs(undefined, _) ->
+ undefined;
+available_signature_algs(SupportedHashSigns, Version) when Version >= {3, 3} ->
+ #hash_sign_algos{hash_sign_algos = SupportedHashSigns};
+available_signature_algs(_, _) ->
+ undefined.
+available_signature_algs(undefined, SupportedHashSigns, _, Version) when
+ Version >= {3,3} ->
+ SupportedHashSigns;
+available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns,
+ _, Version) when Version >= {3,3} ->
+ sets:to_list(sets:intersection(sets:from_list(ClientHashSigns),
+ sets:from_list(SupportedHashSigns)));
+available_signature_algs(_, _, _, _) ->
+ undefined.
+cipher_suites(Suites, Renegotiation, true) ->
+ %% TLS_FALLBACK_SCSV should be placed last -RFC7507
+ cipher_suites(Suites, Renegotiation) ++ [?TLS_FALLBACK_SCSV];
+cipher_suites(Suites, Renegotiation, false) ->
+ cipher_suites(Suites, Renegotiation).
cipher_suites(Suites, false) ->
[?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites];
cipher_suites(Suites, true) ->
Suites.
+%%--------------------------------------------------------------------
+-spec prf(ssl_record:ssl_version(), non_neg_integer(), binary(), binary(), [binary()], non_neg_integer()) ->
+ {ok, binary()} | {error, undefined}.
+%%
+%% 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 = ECCCurve} =
+select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve0} =
Session, Version,
#ssl_options{ciphers = UserSuites, honor_cipher_order = HonorCipherOrder} = SslOpts,
Cache, CacheCb, Cert) ->
@@ -1160,146 +952,336 @@ select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port,
Cache, CacheCb),
case Resumed of
undefined ->
- Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve),
- CipherSuite = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder),
+ Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0),
+ CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder),
+ {ECCCurve, CipherSuite} = cert_curve(Cert, ECCCurve0, CipherSuite0),
Compression = select_compression(Compressions),
{new, Session#session{session_id = SessionId,
+ ecc = ECCCurve,
cipher_suite = CipherSuite,
compression_method = Compression}};
_ ->
{resumed, Resumed}
end.
-%% Deprecated?
supported_ecc({Major, Minor}) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) ->
Curves = tls_v1:ecc_curves(Minor),
#elliptic_curves{elliptic_curve_list = Curves};
supported_ecc(_) ->
#elliptic_curves{elliptic_curve_list = []}.
-%%-------------certificate handling --------------------------------
-
-certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 ->
- case proplists:get_bool(ecdsa,
- proplists:get_value(public_keys, crypto:supports())) of
- true ->
- <<?BYTE(?ECDSA_SIGN), ?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>;
- false ->
- <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>
+premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) ->
+ try
+ public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params)
+ catch
+ error:computation_failed ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end;
+premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) ->
+ try
+ crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base])
+ catch
+ error:computation_failed ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
end;
+premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime,
+ verifier = Verifier}) ->
+ try crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of
+ PremasterSecret ->
+ PremasterSecret
+ catch
+ error:_ ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end;
+premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public},
+ ClientKeys, {Username, Password}) ->
+ case ssl_srp_primes:check_srp_params(Generator, Prime) of
+ ok ->
+ DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]),
+ try crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of
+ PremasterSecret ->
+ PremasterSecret
+ catch
+ error ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end;
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end;
+premaster_secret(#client_rsa_psk_identity{
+ identity = PSKIdentity,
+ exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS}
+ }, #'RSAPrivateKey'{} = Key, PSKLookup) ->
+ PremasterSecret = premaster_secret(EncPMS, Key),
+ psk_secret(PSKIdentity, PSKLookup, PremasterSecret);
+premaster_secret(#server_dhe_psk_params{
+ hint = IdentityHint,
+ dh_params = #server_dh_params{dh_y = PublicDhKey} = Params},
+ PrivateDhKey,
+ LookupFun) ->
+ PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params),
+ psk_secret(IdentityHint, LookupFun, PremasterSecret);
+premaster_secret(#server_ecdhe_psk_params{
+ hint = IdentityHint,
+ dh_params = #server_ecdh_params{
+ public = ECServerPubKey}},
+ PrivateEcDhKey,
+ LookupFun) ->
+ PremasterSecret = premaster_secret(#'ECPoint'{point = ECServerPubKey}, PrivateEcDhKey),
+ psk_secret(IdentityHint, LookupFun, PremasterSecret);
+premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) ->
+ psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret);
+premaster_secret(#client_ecdhe_psk_identity{
+ identity = PSKIdentity,
+ dh_public = PublicEcDhPoint}, PrivateEcDhKey, PSKLookup) ->
+ PremasterSecret = premaster_secret(#'ECPoint'{point = PublicEcDhPoint}, PrivateEcDhKey),
+ psk_secret(PSKIdentity, PSKLookup, PremasterSecret).
+premaster_secret(#client_dhe_psk_identity{
+ identity = PSKIdentity,
+ dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) ->
+ PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params),
+ psk_secret(PSKIdentity, PSKLookup, PremasterSecret).
+premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) ->
+ psk_secret(PSKIdentity, PSKLookup);
+premaster_secret({psk, PSKIdentity}, PSKLookup) ->
+ psk_secret(PSKIdentity, PSKLookup);
+premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) ->
+ public_key:compute_key(ECPoint, ECDHKeys);
+premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) ->
+ try public_key:decrypt_private(EncSecret, RSAPrivateKey,
+ [{rsa_pad, rsa_pkcs1_padding}])
+ catch
+ _:_ ->
+ throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
+ end;
+premaster_secret(EncSecret, #{algorithm := rsa} = Engine) ->
+ try crypto:private_decrypt(rsa, EncSecret, maps:remove(algorithm, Engine),
+ [{rsa_pad, rsa_pkcs1_padding}])
+ catch
+ _:_ ->
+ throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
+ end.
+%%====================================================================
+%% Extensions handling
+%%====================================================================
+client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare) ->
+ HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation),
+ HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts),
+ maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare).
-certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa;
- KeyExchange == dh_rsa;
- KeyExchange == dhe_rsa;
- KeyExchange == ecdhe_rsa ->
- <<?BYTE(?RSA_SIGN)>>;
-
-certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_dss;
- KeyExchange == dhe_dss;
- KeyExchange == srp_dss ->
- <<?BYTE(?DSS_SIGN)>>;
-
-certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_ecdsa;
- KeyExchange == dhe_ecdsa;
- KeyExchange == ecdh_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
- <<?BYTE(?ECDSA_SIGN)>>;
-certificate_types(_, _) ->
- <<?BYTE(?RSA_SIGN)>>.
+add_tls12_extensions(_Version,
+ SslOpts,
+ ConnectionStates,
+ Renegotiation) ->
+ SRP = srp_user(SslOpts),
+ #{renegotiation_info => renegotiation_info(tls_record, client,
+ ConnectionStates, Renegotiation),
+ srp => SRP,
+ alpn => encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation),
+ next_protocol_negotiation =>
+ encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
+ Renegotiation),
+ sni => sni(SslOpts#ssl_options.server_name_indication)
+ }.
-certificate_authorities(CertDbHandle, CertDbRef) ->
- Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef),
- Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) ->
- OTPSubj = TBSCert#'OTPTBSCertificate'.subject,
- DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp),
- DNEncodedLen = byte_size(DNEncodedBin),
- <<?UINT16(DNEncodedLen), DNEncodedBin/binary>>
- end,
- list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]).
-certificate_authorities_from_db(CertDbHandle, CertDbRef) when is_reference(CertDbRef) ->
- ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef ->
- [Cert | Acc];
- (_, Acc) ->
- Acc
- end,
- ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle);
-certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) ->
- %% Cache disabled, Ref contains data
- lists:foldl(fun({decoded, {_Key,Cert}}, Acc) -> [Cert | Acc] end,
- [], CertDbData).
+add_common_extensions({3,4},
+ HelloExtensions,
+ _CipherSuites,
+ #ssl_options{eccs = SupportedECCs,
+ supported_groups = Groups,
+ signature_algs = SignatureSchemes}) ->
+ {EcPointFormats, _} =
+ client_ecc_extensions(SupportedECCs),
+ HelloExtensions#{ec_point_formats => EcPointFormats,
+ elliptic_curves => Groups,
+ signature_algs => signature_algs_ext(SignatureSchemes)};
+
+add_common_extensions(Version,
+ HelloExtensions,
+ CipherSuites,
+ #ssl_options{eccs = SupportedECCs,
+ signature_algs = SupportedHashSigns}) ->
+ {EcPointFormats, EllipticCurves} =
+ case advertises_ec_ciphers(
+ lists:map(fun ssl_cipher_format:suite_bin_to_map/1,
+ CipherSuites)) of
+ true ->
+ client_ecc_extensions(SupportedECCs);
+ false ->
+ {undefined, undefined}
+ end,
+ HelloExtensions#{ec_point_formats => EcPointFormats,
+ elliptic_curves => EllipticCurves,
+ signature_algs => available_signature_algs(SupportedHashSigns, Version)}.
+
+
+maybe_add_tls13_extensions({3,4},
+ HelloExtensions0,
+ #ssl_options{signature_algs_cert = SignatureSchemes,
+ versions = SupportedVersions},
+ KeyShare) ->
+ HelloExtensions =
+ HelloExtensions0#{client_hello_versions =>
+ #client_hello_versions{versions = SupportedVersions},
+ signature_algs_cert =>
+ signature_algs_cert(SignatureSchemes)},
+ maybe_add_key_share(HelloExtensions, KeyShare);
+maybe_add_tls13_extensions(_, HelloExtensions, _, _) ->
+ HelloExtensions.
+
+
+%% TODO: Add support for PSK key establishment
+
+%% RFC 8446 (TLS 1.3) - 4.2.8. Key Share
+%%
+%% 4.2.8.1. Diffie-Hellman Parameters
+%% Diffie-Hellman [DH76] parameters for both clients and servers are
+%% encoded in the opaque key_exchange field of a KeyShareEntry in a
+%% KeyShare structure. The opaque value contains the Diffie-Hellman
+%% public value (Y = g^X mod p) for the specified group (see [RFC7919]
+%% for group definitions) encoded as a big-endian integer and padded to
+%% the left with zeros to the size of p in bytes.
+%%
+%% 4.2.8.2. ECDHE Parameters
+%%
+%% ECDHE parameters for both clients and servers are encoded in the
+%% opaque key_exchange field of a KeyShareEntry in a KeyShare structure.
+%%
+%% For secp256r1, secp384r1, and secp521r1, the contents are the
+%% serialized value of the following struct:
+%%
+%% struct {
+%% uint8 legacy_form = 4;
+%% opaque X[coordinate_length];
+%% opaque Y[coordinate_length];
+%% } UncompressedPointRepresentation;
+%%
+%% X and Y, respectively, are the binary representations of the x and y
+%% values in network byte order. There are no internal length markers,
+%% so each number representation occupies as many octets as implied by
+%% the curve parameters. For P-256, this means that each of X and Y use
+%% 32 octets, padded on the left by zeros if necessary. For P-384, they
+%% take 48 octets each. For P-521, they take 66 octets each.
+maybe_add_key_share(HelloExtensions, undefined) ->
+ HelloExtensions;
+maybe_add_key_share(HelloExtensions, KeyShare) ->
+ #key_share_client_hello{client_shares = ClientShares0} = KeyShare,
+ %% Keep only public keys
+ ClientShares = lists:map(fun kse_remove_private_key/1, ClientShares0),
+ HelloExtensions#{key_share => #key_share_client_hello{
+ client_shares = ClientShares}}.
+
+add_server_share(server_hello, Extensions, KeyShare) ->
+ #key_share_server_hello{server_share = ServerShare0} = KeyShare,
+ %% Keep only public keys
+ ServerShare = kse_remove_private_key(ServerShare0),
+ Extensions#{key_share => #key_share_server_hello{
+ server_share = ServerShare}};
+add_server_share(hello_retry_request, Extensions,
+ #key_share_server_hello{
+ server_share = #key_share_entry{group = Group}}) ->
+ Extensions#{key_share => #key_share_hello_retry_request{
+ selected_group = Group}}.
+
+add_alpn(Extensions, ALPN0) ->
+ ALPN = encode_alpn([ALPN0], false),
+ Extensions#{alpn => ALPN}.
+
+add_selected_version(Extensions) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions#{server_hello_selected_version => SupportedVersions}.
+
+kse_remove_private_key(#key_share_entry{
+ group = Group,
+ key_exchange =
+ #'ECPrivateKey'{publicKey = PublicKey}}) ->
+ #key_share_entry{
+ group = Group,
+ key_exchange = PublicKey};
+kse_remove_private_key(#key_share_entry{
+ group = Group,
+ key_exchange =
+ {PublicKey, _}}) ->
+ #key_share_entry{
+ group = Group,
+ key_exchange = PublicKey}.
+
+signature_algs_ext(undefined) ->
+ undefined;
+signature_algs_ext(SignatureSchemes0) ->
+ %% The SSL option signature_algs contains both hash-sign algorithms (tuples) and
+ %% signature schemes (atoms) if TLS 1.3 is configured.
+ #signature_algorithms{signature_scheme_list = SignatureSchemes0}.
-%%-------------Extension handling --------------------------------
+signature_algs_cert(undefined) ->
+ undefined;
+signature_algs_cert(SignatureSchemes) ->
+ #signature_algorithms_cert{signature_scheme_list = SignatureSchemes}.
handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
- #hello_extensions{renegotiation_info = Info,
- srp = SRP,
- ec_point_formats = ECCFormat,
- alpn = ALPN,
- next_protocol_negotiation = NextProtocolNegotiation}, Version,
+ Exts, Version,
#ssl_options{secure_renegotiate = SecureRenegotation,
alpn_preferred_protocols = ALPNPreferredProtocols} = Opts,
#session{cipher_suite = NegotiatedCipherSuite,
compression_method = Compression} = Session0,
ConnectionStates0, Renegotiation) ->
- Session = handle_srp_extension(SRP, Session0),
- ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info,
+ Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0),
+ ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined),
Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
ConnectionStates0, Renegotiation, SecureRenegotation),
- ServerHelloExtensions = #hello_extensions{
- renegotiation_info = renegotiation_info(RecordCB, server,
- ConnectionStates, Renegotiation),
- ec_point_formats = server_ecc_extension(Version, ECCFormat)
- },
-
+ Empty = empty_extensions(Version, server_hello),
+ ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
+ ConnectionStates, Renegotiation),
+ ec_point_formats => server_ecc_extension(Version,
+ maps:get(ec_point_formats, Exts, undefined))
+ },
+
%% If we receive an ALPN extension and have ALPN configured for this connection,
%% we handle it. Otherwise we check for the NPN extension.
+ ALPN = maps:get(alpn, Exts, undefined),
if
ALPN =/= undefined, ALPNPreferredProtocols =/= undefined ->
- case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {Session, ConnectionStates, Protocol,
- ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}}
- end;
+ Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)),
+ {Session, ConnectionStates, Protocol,
+ ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}};
true ->
+ NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
{Session, ConnectionStates, undefined,
- ServerHelloExtensions#hello_extensions{next_protocol_negotiation=
- encode_protocols_advertised_on_server(ProtocolsToAdvertise)}}
+ ServerHelloExtensions#{next_protocol_negotiation =>
+ encode_protocols_advertised_on_server(ProtocolsToAdvertise)}}
end.
handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
- #hello_extensions{renegotiation_info = Info,
- alpn = ALPN,
- next_protocol_negotiation = NextProtocolNegotiation}, Version,
+ Exts, Version,
#ssl_options{secure_renegotiate = SecureRenegotation,
next_protocol_selector = NextProtoSelector},
ConnectionStates0, Renegotiation) ->
- ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, Info, Random,
+ 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 ->
- case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {ConnectionStates, npn, Protocol}
- end;
+ 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);
[] ->
@@ -1308,240 +1290,291 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello)
end.
-select_version(RecordCB, ClientVersion, Versions) ->
- do_select_version(RecordCB, ClientVersion, Versions).
+select_curve(Client, Server) ->
+ select_curve(Client, Server, false).
-do_select_version(_, ClientVersion, []) ->
- ClientVersion;
-do_select_version(RecordCB, ClientVersion, [Version | Versions]) ->
- case RecordCB:is_higher(Version, ClientVersion) of
- true ->
- %% Version too high for client - keep looking
- do_select_version(RecordCB, ClientVersion, Versions);
- false ->
- %% Version ok for client - look for a higher
- do_select_version(RecordCB, ClientVersion, Versions, Version)
- end.
-%%
-do_select_version(_, _, [], GoodVersion) ->
- GoodVersion;
-do_select_version(
- RecordCB, ClientVersion, [Version | Versions], GoodVersion) ->
- BetterVersion =
- case RecordCB:is_higher(Version, ClientVersion) of
- true ->
- %% Version too high for client
- GoodVersion;
- false ->
- %% Version ok for client
- case RecordCB:is_higher(Version, GoodVersion) of
- true ->
- %% Use higher version
- Version;
- false ->
- GoodVersion
- end
- end,
- do_select_version(RecordCB, ClientVersion, Versions, BetterVersion).
+select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves},
+ #elliptic_curves{elliptic_curve_list = ServerCurves},
+ ServerOrder) ->
+ case ServerOrder of
+ false ->
+ select_shared_curve(ClientCurves, ServerCurves);
+ true ->
+ select_shared_curve(ServerCurves, ClientCurves)
+ end;
+select_curve(undefined, _, _) ->
+ %% Client did not send ECC extension use default curve if
+ %% ECC cipher is negotiated
+ {namedCurve, ?secp256r1}.
-renegotiation_info(_, client, _, false) ->
- #renegotiation_info{renegotiated_connection = undefined};
-renegotiation_info(_RecordCB, server, ConnectionStates, false) ->
- ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
- case maps:get(secure_renegotiation, ConnectionState) of
- true ->
- #renegotiation_info{renegotiated_connection = ?byte(0)};
- false ->
- #renegotiation_info{renegotiated_connection = undefined}
+%%--------------------------------------------------------------------
+-spec select_hashsign(#hash_sign_algos{} | undefined, undefined | binary(),
+ atom(), [atom()], ssl_record:ssl_version()) ->
+ {atom(), atom()} | undefined | #alert{}.
+
+%%
+%% Description: Handles signature_algorithms hello extension (server)
+%%--------------------------------------------------------------------
+select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon;
+ KeyExAlgo == ecdh_anon;
+ KeyExAlgo == srp_anon;
+ KeyExAlgo == psk;
+ KeyExAlgo == dhe_psk;
+ KeyExAlgo == ecdhe_psk ->
+ {null, anon};
+%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have
+%% negotiated a lower version.
+select_hashsign({ClientHashSigns, ClientSignatureSchemes},
+ Cert, KeyExAlgo, undefined, {Major, Minor} = Version)
+ when Major >= 3 andalso Minor >= 3->
+ select_hashsign({ClientHashSigns, ClientSignatureSchemes}, Cert, KeyExAlgo,
+ tls_v1:default_signature_algs(Version), Version);
+select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns},
+ ClientSignatureSchemes0},
+ Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor})
+ when Major >= 3 andalso Minor >= 3 ->
+ ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0),
+ {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ SignAlgo = sign_algo(SignAlgo0),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+
+ %% RFC 5246 (TLS 1.2)
+ %% If the client provided a "signature_algorithms" extension, then all
+ %% certificates provided by the server MUST be signed by a
+ %% hash/signature algorithm pair that appears in that extension.
+ %%
+ %% RFC 8446 (TLS 1.3)
+ %% TLS 1.3 provides two extensions for indicating which signature
+ %% algorithms may be used in digital signatures. The
+ %% "signature_algorithms_cert" extension applies to signatures in
+ %% certificates and the "signature_algorithms" extension, which
+ %% originally appeared in TLS 1.2, applies to signatures in
+ %% CertificateVerify messages.
+ %%
+ %% If no "signature_algorithms_cert" extension is
+ %% present, then the "signature_algorithms" extension also applies to
+ %% signatures appearing in certificates.
+ case is_supported_sign(SignAlgo, Param, ClientHashSigns, ClientSignatureSchemes) of
+ true ->
+ case lists:filter(fun({_, S} = Algos) when S == PublicKeyAlgo ->
+ is_acceptable_hash_sign(Algos, KeyExAlgo, SupportedHashSigns);
+ (_) ->
+ false
+ end, ClientHashSigns) of
+ [] ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
+ [HashSign | _] ->
+ HashSign
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
-renegotiation_info(_RecordCB, client, ConnectionStates, true) ->
- ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
- case maps:get(secure_renegotiation, ConnectionState) of
+select_hashsign(_, Cert, _, _, Version) ->
+ #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
+ #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ select_hashsign_algs(undefined, Algo, Version).
+%%--------------------------------------------------------------------
+-spec select_hashsign(#certificate_request{}, binary(),
+ [atom()], ssl_record:ssl_version()) ->
+ {atom(), atom()} | #alert{}.
+
+%%
+%% Description: Handles signature algorithms selection for certificate requests (client)
+%%--------------------------------------------------------------------
+select_hashsign(#certificate_request{
+ hashsign_algorithms = #hash_sign_algos{
+ hash_sign_algos = HashSigns},
+ certificate_types = Types},
+ Cert,
+ SupportedHashSigns,
+ {Major, Minor}) when Major >= 3 andalso Minor >= 3->
+ {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ SignAlgo = sign_algo(SignAlgo0),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+
+ case is_acceptable_cert_type(PublicKeyAlgo, Types) andalso
+ %% certificate_request has no "signature_algorithms_cert"
+ %% extension in TLS 1.2.
+ is_supported_sign(SignAlgo, Param, HashSigns, undefined) of
true ->
- Data = maps:get(client_verify_data, ConnectionState),
- #renegotiation_info{renegotiated_connection = Data};
+ case lists:filter(fun({_, S} = Algos) when S == PublicKeyAlgo ->
+ is_acceptable_hash_sign(Algos, SupportedHashSigns);
+ (_) ->
+ false
+ end, HashSigns) of
+ [] ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
+ [HashSign | _] ->
+ HashSign
+ end;
false ->
- #renegotiation_info{renegotiated_connection = undefined}
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
+select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) ->
+ {_, _, PublicKeyAlgo0} = get_cert_params(Cert),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
-renegotiation_info(_RecordCB, server, ConnectionStates, true) ->
- ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
- case maps:get(secure_renegotiation, ConnectionState) of
- true ->
- CData = maps:get(client_verify_data, ConnectionState),
- SData = maps:get(server_verify_data, ConnectionState),
- #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>};
- false ->
- #renegotiation_info{renegotiated_connection = undefined}
+ %% Check cert even for TLS 1.0/1.1
+ case is_acceptable_cert_type(PublicKeyAlgo, Types) of
+ true ->
+ select_hashsign(undefined, Cert, undefined, [], Version);
+ false ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end.
-handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)},
- ConnectionStates, false, _, _) ->
- {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
-handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) ->
- case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
- true ->
- {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
- false ->
- {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}
- end;
+%% Gets the relevant parameters of a certificate:
+%% - signature algorithm
+%% - parameters of the signature algorithm
+%% - public key algorithm (key type)
+get_cert_params(Cert) ->
+ #'OTPCertificate'{tbsCertificate = TBSCert,
+ signatureAlgorithm =
+ {_,SignAlgo, Param}} = public_key:pkix_decode_cert(Cert, otp),
+ #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}} =
+ TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ {SignAlgo, Param, PublicKeyAlgo}.
-handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) ->
- {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)};
-handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify},
- ConnectionStates, true, _, _) ->
- ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
- CData = maps:get(client_verify_data, ConnectionState),
- SData = maps:get(server_verify_data, ConnectionState),
- case <<CData/binary, SData/binary>> == ClientServerVerify of
- true ->
- {ok, ConnectionStates};
- false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)
- end;
-handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
- ConnectionStates, true, _, CipherSuites) ->
-
- case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
- true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
- false ->
- ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
- Data = maps:get(client_verify_data, ConnectionState),
- case Data == ClientVerify of
- true ->
- {ok, ConnectionStates};
- false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)
- end
- end;
+get_signature_scheme(undefined) ->
+ undefined;
+get_signature_scheme(#signature_algorithms_cert{
+ signature_scheme_list = ClientSignatureSchemes}) ->
+ ClientSignatureSchemes.
-handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) ->
- handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation);
-handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
- case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
- true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
- false ->
- handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation)
- end.
+%%--------------------------------------------------------------------
+-spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) ->
+ {atom(), atom()}.
-handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
- ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
- case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of
- {_, true} ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure);
- {true, false} ->
- ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION);
- {false, false} ->
- {ok, ConnectionStates}
- end.
+%% Description: For TLS 1.2 hash function and signature algorithm pairs can be
+%% negotiated with the signature_algorithms extension,
+%% for previous versions always use appropriate defaults.
+%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms
+%% If the client does not send the signature_algorithms extension, the
+%% server MUST do the following: (e.i defaults for TLS 1.2)
+%%
+%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA,
+%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had
+%% sent the value {sha1,rsa}.
+%%
+%% - If the negotiated key exchange algorithm is one of (DHE_DSS,
+%% DH_DSS), behave as if the client had sent the value {sha1,dsa}.
+%%
+%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA,
+%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
-hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo,
- srp = SRP,
- signature_algs = HashSigns,
- ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves,
- alpn = ALPN,
- next_protocol_negotiation = NextProtocolNegotiation,
- sni = Sni}) ->
- [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns,
- EcPointFormats, EllipticCurves, ALPN, NextProtocolNegotiation, Sni], Ext =/= undefined].
+%%--------------------------------------------------------------------
+select_hashsign_algs(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso
+ Major >= 3 andalso Minor >= 3 ->
+ HashSign;
+select_hashsign_algs(undefined, ?rsaEncryption, {Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
+ {sha, rsa};
+select_hashsign_algs(undefined,?'id-ecPublicKey', _) ->
+ {sha, ecdsa};
+select_hashsign_algs(undefined, ?rsaEncryption, _) ->
+ {md5sha, rsa};
+select_hashsign_algs(undefined, ?'id-dsa', _) ->
+ {sha, dsa}.
srp_user(#ssl_options{srp_identity = {UserName, _}}) ->
#srp{username = UserName};
srp_user(_) ->
undefined.
-client_ecc_extensions(SupportedECCs) ->
- CryptoSupport = proplists:get_value(public_keys, crypto:supports()),
- case proplists:get_bool(ecdh, CryptoSupport) of
- true ->
- EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]},
- EllipticCurves = SupportedECCs,
- {EcPointFormats, EllipticCurves};
- _ ->
- {undefined, undefined}
- end.
-
-server_ecc_extension(_Version, EcPointFormats) ->
- CryptoSupport = proplists:get_value(public_keys, crypto:supports()),
- case proplists:get_bool(ecdh, CryptoSupport) of
- true ->
- handle_ecc_point_fmt_extension(EcPointFormats);
- false ->
- undefined
- end.
-
-handle_ecc_point_fmt_extension(undefined) ->
+extension_value(undefined) ->
undefined;
-handle_ecc_point_fmt_extension(_) ->
- #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}.
+extension_value(#sni{hostname = HostName}) ->
+ HostName;
+extension_value(#ec_point_formats{ec_point_format_list = List}) ->
+ List;
+extension_value(#elliptic_curves{elliptic_curve_list = List}) ->
+ List;
+extension_value(#supported_groups{supported_groups = SupportedGroups}) ->
+ SupportedGroups;
+extension_value(#hash_sign_algos{hash_sign_algos = Algos}) ->
+ Algos;
+extension_value(#alpn{extension_data = Data}) ->
+ Data;
+extension_value(#next_protocol_negotiation{extension_data = Data}) ->
+ Data;
+extension_value(#srp{username = Name}) ->
+ Name;
+extension_value(#renegotiation_info{renegotiated_connection = Data}) ->
+ Data;
+extension_value(#signature_algorithms{signature_scheme_list = Schemes}) ->
+ Schemes;
+extension_value(#signature_algorithms_cert{signature_scheme_list = Schemes}) ->
+ Schemes;
+extension_value(#key_share_client_hello{client_shares = ClientShares}) ->
+ ClientShares;
+extension_value(#key_share_server_hello{server_share = ServerShare}) ->
+ ServerShare;
+extension_value(#client_hello_versions{versions = Versions}) ->
+ Versions;
+extension_value(#server_hello_selected_version{selected_version = SelectedVersion}) ->
+ SelectedVersion.
-advertises_ec_ciphers([]) ->
- false;
-advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) ->
- true;
-advertises_ec_ciphers([{ecdhe_ecdsa, _,_,_} | _]) ->
- true;
-advertises_ec_ciphers([{ecdh_rsa, _,_,_} | _]) ->
- true;
-advertises_ec_ciphers([{ecdhe_rsa, _,_,_} | _]) ->
- true;
-advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) ->
- true;
-advertises_ec_ciphers([_| Rest]) ->
- advertises_ec_ciphers(Rest).
-select_curve(Client, Server) ->
- select_curve(Client, Server, false).
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+%%------------- Create handshake messages ----------------------------
-select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves},
- #elliptic_curves{elliptic_curve_list = ServerCurves},
- ServerOrder) ->
- case ServerOrder of
- false ->
- select_shared_curve(ClientCurves, ServerCurves);
- true ->
- select_shared_curve(ServerCurves, ClientCurves)
- end;
-select_curve(undefined, _, _) ->
- %% Client did not send ECC extension use default curve if
- %% ECC cipher is negotiated
- {namedCurve, ?secp256r1}.
+int_to_bin(I) ->
+ L = (length(integer_to_list(I, 16)) + 1) div 2,
+ <<I:(L*8)>>.
-select_shared_curve([], _) ->
- no_curve;
-select_shared_curve([Curve | Rest], Curves) ->
- case lists:member(Curve, Curves) of
- true ->
- {namedCurve, Curve};
- false ->
- select_shared_curve(Rest, Curves)
+%% TLS 1.0+
+%% The end-entity certificate provided by the client MUST contain a
+%% key that is compatible with certificate_types.
+certificate_types(_, {N, M}) when N >= 3 andalso M >= 1 ->
+ ECDSA = supported_cert_type_or_empty(ecdsa, ?ECDSA_SIGN),
+ RSA = supported_cert_type_or_empty(rsa, ?RSA_SIGN),
+ DSS = supported_cert_type_or_empty(dss, ?DSS_SIGN),
+ <<ECDSA/binary,RSA/binary,DSS/binary>>;
+%% SSL 3.0
+certificate_types(_, _) ->
+ RSA = supported_cert_type_or_empty(rsa, ?RSA_SIGN),
+ DSS = supported_cert_type_or_empty(dss, ?DSS_SIGN),
+ <<RSA/binary,DSS/binary>>.
+
+%% Returns encoded certificate_type if algorithm is supported
+supported_cert_type_or_empty(Algo, Type) ->
+ case proplists:get_bool(
+ Algo,
+ proplists:get_value(public_keys, crypto:supports())) of
+ true ->
+ <<?BYTE(Type)>>;
+ false ->
+ <<>>
end.
-%% RFC 6066, Section 3: Currently, the only server names supported are
-%% DNS hostnames
-sni(_, disable) ->
- undefined;
-sni(Host, undefined) ->
- sni1(Host);
-sni(_Host, SNIOption) ->
- sni1(SNIOption).
-
-sni1(Hostname) ->
- case inet_parse:domain(Hostname) of
- false -> undefined;
- true -> #sni{hostname = Hostname}
- end.
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
+certificate_authorities(CertDbHandle, CertDbRef) ->
+ Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef),
+ Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) ->
+ OTPSubj = TBSCert#'OTPTBSCertificate'.subject,
+ DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp),
+ DNEncodedLen = byte_size(DNEncodedBin),
+ <<?UINT16(DNEncodedLen), DNEncodedBin/binary>>
+ end,
+ list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]).
+
+certificate_authorities_from_db(CertDbHandle, CertDbRef) when is_reference(CertDbRef) ->
+ ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef ->
+ [Cert | Acc];
+ (_, Acc) ->
+ Acc
+ end,
+ ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle);
+certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) ->
+ %% Cache disabled, Ref contains data
+ lists:foldl(fun({decoded, {_Key,Cert}}, Acc) -> [Cert | Acc] end,
+ [], CertDbData).
+
+%%-------------Handle handshake messages --------------------------------
validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
- CRLCheck, CRLDbHandle, CertPath) ->
+ ServerNameIndication, CustomizeHostCheck, CRLCheck, CRLDbHandle, CertPath) ->
{fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) ->
case ssl_certificate:validate(OtpCert,
Extension,
@@ -1558,9 +1591,9 @@ validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
(OtpCert, VerifyResult, {SslState, UserState}) ->
apply_user_fun(Fun, OtpCert, VerifyResult, UserState,
SslState, CertPath)
- end, {{Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle}, UserState0}};
+ end, {{Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}, UserState0}};
validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
- CRLCheck, CRLDbHandle, CertPath) ->
+ ServerNameIndication, CustomizeHostCheck, CRLCheck, CRLDbHandle, CertPath) ->
{fun(OtpCert, {extension, _} = Extension, SslState) ->
ssl_certificate:validate(OtpCert,
Extension,
@@ -1569,8 +1602,10 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
(VerifyResult == valid_peer) ->
case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
CRLDbHandle, VerifyResult, CertPath) of
- valid ->
- {VerifyResult, SslState};
+ valid ->
+ ssl_certificate:validate(OtpCert,
+ VerifyResult,
+ SslState);
Reason ->
{fail, Reason}
end;
@@ -1578,10 +1613,10 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
ssl_certificate:validate(OtpCert,
VerifyResult,
SslState)
- end, {Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle}}.
+ end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}.
apply_user_fun(Fun, OtpCert, VerifyResult, UserState0,
- {_, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle} = SslState, CertPath) when
+ {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath) when
(VerifyResult == valid) or (VerifyResult == valid_peer) ->
case Fun(OtpCert, VerifyResult, UserState0) of
{Valid, UserState} when (Valid == valid) or (Valid == valid_peer) ->
@@ -1605,6 +1640,45 @@ 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) ->
+ case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef, Chain0) of
+ {ok, _, [PeerCert | Chain] = OrdedChain} when Chain =/= Chain0 -> %% Chain appaears to be unorded
+ {Trusted, Path} = ssl_certificate:trusted_cert_and_path(OrdedChain,
+ CertDbHandle, CertsDbRef,
+ Opts#ssl_options.partial_chain),
+ case public_key:pkix_path_validation(Trusted, Path, Options) of
+ {ok, {PublicKeyInfo,_}} ->
+ {PeerCert, PublicKeyInfo};
+ {error, PathError} ->
+ handle_path_validation_error(PathError, PeerCert, Path,
+ Opts, Options, CertDbHandle, CertsDbRef)
+ end;
+ _ ->
+ path_validation_alert(Reason)
+ end;
+handle_path_validation_error(Reason, _, _, _, _,_, _) ->
+ path_validation_alert(Reason).
+
+handle_incomplete_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, PathError0) ->
+ case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of
+ {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
+ {Trusted, Path} = ssl_certificate:trusted_cert_and_path(Chain,
+ CertDbHandle, CertsDbRef,
+ Opts#ssl_options.partial_chain),
+ case public_key:pkix_path_validation(Trusted, Path, Options) of
+ {ok, {PublicKeyInfo,_}} ->
+ {PeerCert, PublicKeyInfo};
+ {error, PathError} ->
+ path_validation_alert(PathError)
+ end;
+ _ ->
+ path_validation_alert(PathError0)
+ end.
+
path_validation_alert({bad_cert, cert_expired}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED);
path_validation_alert({bad_cert, invalid_issuer}) ->
@@ -1617,8 +1691,9 @@ path_validation_alert({bad_cert, unknown_critical_extension}) ->
?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE);
path_validation_alert({bad_cert, {revoked, _}}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED);
-path_validation_alert({bad_cert, revocation_status_undetermined}) ->
- ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) ->
+ Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE),
+ Alert#alert{reason = Details};
path_validation_alert({bad_cert, selfsigned_peer}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
path_validation_alert({bad_cert, unknown_ca}) ->
@@ -1626,17 +1701,6 @@ path_validation_alert({bad_cert, unknown_ca}) ->
path_validation_alert(Reason) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason).
-encrypted_premaster_secret(Secret, RSAPublicKey) ->
- try
- PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey,
- [{rsa_pad,
- rsa_pkcs1_padding}]),
- #encrypted_premaster_secret{premaster_secret = PreMasterSecret}
- catch
- _:_->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed))
- end.
-
digitally_signed(Version, Hashes, HashAlgo, PrivateKey) ->
try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of
Signature ->
@@ -1645,17 +1709,123 @@ digitally_signed(Version, Hashes, HashAlgo, PrivateKey) ->
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, #'DSAPrivateKey'{} = Key) ->
- 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) ->
+ 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) ->
public_key:sign({digest, Hash}, HashAlgo, Key).
+bad_key(#'DSAPrivateKey'{}) ->
+ unacceptable_dsa_key;
+bad_key(#'RSAPrivateKey'{}) ->
+ unacceptable_rsa_key;
+bad_key(#'ECPrivateKey'{}) ->
+ unacceptable_ecdsa_key.
+
+crl_check(_, false, _,_,_, _, _) ->
+ valid;
+crl_check(_, peer, _, _,_, valid, _) -> %% Do not check CAs with this option.
+ valid;
+crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath) ->
+ Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) ->
+ ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath,
+ DBInfo})
+ end, {CertDbHandle, CertDbRef}}},
+ {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end},
+ {undetermined_details, true}
+ ],
+ case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) of
+ no_dps ->
+ crl_check_same_issuer(OtpCert, Check,
+ dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer),
+ Options);
+ DpsAndCRLs -> %% This DP list may be empty if relevant CRLs existed
+ %% but could not be retrived, will result in {bad_cert, revocation_status_undetermined}
+ case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of
+ {bad_cert, {revocation_status_undetermined, _}} ->
+ crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback,
+ CRLDbHandle, same_issuer), Options);
+ Other ->
+ Other
+ end
+ end.
+
+crl_check_same_issuer(OtpCert, best_effort, Dps, Options) ->
+ case public_key:pkix_crls_validate(OtpCert, Dps, Options) of
+ {bad_cert, {revocation_status_undetermined, _}} ->
+ valid;
+ Other ->
+ Other
+ end;
+crl_check_same_issuer(OtpCert, _, Dps, Options) ->
+ public_key:pkix_crls_validate(OtpCert, Dps, Options).
+
+dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) ->
+ case public_key:pkix_dist_points(OtpCert) of
+ [] ->
+ no_dps;
+ DistPoints ->
+ Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer,
+ CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle),
+ dps_and_crls(DistPoints, CRLs, [])
+ end;
+
+dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer) ->
+ DP = #'DistributionPoint'{distributionPoint = {fullName, GenNames}} =
+ public_key:pkix_dist_point(OtpCert),
+ CRLs = lists:flatmap(fun({directoryName, Issuer}) ->
+ Callback:select(Issuer, CRLDbHandle);
+ (_) ->
+ []
+ end, GenNames),
+ [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs].
+
+dps_and_crls([], _, Acc) ->
+ Acc;
+dps_and_crls([DP | Rest], CRLs, Acc) ->
+ DpCRL = [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs],
+ dps_and_crls(Rest, CRLs, DpCRL ++ Acc).
+
+distpoints_lookup([],_, _, _) ->
+ [];
+distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) ->
+ Result =
+ try Callback:lookup(DistPoint, Issuer, CRLDbHandle)
+ catch
+ error:undef ->
+ %% The callback module still uses the 2-argument
+ %% version of the lookup function.
+ Callback:lookup(DistPoint, CRLDbHandle)
+ end,
+ case Result of
+ not_available ->
+ distpoints_lookup(Rest, Issuer, Callback, CRLDbHandle);
+ CRLs ->
+ CRLs
+ end.
+
+encrypted_premaster_secret(Secret, RSAPublicKey) ->
+ try
+ PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey,
+ [{rsa_pad,
+ rsa_pkcs1_padding}]),
+ #encrypted_premaster_secret{premaster_secret = PreMasterSecret}
+ catch
+ _:_->
+ 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) ->
@@ -1708,24 +1878,7 @@ calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom)
calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) ->
tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom).
-
-handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite,
- ClientCipherSuites, Compression,
- ConnectionStates0, Renegotiation, SecureRenegotation) ->
- case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
- Renegotiation, SecureRenegotation,
- ClientCipherSuites) of
- {ok, ConnectionStates} ->
- hello_pending_connection_states(RecordCB, Role,
- Version,
- NegotiatedCipherSuite,
- Random,
- Compression,
- ConnectionStates);
- #alert{} = Alert ->
- throw(Alert)
- end.
-
+
%% Update pending connection states with parameters exchanged via
%% hello messages
%% NOTE : Role is the role of the receiver of the hello message
@@ -1765,7 +1918,43 @@ hello_security_parameters(server, Version, #{security_parameters := SecParams},
compression_algorithm = Compression
}.
-%%-------------Encode/Decode --------------------------------
+select_compression(_CompressionMetodes) ->
+ ?NULL.
+
+do_select_version(_, ClientVersion, []) ->
+ ClientVersion;
+do_select_version(RecordCB, ClientVersion, [Version | Versions]) ->
+ case RecordCB:is_higher(Version, ClientVersion) of
+ true ->
+ %% Version too high for client - keep looking
+ do_select_version(RecordCB, ClientVersion, Versions);
+ false ->
+ %% Version ok for client - look for a higher
+ do_select_version(RecordCB, ClientVersion, Versions, Version)
+ end.
+%%
+do_select_version(_, _, [], GoodVersion) ->
+ GoodVersion;
+do_select_version(
+ RecordCB, ClientVersion, [Version | Versions], GoodVersion) ->
+ BetterVersion =
+ case RecordCB:is_higher(Version, ClientVersion) of
+ true ->
+ %% Version too high for client
+ GoodVersion;
+ false ->
+ %% Version ok for client
+ case RecordCB:is_higher(Version, GoodVersion) of
+ true ->
+ %% Use higher version
+ Version;
+ false ->
+ GoodVersion
+ end
+ end,
+ do_select_version(RecordCB, ClientVersion, Versions, BetterVersion).
+
+%%-------------Encode handshakes --------------------------------
encode_server_key(#server_dh_params{dh_p = P, dh_g = G, dh_y = Y}) ->
PLen = byte_size(P),
@@ -1791,6 +1980,18 @@ encode_server_key(#server_dhe_psk_params{
YLen = byte_size(Y),
<<?UINT16(Len), PskIdentityHint/binary,
?UINT16(PLen), P/binary, ?UINT16(GLen), G/binary, ?UINT16(YLen), Y/binary>>;
+encode_server_key(Params = #server_ecdhe_psk_params{hint = undefined}) ->
+ encode_server_key(Params#server_ecdhe_psk_params{hint = <<>>});
+encode_server_key(#server_ecdhe_psk_params{
+ hint = PskIdentityHint,
+ dh_params = #server_ecdh_params{
+ curve = {namedCurve, ECCurve}, public = ECPubKey}}) ->
+ %%TODO: support arbitrary keys
+ Len = byte_size(PskIdentityHint),
+ KLen = size(ECPubKey),
+ <<?UINT16(Len), PskIdentityHint/binary,
+ ?BYTE(?NAMED_CURVE), ?UINT16((tls_v1:oid_to_enum(ECCurve))),
+ ?BYTE(KLen), ECPubKey/binary>>;
encode_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}) ->
NLen = byte_size(N),
GLen = byte_size(G),
@@ -1823,6 +2024,12 @@ encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic},
Len = byte_size(Id),
DHLen = byte_size(DHPublic),
<<?UINT16(Len), Id/binary, ?UINT16(DHLen), DHPublic/binary>>;
+encode_client_key(Identity = #client_ecdhe_psk_identity{identity = undefined}, Version) ->
+ encode_client_key(Identity#client_ecdhe_psk_identity{identity = <<"psk_identity">>}, Version);
+encode_client_key(#client_ecdhe_psk_identity{identity = Id, dh_public = DHPublic}, _) ->
+ Len = byte_size(Id),
+ DHLen = byte_size(DHPublic),
+ <<?UINT16(Len), Id/binary, ?BYTE(DHLen), DHPublic/binary>>;
encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}, Version) ->
encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version);
encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) ->
@@ -1853,6 +2060,177 @@ encode_protocol(Protocol, Acc) ->
Len = byte_size(Protocol),
<<Acc/binary, ?BYTE(Len), Protocol/binary>>.
+enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo},
+ ClientRandom, ServerRandom, PrivateKey) ->
+ EncParams = encode_server_key(Params),
+ case HashAlgo of
+ null ->
+ #server_key_params{params = Params,
+ params_bin = EncParams,
+ hashsign = {null, anon},
+ signature = <<>>};
+ _ ->
+ Hash =
+ server_key_exchange_hash(HashAlgo, <<ClientRandom/binary,
+ ServerRandom/binary,
+ EncParams/binary>>),
+ Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey),
+ #server_key_params{params = Params,
+ params_bin = EncParams,
+ hashsign = {HashAlgo, SignAlgo},
+ signature = Signature}
+ end.
+
+%% While the RFC opens the door to allow ALPN during renegotiation, in practice
+%% this does not work and it is recommended to ignore any ALPN extension during
+%% renegotiation, as done here.
+encode_alpn(_, true) ->
+ undefined;
+encode_alpn(undefined, _) ->
+ undefined;
+encode_alpn(Protocols, _) ->
+ #alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
+
+
+encode_versions(Versions) ->
+ encode_versions(lists:reverse(Versions), <<>>).
+%%
+encode_versions([], Acc) ->
+ Acc;
+encode_versions([{M,N}|T], Acc) ->
+ encode_versions(T, <<?BYTE(M),?BYTE(N),Acc/binary>>).
+
+encode_client_shares(ClientShares) ->
+ encode_client_shares(ClientShares, <<>>).
+%%
+encode_client_shares([], Acc) ->
+ Acc;
+encode_client_shares([KeyShareEntry0|T], Acc) ->
+ KeyShareEntry = encode_key_share_entry(KeyShareEntry0),
+ encode_client_shares(T, <<Acc/binary,KeyShareEntry/binary>>).
+
+encode_key_share_entry(#key_share_entry{
+ group = Group,
+ key_exchange = KeyExchange}) ->
+ Len = byte_size(KeyExchange),
+ <<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>.
+
+encode_psk_key_exchange_modes(KEModes) ->
+ encode_psk_key_exchange_modes(lists:reverse(KEModes), <<>>).
+%%
+encode_psk_key_exchange_modes([], Acc) ->
+ Acc;
+encode_psk_key_exchange_modes([psk_ke|T], Acc) ->
+ encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_KE),Acc/binary>>);
+encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) ->
+ encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_DHE_KE),Acc/binary>>).
+
+
+encode_psk_identities(Identities) ->
+ encode_psk_identities(Identities, <<>>).
+%%
+encode_psk_identities([], Acc) ->
+ Len = byte_size(Acc),
+ <<?UINT16(Len), Acc/binary>>;
+encode_psk_identities([#psk_identity{
+ identity = Identity,
+ obfuscated_ticket_age = Age}|T], Acc) ->
+ IdLen = byte_size(Identity),
+ encode_psk_identities(T, <<Acc/binary,?UINT16(IdLen),Identity/binary,Age/binary>>).
+
+
+encode_psk_binders(Binders) ->
+ encode_psk_binders(Binders, <<>>).
+%%
+encode_psk_binders([], Acc) ->
+ Len = byte_size(Acc),
+ <<?UINT16(Len), Acc/binary>>;
+encode_psk_binders([Binder|T], Acc) ->
+ Len = byte_size(Binder),
+ encode_psk_binders(T, <<Acc/binary,?BYTE(Len),Binder/binary>>).
+
+
+hello_extensions_list(HelloExtensions) ->
+ [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined].
+
+%%-------------Decode handshakes---------------------------------
+dec_server_key(<<?UINT16(PLen), P:PLen/binary,
+ ?UINT16(GLen), G:GLen/binary,
+ ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct,
+ ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) ->
+ Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y},
+ {BinMsg, HashSign, Signature} = dec_server_key_params(PLen + GLen + YLen + 6, KeyStruct, Version),
+ #server_key_params{params = Params,
+ params_bin = BinMsg,
+ hashsign = HashSign,
+ signature = Signature};
+%% ECParameters with named_curve
+%% TODO: explicit curve
+dec_server_key(<<?BYTE(?NAMED_CURVE), ?UINT16(CurveID),
+ ?BYTE(PointLen), ECPoint:PointLen/binary,
+ _/binary>> = KeyStruct,
+ ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) ->
+ Params = #server_ecdh_params{curve = {namedCurve, tls_v1:enum_to_oid(CurveID)},
+ public = ECPoint},
+ {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version),
+ #server_key_params{params = Params,
+ params_bin = BinMsg,
+ hashsign = HashSign,
+ signature = Signature};
+dec_server_key(<<?UINT16(Len), PskIdentityHint:Len/binary, _/binary>> = KeyStruct,
+ KeyExchange, Version)
+ when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK ->
+ Params = #server_psk_params{
+ hint = PskIdentityHint},
+ {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version),
+ #server_key_params{params = Params,
+ params_bin = BinMsg,
+ hashsign = HashSign,
+ signature = Signature};
+dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary,
+ ?UINT16(PLen), P:PLen/binary,
+ ?UINT16(GLen), G:GLen/binary,
+ ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct,
+ ?KEY_EXCHANGE_DHE_PSK, Version) ->
+ DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y},
+ Params = #server_dhe_psk_params{
+ hint = IdentityHint,
+ dh_params = DHParams},
+ {BinMsg, HashSign, Signature} = dec_server_key_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version),
+ #server_key_params{params = Params,
+ params_bin = BinMsg,
+ hashsign = HashSign,
+ signature = Signature};
+dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary,
+ ?BYTE(?NAMED_CURVE), ?UINT16(CurveID),
+ ?BYTE(PointLen), ECPoint:PointLen/binary,
+ _/binary>> = KeyStruct,
+ ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, Version) ->
+ DHParams = #server_ecdh_params{
+ curve = {namedCurve, tls_v1:enum_to_oid(CurveID)},
+ public = ECPoint},
+ Params = #server_ecdhe_psk_params{
+ hint = IdentityHint,
+ dh_params = DHParams},
+ {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2 + PointLen + 4, KeyStruct, Version),
+ #server_key_params{params = Params,
+ params_bin = BinMsg,
+ hashsign = HashSign,
+ signature = Signature};
+dec_server_key(<<?UINT16(NLen), N:NLen/binary,
+ ?UINT16(GLen), G:GLen/binary,
+ ?BYTE(SLen), S:SLen/binary,
+ ?UINT16(BLen), B:BLen/binary, _/binary>> = KeyStruct,
+ ?KEY_EXCHANGE_SRP, Version) ->
+ Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B},
+ {BinMsg, HashSign, Signature} = dec_server_key_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version),
+ #server_key_params{params = Params,
+ params_bin = BinMsg,
+ hashsign = HashSign,
+ signature = Signature};
+dec_server_key(_, KeyExchange, _) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})).
+
dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) ->
#encrypted_premaster_secret{premaster_secret = PKEPMS};
dec_client_key(<<?UINT16(_), PKEPMS/binary>>, ?KEY_EXCHANGE_RSA, _) ->
@@ -1874,6 +2252,10 @@ dec_client_key(<<?UINT16(Len), Id:Len/binary,
?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>,
?KEY_EXCHANGE_DHE_PSK, _) ->
#client_dhe_psk_identity{identity = Id, dh_public = DH_Y};
+dec_client_key(<<?UINT16(Len), Id:Len/binary,
+ ?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>,
+ ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, _) ->
+ #client_ecdhe_psk_identity{identity = Id, dh_public = DH_Y};
dec_client_key(<<?UINT16(Len), Id:Len/binary, PKEPMS/binary>>,
?KEY_EXCHANGE_RSA_PSK, {3, 0}) ->
#client_rsa_psk_identity{identity = Id,
@@ -1909,16 +2291,60 @@ dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) ->
dec_server_key_signature(_, _, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)).
-dec_hello_extensions(<<>>, Acc) ->
+%% Processes a ClientHello/ServerHello message and returns the version to be used
+%% in the decoding functions. The following rules apply:
+%% - IF supported_versions extension is absent:
+%% RETURN the lowest of (LocalVersion and LegacyVersion)
+%% - IF supported_versions estension is present:
+%% RETURN the lowest of (LocalVersion and first element of supported versions)
+process_supported_versions_extension(<<>>, LocalVersion, LegacyVersion)
+ when LegacyVersion =< LocalVersion ->
+ LegacyVersion;
+process_supported_versions_extension(<<>>, LocalVersion, _LegacyVersion) ->
+ LocalVersion;
+process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, _Rest/binary>>,
+ LocalVersion, _LegacyVersion) when Len > 2 ->
+ <<?BYTE(_),Versions0/binary>> = ExtData,
+ [Highest|_] = decode_versions(Versions0),
+ if Highest =< LocalVersion ->
+ Highest;
+ true ->
+ LocalVersion
+ end;
+process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ?BYTE(Major),?BYTE(Minor), _Rest/binary>>,
+ LocalVersion, _LegacyVersion) when Len =:= 2 ->
+ SelectedVersion = {Major, Minor},
+ if SelectedVersion =< LocalVersion ->
+ SelectedVersion;
+ true ->
+ LocalVersion
+ end;
+process_supported_versions_extension(<<?UINT16(_), ?UINT16(Len),
+ _ExtData:Len/binary, Rest/binary>>,
+ LocalVersion, LegacyVersion) ->
+ process_supported_versions_extension(Rest, LocalVersion, LegacyVersion);
+%% Tolerate protocol encoding errors and skip parsing the rest of the extension.
+process_supported_versions_extension(_, LocalVersion, LegacyVersion)
+ when LegacyVersion =< LocalVersion ->
+ LegacyVersion;
+process_supported_versions_extension(_, LocalVersion, _) ->
+ LocalVersion.
+
+decode_extensions(<<>>, _Version, _MessageType, Acc) ->
Acc;
-dec_hello_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc)
- when Len + 2 =:= ExtLen ->
+decode_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
+ ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Len + 2 =:= ExtLen ->
ALPN = #alpn{extension_data = ExtensionData},
- dec_hello_extensions(Rest, Acc#hello_extensions{alpn = ALPN});
-dec_hello_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType, Acc#{alpn => ALPN});
+decode_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len),
+ ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
NextP = #next_protocol_negotiation{extension_data = ExtensionData},
- dec_hello_extensions(Rest, Acc#hello_extensions{next_protocol_negotiation = NextP});
-dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType, Acc#{next_protocol_negotiation => NextP});
+decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len),
+ Info:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
RenegotiateInfo = case Len of
1 -> % Initial handshake
Info; % should be <<0>> will be matched in handle_renegotiation_info
@@ -1927,25 +2353,54 @@ dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binar
<<?BYTE(VerifyLen), VerifyInfo/binary>> = Info,
VerifyInfo
end,
- dec_hello_extensions(Rest, Acc#hello_extensions{renegotiation_info =
- #renegotiation_info{renegotiated_connection =
- RenegotiateInfo}});
-
-dec_hello_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), SRP:SRPLen/binary, Rest/binary>>, Acc)
- when Len == SRPLen + 2 ->
- dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}});
-
-dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{renegotiation_info =>
+ #renegotiation_info{renegotiated_connection =
+ RenegotiateInfo}});
+
+decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
+ SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Len == SRPLen + 1 ->
+ decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}});
+
+decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Version < {3,4} ->
SignAlgoListLen = Len - 2,
<<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData,
HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} ||
<<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList],
- dec_hello_extensions(Rest, Acc#hello_extensions{signature_algs =
- #hash_sign_algos{hash_sign_algos = HashSignAlgos}});
-
-dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{signature_algs =>
+ #hash_sign_algos{hash_sign_algos =
+ HashSignAlgos}});
+
+decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Version =:= {3,4} ->
+ SignSchemeListLen = Len - 2,
+ <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
+ SignSchemes = [ssl_cipher:signature_scheme(SignScheme) ||
+ <<?UINT16(SignScheme)>> <= SignSchemeList],
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{signature_algs =>
+ #signature_algorithms{
+ signature_scheme_list = SignSchemes}});
+
+decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ SignSchemeListLen = Len - 2,
+ <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
+ SignSchemes = [ssl_cipher:signature_scheme(SignScheme) ||
+ <<?UINT16(SignScheme)>> <= SignSchemeList],
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{signature_algs_cert =>
+ #signature_algorithms_cert{
+ signature_scheme_list = SignSchemes}});
+
+decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Version < {3,4} ->
<<?UINT16(_), EllipticCurveList/binary>> = ExtData,
%% Ignore unknown curves
Pick = fun(Enum) ->
@@ -1957,31 +2412,130 @@ dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
end
end,
EllipticCurves = lists:filtermap(Pick, [ECC || <<ECC:16>> <= EllipticCurveList]),
- dec_hello_extensions(Rest, Acc#hello_extensions{elliptic_curves =
- #elliptic_curves{elliptic_curve_list =
- EllipticCurves}});
-dec_hello_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{elliptic_curves =>
+ #elliptic_curves{elliptic_curve_list =
+ EllipticCurves}});
+
+decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Version =:= {3,4} ->
+ <<?UINT16(_), GroupList/binary>> = ExtData,
+ %% Ignore unknown curves
+ Pick = fun(Enum) ->
+ case tls_v1:enum_to_group(Enum) of
+ undefined ->
+ false;
+ Group ->
+ {true, Group}
+ end
+ end,
+ SupportedGroups = lists:filtermap(Pick, [Group || <<Group:16>> <= GroupList]),
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{elliptic_curves =>
+ #supported_groups{supported_groups =
+ SupportedGroups}});
+
+decode_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
<<?BYTE(_), ECPointFormatList/binary>> = ExtData,
ECPointFormats = binary_to_list(ECPointFormatList),
- dec_hello_extensions(Rest, Acc#hello_extensions{ec_point_formats =
- #ec_point_formats{ec_point_format_list =
- ECPointFormats}});
-
-dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), Rest/binary>>, Acc) when Len == 0 ->
- dec_hello_extensions(Rest, Acc#hello_extensions{sni = ""}); %% Server may send an empy SNI
-
-dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{ec_point_formats =>
+ #ec_point_formats{ec_point_format_list =
+ ECPointFormats}});
+
+decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
+ Rest/binary>>, Version, MessageType, Acc) when Len == 0 ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{sni => #sni{hostname = ""}}); %% Server may send an empy SNI
+
+decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
<<?UINT16(_), NameList/binary>> = ExtData,
- dec_hello_extensions(Rest, Acc#hello_extensions{sni = dec_sni(NameList)});
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{sni => dec_sni(NameList)});
+
+decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 ->
+ <<?BYTE(_),Versions/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{client_hello_versions =>
+ #client_hello_versions{
+ versions = decode_versions(Versions)}});
+
+decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ?UINT16(SelectedVersion), Rest/binary>>, Version, MessageType, Acc)
+ when Len =:= 2, SelectedVersion =:= 16#0304 ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{server_hello_selected_version =>
+ #server_hello_selected_version{selected_version =
+ {3,4}}});
+
+decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = client_hello, Acc) ->
+ <<?UINT16(_),ClientShares/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{key_share =>
+ #key_share_client_hello{
+ client_shares = decode_client_shares(ClientShares)}});
+
+decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = server_hello, Acc) ->
+ <<?UINT16(Group),?UINT16(KeyLen),KeyExchange:KeyLen/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{key_share =>
+ #key_share_server_hello{
+ server_share =
+ #key_share_entry{
+ group = tls_v1:enum_to_group(Group),
+ key_exchange = KeyExchange}}});
+
+decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = hello_retry_request, Acc) ->
+ <<?UINT16(Group)>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{key_share =>
+ #key_share_hello_retry_request{
+ selected_group = tls_v1:enum_to_group(Group)}});
+
+decode_extensions(<<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ <<?BYTE(PLen),KEModes:PLen/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{psk_key_exchange_modes =>
+ #psk_key_exchange_modes{
+ ke_modes = decode_psk_key_exchange_modes(KEModes)}});
+
+decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = client_hello, Acc) ->
+ <<?UINT16(IdLen),Identities:IdLen/binary,?UINT16(BLen),Binders:BLen/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{pre_shared_key =>
+ #pre_shared_key_client_hello{
+ offered_psks = #offered_psks{
+ identities = decode_psk_identities(Identities),
+ binders = decode_psk_binders(Binders)}}});
+
+decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = server_hello, Acc) ->
+ <<?UINT16(Identity)>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{pre_shared_key =>
+ #pre_shared_key_server_hello{
+ selected_identity = Identity}});
+
%% Ignore data following the ClientHello (i.e.,
%% extensions) if not understood.
-
-dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) ->
- dec_hello_extensions(Rest, Acc);
+decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ decode_extensions(Rest, Version, MessageType, Acc);
%% This theoretically should not happen if the protocol is followed, but if it does it is ignored.
-dec_hello_extensions(_, Acc) ->
+decode_extensions(_, _, _, Acc) ->
Acc.
dec_hashsign(<<?BYTE(HashAlgo), ?BYTE(SignAlgo)>>) ->
@@ -1994,6 +2548,31 @@ dec_sni(<<?BYTE(?SNI_NAMETYPE_HOST_NAME), ?UINT16(Len),
dec_sni(<<?BYTE(_), ?UINT16(Len), _:Len, Rest/binary>>) -> dec_sni(Rest);
dec_sni(_) -> undefined.
+decode_alpn(undefined) ->
+ undefined;
+decode_alpn(#alpn{extension_data=Data}) ->
+ decode_protocols(Data, []).
+
+decode_versions(Versions) ->
+ decode_versions(Versions, []).
+%%
+decode_versions(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_versions(<<?BYTE(M),?BYTE(N),Rest/binary>>, Acc) ->
+ decode_versions(Rest, [{M,N}|Acc]).
+
+
+decode_client_shares(ClientShares) ->
+ decode_client_shares(ClientShares, []).
+%%
+decode_client_shares(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_client_shares(<<?UINT16(Group),?UINT16(Len),KeyExchange:Len/binary,Rest/binary>>, Acc) ->
+ decode_client_shares(Rest, [#key_share_entry{
+ group = tls_v1:enum_to_group(Group),
+ key_exchange= KeyExchange
+ }|Acc]).
+
decode_next_protocols({next_protocol_negotiation, Protocols}) ->
decode_protocols(Protocols, []).
@@ -2009,6 +2588,38 @@ decode_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) ->
decode_protocols(_Bytes, _Acc) ->
{error, invalid_protocols}.
+
+decode_psk_key_exchange_modes(KEModes) ->
+ decode_psk_key_exchange_modes(KEModes, []).
+%%
+decode_psk_key_exchange_modes(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_psk_key_exchange_modes(<<?BYTE(?PSK_KE), Rest/binary>>, Acc) ->
+ decode_psk_key_exchange_modes(Rest, [psk_ke|Acc]);
+decode_psk_key_exchange_modes(<<?BYTE(?PSK_DHE_KE), Rest/binary>>, Acc) ->
+ decode_psk_key_exchange_modes(Rest, [psk_dhe_ke|Acc]).
+
+
+decode_psk_identities(Identities) ->
+ decode_psk_identities(Identities, []).
+%%
+decode_psk_identities(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_psk_identities(<<?UINT16(Len), Identity:Len/binary, Age:4/binary, Rest/binary>>, Acc) ->
+ decode_psk_identities(Rest, [#psk_identity{
+ identity = Identity,
+ obfuscated_ticket_age = Age}|Acc]).
+
+
+decode_psk_binders(Binders) ->
+ decode_psk_binders(Binders, []).
+%%
+decode_psk_binders(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_psk_binders(<<?BYTE(Len), Binder:Len/binary, Rest/binary>>, Acc) ->
+ decode_psk_binders(Rest, [Binder|Acc]).
+
+
%% encode/decode stream of certificate data to/from list of certificate data
certs_to_list(ASN1Certs) ->
certs_to_list(ASN1Certs, []).
@@ -2038,6 +2649,7 @@ from_2bytes(<<>>, Acc) ->
lists:reverse(Acc);
from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) ->
from_2bytes(Rest, [?uint16(N) | Acc]).
+
key_exchange_alg(rsa) ->
?KEY_EXCHANGE_RSA;
key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss;
@@ -2051,6 +2663,8 @@ key_exchange_alg(psk) ->
?KEY_EXCHANGE_PSK;
key_exchange_alg(dhe_psk) ->
?KEY_EXCHANGE_DHE_PSK;
+key_exchange_alg(ecdhe_psk) ->
+ ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK;
key_exchange_alg(rsa_psk) ->
?KEY_EXCHANGE_RSA_PSK;
key_exchange_alg(Alg)
@@ -2059,18 +2673,139 @@ key_exchange_alg(Alg)
key_exchange_alg(_) ->
?NULL.
+%%-------------Cipher suite handling -----------------------------
+select_cipher_suite(CipherSuites, Suites, false) ->
+ select_cipher_suite(CipherSuites, Suites);
+select_cipher_suite(CipherSuites, Suites, true) ->
+ select_cipher_suite(Suites, CipherSuites).
+
+select_cipher_suite([], _) ->
+ no_suite;
+select_cipher_suite([Suite | ClientSuites], SupportedSuites) ->
+ case is_member(Suite, SupportedSuites) of
+ true ->
+ Suite;
+ false ->
+ select_cipher_suite(ClientSuites, SupportedSuites)
+ end.
+
+is_member(Suite, SupportedSuites) ->
+ lists:member(Suite, SupportedSuites).
+
+psk_secret(PSKIdentity, PSKLookup) ->
+ case handle_psk_identity(PSKIdentity, PSKLookup) of
+ {ok, PSK} when is_binary(PSK) ->
+ Len = erlang:byte_size(PSK),
+ <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>;
+ #alert{} = Alert ->
+ Alert;
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end.
+
+psk_secret(PSKIdentity, PSKLookup, PremasterSecret) ->
+ case handle_psk_identity(PSKIdentity, PSKLookup) of
+ {ok, PSK} when is_binary(PSK) ->
+ Len = erlang:byte_size(PremasterSecret),
+ PSKLen = erlang:byte_size(PSK),
+ <<?UINT16(Len), PremasterSecret/binary, ?UINT16(PSKLen), PSK/binary>>;
+ #alert{} = Alert ->
+ Alert;
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end.
+
+handle_psk_identity(_PSKIdentity, LookupFun)
+ when LookupFun == undefined ->
+ error;
+handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
+ Fun(psk, PSKIdentity, UserState).
+
+
+filter_hashsigns([], [], _, _, Acc) ->
+ lists:reverse(Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
+ Acc) when KeyExchange == dhe_ecdsa;
+ KeyExchange == ecdhe_ecdsa ->
+ do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Version, Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
+ Acc) when KeyExchange == rsa;
+ KeyExchange == dhe_rsa;
+ KeyExchange == ecdhe_rsa;
+ KeyExchange == srp_rsa;
+ KeyExchange == rsa_psk ->
+ do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Version, Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, Acc) when
+ KeyExchange == dhe_dss;
+ KeyExchange == srp_dss ->
+ do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Version, Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Verion,
+ Acc) when
+ KeyExchange == dh_dss;
+ KeyExchange == dh_rsa;
+ KeyExchange == dh_ecdsa;
+ KeyExchange == ecdh_rsa;
+ KeyExchange == ecdh_ecdsa ->
+ %% Fixed DH certificates MAY be signed with any hash/signature
+ %% algorithm pair appearing in the hash_sign extension. The names
+ %% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical.
+ filter_hashsigns(Suites, Algos, HashSigns, Verion, [Suite| Acc]);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
+ Acc) when
+ KeyExchange == dh_anon;
+ KeyExchange == ecdh_anon;
+ KeyExchange == srp_anon;
+ KeyExchange == psk;
+ KeyExchange == dhe_psk;
+ KeyExchange == ecdhe_psk ->
+ %% In this case hashsigns is not used as the kexchange is anonaymous
+ filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]).
+
+do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Version, Acc) ->
+ case lists:keymember(SignAlgo, 2, HashSigns) of
+ true ->
+ filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
+ false ->
+ filter_hashsigns(Suites, Algos, HashSigns, Version, Acc)
+ end.
+
+filter_unavailable_ecc_suites(no_curve, Suites) ->
+ ECCSuites = ssl_cipher:filter_suites(Suites, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true;
+ (ecdhe_ecdsa) -> true;
+ (ecdh_rsa) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}),
+ Suites -- ECCSuites;
+filter_unavailable_ecc_suites(_, Suites) ->
+ Suites.
%%-------------Extension handling --------------------------------
+handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite,
+ ClientCipherSuites, Compression,
+ ConnectionStates0, Renegotiation, SecureRenegotation) ->
+ {ok, ConnectionStates} = handle_renegotiation_info(Version, RecordCB, Role, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation,
+ ClientCipherSuites),
+ hello_pending_connection_states(RecordCB, Role,
+ Version,
+ NegotiatedCipherSuite,
+ Random,
+ Compression,
+ ConnectionStates).
+
%% Receive protocols, choose one from the list, return it.
handle_alpn_extension(_, {error, Reason}) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason));
handle_alpn_extension([], _) ->
- ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL);
+ throw(?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL));
handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) ->
- case lists:member(ServerProtocol, ClientProtocols) of
- true -> ServerProtocol;
- false -> handle_alpn_extension(Tail, ClientProtocols)
- end.
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true -> ServerProtocol;
+ false -> handle_alpn_extension(Tail, ClientProtocols)
+ end.
handle_next_protocol(undefined,
_NextProtocolSelector, _Renegotiating) ->
@@ -2083,14 +2818,14 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols,
true ->
select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector);
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension))
end.
handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)->
case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of
#alert{} = Alert ->
- Alert;
+ throw(Alert);
ProtocolsToAdvertise ->
ProtocolsToAdvertise
end.
@@ -2123,236 +2858,339 @@ handle_srp_extension(undefined, Session) ->
handle_srp_extension(#srp{username = Username}, Session) ->
Session#session{srp_username = Username}.
-%%-------------Misc --------------------------------
-
-select_cipher_suite(CipherSuites, Suites, false) ->
- select_cipher_suite(CipherSuites, Suites);
-select_cipher_suite(CipherSuites, Suites, true) ->
- select_cipher_suite(Suites, CipherSuites).
+is_acceptable_hash_sign( _, KeyExAlgo, _) when
+ KeyExAlgo == psk;
+ KeyExAlgo == dhe_psk;
+ KeyExAlgo == ecdhe_psk;
+ KeyExAlgo == srp_anon;
+ KeyExAlgo == dh_anon;
+ KeyExAlgo == ecdhe_anon
+ ->
+ true;
+is_acceptable_hash_sign(Algos,_, SupportedHashSigns) ->
+ is_acceptable_hash_sign(Algos, SupportedHashSigns).
-select_cipher_suite([], _) ->
- no_suite;
-select_cipher_suite([Suite | ClientSuites], SupportedSuites) ->
- case is_member(Suite, SupportedSuites) of
- true ->
- Suite;
- false ->
- select_cipher_suite(ClientSuites, SupportedSuites)
- end.
+is_acceptable_hash_sign(Algos, SupportedHashSigns) ->
+ lists:member(Algos, SupportedHashSigns).
-int_to_bin(I) ->
- L = (length(integer_to_list(I, 16)) + 1) div 2,
- <<I:(L*8)>>.
+is_acceptable_cert_type(Sign, Types) ->
+ lists:member(sign_type(Sign), binary_to_list(Types)).
-is_member(Suite, SupportedSuites) ->
- lists:member(Suite, SupportedSuites).
+%% signature_algorithms_cert = undefined
+is_supported_sign(SignAlgo, _, HashSigns, undefined) ->
+ lists:member(SignAlgo, HashSigns);
+
+%% {'SignatureAlgorithm',{1,2,840,113549,1,1,11},'NULL'}
+is_supported_sign({Hash, Sign}, 'NULL', _, SignatureSchemes) ->
+ Fun = fun (Scheme, Acc) ->
+ {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme),
+ S1 = case S0 of
+ rsa_pkcs1 -> rsa;
+ S -> S
+ end,
+ H1 = case H0 of
+ sha1 -> sha;
+ H -> H
+ end,
+ Acc orelse (Sign =:= S1 andalso
+ Hash =:= H1)
+ end,
+ lists:foldl(Fun, false, SignatureSchemes);
+
+%% TODO: Implement validation for the curve used in the signature
+%% RFC 3279 - 2.2.3 ECDSA Signature Algorithm
+%% When the ecdsa-with-SHA1 algorithm identifier appears as the
+%% algorithm field in an AlgorithmIdentifier, the encoding MUST omit the
+%% parameters field. That is, the AlgorithmIdentifier SHALL be a
+%% SEQUENCE of one component: the OBJECT IDENTIFIER ecdsa-with-SHA1.
+%%
+%% The elliptic curve parameters in the subjectPublicKeyInfo field of
+%% the certificate of the issuer SHALL apply to the verification of the
+%% signature.
+is_supported_sign({Hash, Sign}, _Param, _, SignatureSchemes) ->
+ Fun = fun (Scheme, Acc) ->
+ {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme),
+ S1 = case S0 of
+ rsa_pkcs1 -> rsa;
+ S -> S
+ end,
+ H1 = case H0 of
+ sha1 -> sha;
+ H -> H
+ end,
+ Acc orelse (Sign =:= S1 andalso
+ Hash =:= H1)
+ end,
+ lists:foldl(Fun, false, SignatureSchemes).
+
+%% SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
+%% dsa | rsa-encryption | dh | kea | ec-public-key }
+public_key_algo(?rsaEncryption) ->
+ rsa;
+public_key_algo(?'id-ecPublicKey') ->
+ ecdsa;
+public_key_algo(?'id-dsa') ->
+ dsa.
+
+%% SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
+%% dsa-with-sha1 | dsaWithSHA1 | md2-with-rsa-encryption |
+%% md5-with-rsa-encryption | sha1-with-rsa-encryption | sha-1with-rsa-encryption |
+%% sha224-with-rsa-encryption |
+%% sha256-with-rsa-encryption |
+%% sha384-with-rsa-encryption |
+%% sha512-with-rsa-encryption |
+%% ecdsa-with-sha1 |
+%% ecdsa-with-sha224 |
+%% ecdsa-with-sha256 |
+%% ecdsa-with-sha384 |
+%% ecdsa-with-sha512 }
+sign_algo(Alg) ->
+ public_key:pkix_sign_types(Alg).
-select_compression(_CompressionMetodes) ->
- ?NULL.
+sign_type(rsa) ->
+ ?RSA_SIGN;
+sign_type(dsa) ->
+ ?DSS_SIGN;
+sign_type(ecdsa) ->
+ ?ECDSA_SIGN.
-available_signature_algs(undefined, _, _) ->
- undefined;
-available_signature_algs(SupportedHashSigns, {Major, Minor}, AllVersions) when Major >= 3 andalso Minor >= 3 ->
- case tls_record:lowest_protocol_version(AllVersions) of
- {3, 3} ->
- #hash_sign_algos{hash_sign_algos = SupportedHashSigns};
- _ ->
- undefined
- end;
-available_signature_algs(_, _, _) ->
- undefined.
+server_name(_, _, server) ->
+ undefined; %% Not interesting to check your own name.
+server_name(undefined, Host, client) ->
+ {fallback, Host}; %% Fallback to Host argument to connect
+server_name(SNI, _, client) ->
+ SNI. %% If Server Name Indication is available
-psk_secret(PSKIdentity, PSKLookup) ->
- case handle_psk_identity(PSKIdentity, PSKLookup) of
- {ok, PSK} when is_binary(PSK) ->
- Len = erlang:byte_size(PSK),
- <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>;
- #alert{} = Alert ->
- Alert;
+client_ecc_extensions(SupportedECCs) ->
+ CryptoSupport = proplists:get_value(public_keys, crypto:supports()),
+ case proplists:get_bool(ecdh, CryptoSupport) of
+ true ->
+ %% RFC 8422 - 5.1. Client Hello Extensions
+ %% Clients SHOULD send both the Supported Elliptic Curves Extension and the
+ %% Supported Point Formats Extension. If the Supported Point Formats
+ %% Extension is indeed sent, it MUST contain the value 0 (uncompressed)
+ %% as one of the items in the list of point formats.
+ EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]},
+ EllipticCurves = SupportedECCs,
+ {EcPointFormats, EllipticCurves};
_ ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ {undefined, undefined}
end.
-psk_secret(PSKIdentity, PSKLookup, PremasterSecret) ->
- case handle_psk_identity(PSKIdentity, PSKLookup) of
- {ok, PSK} when is_binary(PSK) ->
- Len = erlang:byte_size(PremasterSecret),
- PSKLen = erlang:byte_size(PSK),
- <<?UINT16(Len), PremasterSecret/binary, ?UINT16(PSKLen), PSK/binary>>;
- #alert{} = Alert ->
- Alert;
- _ ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+server_ecc_extension(_Version, EcPointFormats) ->
+ CryptoSupport = proplists:get_value(public_keys, crypto:supports()),
+ case proplists:get_bool(ecdh, CryptoSupport) of
+ true ->
+ handle_ecc_point_fmt_extension(EcPointFormats);
+ false ->
+ undefined
end.
-handle_psk_identity(_PSKIdentity, LookupFun)
- when LookupFun == undefined ->
- error;
-handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
- Fun(psk, PSKIdentity, UserState).
+handle_ecc_point_fmt_extension(undefined) ->
+ undefined;
+handle_ecc_point_fmt_extension(_) ->
+ #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}.
-crl_check(_, false, _,_,_, _, _) ->
- valid;
-crl_check(_, peer, _, _,_, valid, _) -> %% Do not check CAs with this option.
- valid;
-crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath) ->
- Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) ->
- ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath,
- DBInfo})
- end, {CertDbHandle, CertDbRef}}},
- {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end}
- ],
- case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) of
- no_dps ->
- crl_check_same_issuer(OtpCert, Check,
- dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer),
- Options);
- DpsAndCRLs -> %% This DP list may be empty if relevant CRLs existed
- %% but could not be retrived, will result in {bad_cert, revocation_status_undetermined}
- case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of
- {bad_cert, revocation_status_undetermined} ->
- crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback,
- CRLDbHandle, same_issuer), Options);
- Other ->
- Other
- end
+advertises_ec_ciphers([]) ->
+ false;
+advertises_ec_ciphers([#{key_exchange := ecdh_ecdsa} | _]) ->
+ true;
+advertises_ec_ciphers([#{key_exchange := ecdhe_ecdsa} | _]) ->
+ true;
+advertises_ec_ciphers([#{key_exchange := ecdh_rsa} | _]) ->
+ true;
+advertises_ec_ciphers([#{key_exchange := ecdhe_rsa} | _]) ->
+ true;
+advertises_ec_ciphers([#{key_exchange := ecdh_anon} | _]) ->
+ true;
+advertises_ec_ciphers([{ecdhe_psk, _,_,_} | _]) ->
+ true;
+advertises_ec_ciphers([_| Rest]) ->
+ advertises_ec_ciphers(Rest).
+
+select_shared_curve([], _) ->
+ no_curve;
+select_shared_curve([Curve | Rest], Curves) ->
+ case lists:member(Curve, Curves) of
+ true ->
+ {namedCurve, Curve};
+ false ->
+ select_shared_curve(Rest, Curves)
end.
-crl_check_same_issuer(OtpCert, best_effort, Dps, Options) ->
- case public_key:pkix_crls_validate(OtpCert, Dps, Options) of
- {bad_cert, revocation_status_undetermined} ->
- valid;
- Other ->
- Other
- end;
-crl_check_same_issuer(OtpCert, _, Dps, Options) ->
- public_key:pkix_crls_validate(OtpCert, Dps, Options).
+sni(undefined) ->
+ undefined;
+sni(disable) ->
+ undefined;
+sni(Hostname) ->
+ #sni{hostname = Hostname}.
-dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) ->
- case public_key:pkix_dist_points(OtpCert) of
- [] ->
- no_dps;
- DistPoints ->
- Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer,
- CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle),
- dps_and_crls(DistPoints, CRLs, [])
+renegotiation_info(_, client, _, false) ->
+ #renegotiation_info{renegotiated_connection = undefined};
+renegotiation_info(_RecordCB, server, ConnectionStates, false) ->
+ ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
+ case maps:get(secure_renegotiation, ConnectionState) of
+ true ->
+ #renegotiation_info{renegotiated_connection = ?byte(0)};
+ false ->
+ #renegotiation_info{renegotiated_connection = undefined}
+ end;
+renegotiation_info(_RecordCB, client, ConnectionStates, true) ->
+ ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
+ case maps:get(secure_renegotiation, ConnectionState) of
+ true ->
+ Data = maps:get(client_verify_data, ConnectionState),
+ #renegotiation_info{renegotiated_connection = Data};
+ false ->
+ #renegotiation_info{renegotiated_connection = undefined}
end;
-dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer) ->
- DP = #'DistributionPoint'{distributionPoint = {fullName, GenNames}} =
- public_key:pkix_dist_point(OtpCert),
- CRLs = lists:flatmap(fun({directoryName, Issuer}) ->
- Callback:select(Issuer, CRLDbHandle);
- (_) ->
- []
- end, GenNames),
- [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs].
+renegotiation_info(_RecordCB, server, ConnectionStates, true) ->
+ ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
+ case maps:get(secure_renegotiation, ConnectionState) of
+ true ->
+ CData = maps:get(client_verify_data, ConnectionState),
+ SData = maps:get(server_verify_data, ConnectionState),
+ #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>};
+ false ->
+ #renegotiation_info{renegotiated_connection = undefined}
+ end.
-dps_and_crls([], _, Acc) ->
- Acc;
-dps_and_crls([DP | Rest], CRLs, Acc) ->
- DpCRL = [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs],
- dps_and_crls(Rest, CRLs, DpCRL ++ Acc).
-
-distpoints_lookup([],_, _, _) ->
- [];
-distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) ->
- Result =
- try Callback:lookup(DistPoint, Issuer, CRLDbHandle)
- catch
- error:undef ->
- %% The callback module still uses the 2-argument
- %% version of the lookup function.
- Callback:lookup(DistPoint, CRLDbHandle)
- end,
- case Result of
- not_available ->
- distpoints_lookup(Rest, Issuer, Callback, CRLDbHandle);
- CRLs ->
- CRLs
- end.
+handle_renegotiation_info(_, _RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)},
+ ConnectionStates, false, _, _) ->
+ {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
-sign_algo(?rsaEncryption) ->
- rsa;
-sign_algo(?'id-ecPublicKey') ->
- ecdsa;
-sign_algo(?'id-dsa') ->
- dsa;
-sign_algo(Alg) ->
- {_, Sign} =public_key:pkix_sign_types(Alg),
- Sign.
-
-is_acceptable_hash_sign(Algos, _, _, KeyExAlgo, SupportedHashSigns) when
- KeyExAlgo == dh_dss;
- KeyExAlgo == dh_rsa;
- KeyExAlgo == dh_ecdsa ->
- %% dh_* could be called only dh in TLS-1.2
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign(Algos, rsa, ecdsa, ecdh_rsa, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, dhe_rsa, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, ecdhe_rsa, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, rsa, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, srp_rsa, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, rsa_psk, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, dhe_dss, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, srp_dss, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, _, dhe_ecdsa, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdhe_ecdsa, SupportedHashSigns) ->
- is_acceptable_hash_sign(Algos, SupportedHashSigns);
-is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when
- KeyExAlgo == psk;
- KeyExAlgo == dhe_psk;
- KeyExAlgo == srp_anon;
- KeyExAlgo == dh_anon;
- KeyExAlgo == ecdhe_anon
- ->
- true;
-is_acceptable_hash_sign(_,_, _,_,_) ->
- false.
+handle_renegotiation_info(_, _RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) ->
+ case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
+ true ->
+ {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
+ false ->
+ {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}
+ end;
-is_acceptable_hash_sign(Algos, SupportedHashSigns) ->
- lists:member(Algos, SupportedHashSigns).
+handle_renegotiation_info(_, _RecordCB, _, undefined, ConnectionStates, false, _, _) ->
+ {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)};
-is_acceptable_cert_type(Sign, _HashSigns, Types) ->
- lists:member(sign_type(Sign), binary_to_list(Types)).
+handle_renegotiation_info(_, _RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify},
+ ConnectionStates, true, _, _) ->
+ ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
+ CData = maps:get(client_verify_data, ConnectionState),
+ SData = maps:get(server_verify_data, ConnectionState),
+ case <<CData/binary, SData/binary>> == ClientServerVerify of
+ true ->
+ {ok, ConnectionStates};
+ false ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation))
+ end;
+handle_renegotiation_info(_, _RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
+ ConnectionStates, true, _, CipherSuites) ->
-is_supported_sign(Sign, HashSigns) ->
- [] =/= lists:dropwhile(fun({_, S}) when S =/= Sign ->
- true;
- (_)->
- false
- end, HashSigns).
-sign_type(rsa) ->
- ?RSA_SIGN;
-sign_type(dsa) ->
- ?DSS_SIGN;
-sign_type(ecdsa) ->
- ?ECDSA_SIGN.
+ case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
+ true ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
+ false ->
+ ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
+ Data = maps:get(client_verify_data, ConnectionState),
+ case Data == ClientVerify of
+ true ->
+ {ok, ConnectionStates};
+ false ->
+ 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);
-bad_key(#'DSAPrivateKey'{}) ->
- unacceptable_dsa_key;
-bad_key(#'RSAPrivateKey'{}) ->
- unacceptable_rsa_key;
-bad_key(#'ECPrivateKey'{}) ->
- unacceptable_ecdsa_key.
+handle_renegotiation_info(_, RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
+ case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
+ true ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
+ false ->
+ handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation)
+ end.
-available_signature_algs(undefined, SupportedHashSigns, _, {Major, Minor}) when
- (Major >= 3) andalso (Minor >= 3) ->
- SupportedHashSigns;
-available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns,
- _, {Major, Minor}) when (Major >= 3) andalso (Minor >= 3) ->
- sets:to_list(sets:intersection(sets:from_list(ClientHashSigns),
- sets:from_list(SupportedHashSigns)));
-available_signature_algs(_, _, _, _) ->
- undefined.
+handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
+ ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
+ case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of
+ {_, true} ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure));
+ {true, false} ->
+ throw(?ALERT_REC(?FATAL, ?NO_RENEGOTIATION));
+ {false, false} ->
+ {ok, ConnectionStates}
+ end.
+cert_curve(_, _, no_suite) ->
+ {no_curve, no_suite};
+cert_curve(Cert, ECCCurve0, CipherSuite) ->
+ case ssl_cipher_format:suite_bin_to_map(CipherSuite) of
+ #{key_exchange := Kex} when Kex == ecdh_ecdsa;
+ Kex == ecdh_rsa ->
+ OtpCert = public_key:pkix_decode_cert(Cert, otp),
+ TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
+ #'OTPSubjectPublicKeyInfo'{algorithm = AlgInfo}
+ = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ {namedCurve, Oid} = AlgInfo#'PublicKeyAlgorithm'.parameters,
+ {{namedCurve, Oid}, CipherSuite};
+ _ ->
+ {ECCCurve0, CipherSuite}
+ end.
+
+empty_extensions() ->
+ #{}.
+
+empty_extensions({3,4}, client_hello) ->
+ #{
+ sni => undefined,
+ %% max_fragment_length => undefined,
+ %% status_request => undefined,
+ elliptic_curves => undefined,
+ signature_algs => undefined,
+ %% use_srtp => undefined,
+ %% heartbeat => undefined,
+ alpn => undefined,
+ %% signed_cert_timestamp => undefined,
+ %% client_cert_type => undefined,
+ %% server_cert_type => undefined,
+ %% padding => undefined,
+ key_share => undefined,
+ pre_shared_key => undefined,
+ psk_key_exchange_modes => undefined,
+ %% early_data => undefined,
+ %% cookie => undefined,
+ client_hello_versions => undefined,
+ %% cert_authorities => undefined,
+ %% post_handshake_auth => undefined,
+ signature_algs_cert => undefined
+ };
+empty_extensions({3, 3}, client_hello) ->
+ Ext = empty_extensions({3,2}, client_hello),
+ Ext#{signature_algs => undefined};
+empty_extensions(_, client_hello) ->
+ #{renegotiation_info => undefined,
+ alpn => undefined,
+ next_protocol_negotiation => undefined,
+ srp => undefined,
+ ec_point_formats => undefined,
+ elliptic_curves => undefined,
+ sni => undefined};
+empty_extensions({3,4}, server_hello) ->
+ #{server_hello_selected_version => undefined,
+ key_share => undefined,
+ pre_shared_key => undefined
+ };
+empty_extensions({3,4}, hello_retry_request) ->
+ #{server_hello_selected_version => undefined,
+ key_share => undefined,
+ pre_shared_key => undefined
+ };
+empty_extensions({3,0}, _) ->
+ empty_extensions();
+empty_extensions(_, server_hello) ->
+ #{renegotiation_info => undefined,
+ alpn => undefined,
+ next_protocol_negotiation => undefined,
+ ec_point_formats => undefined}.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 324b7dbde3..b248edcaa9 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -47,14 +47,15 @@
srp_username,
is_resumable,
time_stamp,
- ecc
+ 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).
--define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, 2).
--define(DEFAULT_DIFFIE_HELLMAN_PRIME,
- 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF).
+-define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, ssl_dh_groups:modp2048_generator()).
+-define(DEFAULT_DIFFIE_HELLMAN_PRIME, ssl_dh_groups:modp2048_prime()).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Handsake protocol - RFC 4346 section 7.4
@@ -105,7 +106,11 @@
srp,
ec_point_formats,
elliptic_curves,
- sni
+ sni,
+ client_hello_versions,
+ server_hello_selected_version,
+ signature_algs_cert,
+ key_share
}).
-record(server_hello, {
@@ -133,6 +138,7 @@
-define(KEY_EXCHANGE_DIFFIE_HELLMAN, 1).
-define(KEY_EXCHANGE_EC_DIFFIE_HELLMAN, 6).
-define(KEY_EXCHANGE_PSK, 2).
+-define(KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, 7).
-define(KEY_EXCHANGE_DHE_PSK, 3).
-define(KEY_EXCHANGE_RSA_PSK, 4).
-define(KEY_EXCHANGE_SRP, 5).
@@ -162,6 +168,11 @@
dh_params
}).
+-record(server_ecdhe_psk_params, {
+ hint,
+ dh_params
+ }).
+
-record(server_srp_params, {
srp_n, %% opaque srp_N<1..2^16-1>
srp_g, %% opaque srp_g<1..2^16-1>
@@ -254,6 +265,11 @@
dh_public
}).
+-record(client_ecdhe_psk_identity, {
+ identity,
+ dh_public
+ }).
+
-record(client_rsa_psk_identity, {
identity,
exchange_keys
@@ -302,12 +318,12 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(SIGNATURE_ALGORITHMS_EXT, 13).
--record(hash_sign_algos, {
- hash_sign_algos
- }).
+-record(hash_sign_algos, {hash_sign_algos}).
+%% RFC 8446 (TLS 1.3)
+-record(signature_algorithms, {signature_scheme_list}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Application-Layer Protocol Negotiation RFC 7301
+%% RFC 7301 Application-Layer Protocol Negotiation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(ALPN_EXT, 16).
@@ -327,9 +343,8 @@
-record(next_protocol, {selected_protocol}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% ECC Extensions RFC 4492 section 4 and 5
+%% ECC Extensions RFC 8422 section 4 and 5
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-define(ELLIPTIC_CURVES_EXT, 10).
-define(EC_POINT_FORMATS_EXT, 11).
@@ -337,11 +352,18 @@
elliptic_curve_list
}).
+%% RFC 8446 (TLS 1.3) renamed the "elliptic_curve" extension.
+-record(supported_groups, {
+ supported_groups
+ }).
+
-record(ec_point_formats, {
ec_point_format_list
}).
-define(ECPOINT_UNCOMPRESSED, 0).
+%% Defined in RFC 4492, deprecated by RFC 8422
+%% RFC 8422 compliant implementations MUST not support the two formats below
-define(ECPOINT_ANSIX962_COMPRESSED_PRIME, 1).
-define(ECPOINT_ANSIX962_COMPRESSED_CHAR2, 2).
@@ -354,10 +376,11 @@
-define(NAMED_CURVE, 3).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Server name indication RFC 6066 section 3
+%% RFC 6066 Server name indication
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--define(SNI_EXT, 16#0000).
+%% section 3
+-define(SNI_EXT, 0).
%% enum { host_name(0), (255) } NameType;
-define(SNI_NAMETYPE_HOST_NAME, 0).
@@ -366,4 +389,56 @@
hostname = undefined
}).
+%% 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)
+%% and Datagram Transport Layer Security (DTLS)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(CLIENT_CERTIFICATE_TYPE, 19).
+-define(SERVER_CERTIFICATE_TYPE, 20).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 6520 Transport Layer Security (TLS) and
+%% Datagram Transport Layer Security (DTLS) Heartbeat Extension
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(HS_HEARTBEAT, 15).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 6962 Certificate Transparency
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(SIGNED_CERTIFICATE_TIMESTAMP, 18).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 7685 A Transport Layer Security (TLS) ClientHello Padding Extension
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(PADDING, 21).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Supported Versions RFC 8446 (TLS 1.3) section 4.2.1 also affects TLS-1.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SUPPORTED_VERSIONS_EXT, 43).
+
+-record(client_hello_versions, {versions}).
+-record(server_hello_selected_version, {selected_version}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Signature Algorithms RFC 8446 (TLS 1.3) section 4.2.3 also affects TLS-1.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SIGNATURE_ALGORITHMS_CERT_EXT, 50).
+
+-record(signature_algorithms_cert, {signature_scheme_list}).
+
-endif. % -ifdef(ssl_handshake).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index ac212a56d8..06c3ccae45 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% 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.
@@ -25,16 +25,15 @@
-include_lib("public_key/include/public_key.hrl").
+-define(VSN, "8.2.6").
-define(SECRET_PRINTOUT, "***").
--type reason() :: term().
--type reply() :: term().
--type msg() :: term().
--type from() :: term().
--type host() :: inet:ip_address() | inet:hostname().
--type session_id() :: 0 | binary().
+-type reason() :: any().
+-type reply() :: any().
+-type msg() :: any().
+-type from() :: any().
-type certdb_ref() :: reference().
--type db_handle() :: term().
+-type db_handle() :: any().
-type der_cert() :: binary().
-type issuer() :: tuple().
-type serialnumber() :: integer().
@@ -60,6 +59,7 @@
-define(CDR_MAGIC, "GIOP").
-define(CDR_HDR_SIZE, 12).
+-define(INTERNAL_ACTIVE_N, 100).
-define(DEFAULT_TIMEOUT, 5000).
-define(NO_DIST_POINT, "http://dummy/no_distribution_point").
@@ -71,35 +71,64 @@
-define(FALSE, 1).
%% sslv3 is considered insecure due to lack of padding check (Poodle attack)
-%% Keep as interop with legacy software but do not support as default
--define(ALL_AVAILABLE_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]).
--define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1]).
--define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1]).
--define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]).
+%% 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_DATAGRAM_VERSIONS, ['dtlsv1.2', dtlsv1]).
+%% Defines the default versions when not specified by an ssl option.
+-define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2']).
+-define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1']).
+
+%% Versions allowed in TLSCiphertext.version (TLS 1.2 and prior) and
+%% TLSCiphertext.legacy_record_version (TLS 1.3).
+%% 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_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2']).
-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, [dtlsv1]).
+%% 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
+-define(RANDOM_OVERRIDE_TLS12, <<16#44,16#4F,16#57,16#4E,16#47,16#52,16#44,16#01>>).
+-define(RANDOM_OVERRIDE_TLS11, <<16#44,16#4F,16#57,16#4E,16#47,16#52,16#44,16#00>>).
+
-define('24H_in_msec', 86400000).
-define('24H_in_sec', 86400).
-record(ssl_options, {
- protocol :: tls | dtls,
- versions :: [ssl_record:ssl_version()], %% ssl_record:atom_version() in API
- verify :: verify_none | verify_peer,
+ protocol :: tls | dtls | 'undefined',
+ versions :: [ssl_record:ssl_version()] | 'undefined', %% ssl_record:atom_version() in API
+ verify :: verify_none | verify_peer | 'undefined',
verify_fun, %%:: fun(CertVerifyErrors::term()) -> boolean(),
- partial_chain :: fun(),
- fail_if_no_peer_cert :: boolean(),
- verify_client_once :: boolean(),
+ partial_chain :: fun() | 'undefined',
+ fail_if_no_peer_cert :: boolean() | 'undefined',
+ verify_client_once :: boolean() | 'undefined',
%% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError}
validate_extensions_fun,
- depth :: integer(),
- certfile :: binary(),
+ depth :: integer() | 'undefined',
+ certfile :: binary() | 'undefined',
cert :: public_key:der_encoded() | secret_printout() | 'undefined',
- keyfile :: binary(),
- key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()} | secret_printout() | 'undefined',
+ keyfile :: binary() | 'undefined',
+ key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo' | 'undefined',
+ public_key:der_encoded()} | map() %%map() -> ssl:key() how to handle dialyzer?
+ | secret_printout() | 'undefined',
password :: string() | secret_printout() | 'undefined',
cacerts :: [public_key:der_encoded()] | secret_printout() | 'undefined',
- cacertfile :: binary(),
- dh :: public_key:der_encoded() | secret_printout(),
+ cacertfile :: binary() | 'undefined',
+ dh :: public_key:der_encoded() | secret_printout() | 'undefined',
dhfile :: binary() | secret_printout() | 'undefined',
user_lookup_fun, % server option, fun to lookup the user
psk_identity :: binary() | secret_printout() | 'undefined',
@@ -108,26 +137,26 @@
%% Local policy for the server if it want's to reuse the session
%% or not. Defaluts to allways returning true.
%% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean()
- reuse_session,
+ reuse_session :: fun() | binary() | undefined, %% Server side is a fun()
%% If false sessions will never be reused, if true they
%% will be reused if possible.
- reuse_sessions :: boolean(),
+ reuse_sessions :: boolean() | save | 'undefined', %% Only client side can use value save
renegotiate_at,
secure_renegotiate,
client_renegotiation,
%% undefined if not hibernating, or number of ms of
%% inactivity after which ssl_connection will go into
%% hibernation
- hibernate_after :: timeout(),
+ hibernate_after :: timeout() | 'undefined',
%% This option should only be set to true by inet_tls_dist
erl_dist = false :: boolean(),
- alpn_advertised_protocols = undefined :: [binary()] | undefined ,
+ alpn_advertised_protocols = undefined :: [binary()] | undefined,
alpn_preferred_protocols = undefined :: [binary()] | undefined,
next_protocols_advertised = undefined :: [binary()] | undefined,
next_protocol_selector = undefined, %% fun([binary()]) -> binary())
- log_alert :: boolean(),
+ log_level = notice :: atom(),
server_name_indication = undefined,
- sni_hosts :: [{inet:hostname(), [tuple()]}],
+ sni_hosts :: [{inet:hostname(), [tuple()]}] | 'undefined',
sni_fun :: function() | undefined,
%% Should the server prefer its own cipher order over the one provided by
%% the client?
@@ -137,14 +166,19 @@
%%mitigation entirely?
beast_mitigation = one_n_minus_one :: one_n_minus_one | zero_n | disabled,
fallback = false :: boolean(),
- crl_check :: boolean() | peer | best_effort,
+ crl_check :: boolean() | peer | best_effort | 'undefined',
crl_cache,
signature_algs,
+ signature_algs_cert,
eccs,
- honor_ecc_order :: boolean(),
- v2_hello_compatible :: boolean(),
- max_handshake_size :: integer()
- }).
+ supported_groups, %% RFC 8422, RFC 8446
+ honor_ecc_order :: boolean() | 'undefined',
+ max_handshake_size :: integer() | 'undefined',
+ handshake,
+ customize_hostname_check
+ %% ,
+ %% save_session :: boolean()
+ }).
-record(socket_options,
{
@@ -158,17 +192,18 @@
-record(config, {ssl, %% SSL parameters
inet_user, %% User set inet options
emulated, %% Emulated option list or "inherit_tracker" pid
- udp_handler,
+ dtls_handler,
inet_ssl, %% inet options for internal ssl socket
transport_info, %% Callback info
connection_cb
}).
-
-type state_name() :: hello | abbreviated | certify | cipher | connection.
--type gen_fsm_state_return() :: {next_state, state_name(), term()} |
- {next_state, state_name(), term(), timeout()} |
- {stop, term(), term()}.
+-type gen_fsm_state_return() :: {next_state, state_name(), any()} |
+ {next_state, state_name(), any(), timeout()} |
+ {stop, any(), any()}.
+-type ssl_options() :: #ssl_options{}.
+
-endif. % -ifdef(ssl_internal).
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
new file mode 100644
index 0000000000..514a4464bc
--- /dev/null
+++ b/lib/ssl/src/ssl_logger.erl
@@ -0,0 +1,451 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2019. 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(ssl_logger).
+
+-export([debug/4,
+ format/2,
+ notice/2]).
+
+-define(DEC2HEX(X),
+ if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0;
+ ((X) >= 10) andalso ((X) =< 15) -> (X) + $a - 10
+ end).
+
+-define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))).
+
+-include("tls_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("tls_handshake.hrl").
+-include("dtls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+%%-------------------------------------------------------------------------
+%% External API
+%%-------------------------------------------------------------------------
+
+%% SSL log formatter
+format(#{level:= _Level, msg:= {report, Msg}, meta:= _Meta}, _Config0) ->
+ #{direction := Direction,
+ protocol := Protocol,
+ message := Content} = Msg,
+ case Protocol of
+ 'record' ->
+ BinMsg =
+ case Content of
+ #ssl_tls{} ->
+ [tls_record:build_tls_record(Content)];
+ _ when is_list(Content) ->
+ lists:flatten(Content)
+ end,
+ format_tls_record(Direction, BinMsg);
+ 'handshake' ->
+ format_handshake(Direction, Content);
+ _Other ->
+ []
+ end.
+
+%% Stateful logging
+debug(Level, Direction, Protocol, Message)
+ when (Direction =:= inbound orelse Direction =:= outbound) andalso
+ (Protocol =:= 'record' orelse Protocol =:= 'handshake') ->
+ case logger:compare_levels(Level, debug) of
+ lt ->
+ ?LOG_DEBUG(#{direction => Direction,
+ protocol => Protocol,
+ message => Message},
+ #{domain => [otp,ssl,Protocol]});
+ eq ->
+ ?LOG_DEBUG(#{direction => Direction,
+ protocol => Protocol,
+ message => Message},
+ #{domain => [otp,ssl,Protocol]});
+ _ ->
+ ok
+ end.
+
+%% Stateful logging
+notice(Level, Report) ->
+ case logger:compare_levels(Level, notice) of
+ lt ->
+ ?LOG_NOTICE(Report);
+ eq ->
+ ?LOG_NOTICE(Report);
+ _ ->
+ ok
+ end.
+
+
+%%-------------------------------------------------------------------------
+%% Handshake Protocol
+%%-------------------------------------------------------------------------
+format_handshake(Direction, BinMsg) ->
+ {Header, Message} = parse_handshake(Direction, BinMsg),
+ io_lib:format("~s~n~s~n", [Header, Message]).
+
+
+parse_handshake(Direction, #client_hello{
+ client_version = Version0,
+ cipher_suites = CipherSuites0,
+ extensions = Extensions
+ } = ClientHello) ->
+ Version = get_client_version(Version0, Extensions),
+ Header = io_lib:format("~s ~s Handshake, ClientHello",
+ [header_prefix(Direction),
+ version(Version)]),
+ CipherSuites = parse_cipher_suites(CipherSuites0),
+ Message = io_lib:format("~p",
+ [?rec_info(client_hello,
+ ClientHello#client_hello{cipher_suites = CipherSuites})]),
+ {Header, Message};
+parse_handshake(Direction, #server_hello{
+ server_version = Version0,
+ cipher_suite = CipherSuite0,
+ extensions = Extensions
+ } = ServerHello) ->
+ Version = get_server_version(Version0, Extensions),
+ Header = io_lib:format("~s ~s Handshake, ServerHello",
+ [header_prefix(Direction),
+ version(Version)]),
+ CipherSuite = format_cipher(CipherSuite0),
+ Message = io_lib:format("~p",
+ [?rec_info(server_hello,
+ ServerHello#server_hello{cipher_suite = CipherSuite})]),
+ {Header, Message};
+parse_handshake(Direction, #hello_verify_request{} = HelloVerifyRequest) ->
+ Header = io_lib:format("~s Handshake, HelloVerifyRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(hello_verify_request, HelloVerifyRequest)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate{} = Certificate) ->
+ Header = io_lib:format("~s Handshake, Certificate",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate, Certificate)]),
+ {Header, Message};
+parse_handshake(Direction, #server_key_exchange{} = ServerKeyExchange) ->
+ Header = io_lib:format("~s Handshake, ServerKeyExchange",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(server_key_exchange, ServerKeyExchange)]),
+ {Header, Message};
+parse_handshake(Direction, #server_key_params{} = ServerKeyExchange) ->
+ Header = io_lib:format("~s Handshake, ServerKeyExchange",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(server_key_params, ServerKeyExchange)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_request{} = CertificateRequest) ->
+ Header = io_lib:format("~s Handshake, CertificateRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_request, CertificateRequest)]),
+ {Header, Message};
+parse_handshake(Direction, #server_hello_done{} = ServerHelloDone) ->
+ Header = io_lib:format("~s Handshake, ServerHelloDone",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(server_hello_done, ServerHelloDone)]),
+ {Header, Message};
+parse_handshake(Direction, #client_key_exchange{} = ClientKeyExchange) ->
+ Header = io_lib:format("~s Handshake, ClientKeyExchange",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(client_key_exchange, ClientKeyExchange)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_verify{} = CertificateVerify) ->
+ Header = io_lib:format("~s Handshake, CertificateVerify",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_verify, CertificateVerify)]),
+ {Header, Message};
+parse_handshake(Direction, #finished{} = Finished) ->
+ Header = io_lib:format("~s Handshake, Finished",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(finished, Finished)]),
+ {Header, Message};
+parse_handshake(Direction, #hello_request{} = HelloRequest) ->
+ Header = io_lib:format("~s Handshake, HelloRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(hello_request, HelloRequest)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_request_1_3{} = CertificateRequest) ->
+ Header = io_lib:format("~s Handshake, CertificateRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_request_1_3, CertificateRequest)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_1_3{} = Certificate) ->
+ Header = io_lib:format("~s Handshake, Certificate",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_1_3, Certificate)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_verify_1_3{} = CertificateVerify) ->
+ Header = io_lib:format("~s Handshake, CertificateVerify",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_verify_1_3, CertificateVerify)]),
+ {Header, Message};
+parse_handshake(Direction, #encrypted_extensions{} = EncryptedExtensions) ->
+ Header = io_lib:format("~s Handshake, EncryptedExtensions",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(encrypted_extensions, EncryptedExtensions)]),
+ {Header, Message};
+parse_handshake(Direction, #new_session_ticket{} = NewSessionTicket) ->
+ Header = io_lib:format("~s Post-Handshake, NewSessionTicket",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(new_session_ticket, NewSessionTicket)]),
+ {Header, Message}.
+
+
+parse_cipher_suites([_|_] = Ciphers) ->
+ [format_cipher(C) || C <- Ciphers].
+
+format_cipher(C0) ->
+ try ssl_cipher_format:suite_bin_to_map(C0) of
+ Map ->
+ ssl_cipher_format:suite_map_to_str(Map)
+ catch
+ error:function_clause ->
+ format_uknown_cipher_suite(C0)
+ end.
+
+get_client_version(Version, Extensions) ->
+ CHVersions = maps:get(client_hello_versions, Extensions, undefined),
+ case CHVersions of
+ #client_hello_versions{versions = [Highest|_]} ->
+ Highest;
+ undefined ->
+ Version
+ end.
+
+get_server_version(Version, Extensions) ->
+ SHVersion = maps:get(server_hello_selected_version, Extensions, undefined),
+ case SHVersion of
+ #server_hello_selected_version{selected_version = SelectedVersion} ->
+ SelectedVersion;
+ undefined ->
+ Version
+ end.
+
+version({3,4}) ->
+ "TLS 1.3";
+version({3,3}) ->
+ "TLS 1.2";
+version({3,2}) ->
+ "TLS 1.1";
+version({3,1}) ->
+ "TLS 1.0";
+version({3,0}) ->
+ "SSL 3.0";
+version({254,253}) ->
+ "DTLS 1.2";
+version({254,255}) ->
+ "DTLS 1.0";
+version({M,N}) ->
+ io_lib:format("TLS/DTLS [0x0~B0~B]", [M,N]).
+
+header_prefix(inbound) ->
+ "<<<";
+header_prefix(outbound) ->
+ ">>>".
+
+
+%%-------------------------------------------------------------------------
+%% TLS Record Protocol
+%%-------------------------------------------------------------------------
+format_tls_record(Direction, BinMsg) ->
+ {Message, Size} = convert_to_hex('tls_record', BinMsg),
+ Header = io_lib:format("~s (~B bytes) ~s~n",
+ [header_prefix_tls_record(Direction),
+ Size,
+ tls_record_version(BinMsg)]),
+ Header ++ Message.
+
+
+header_prefix_tls_record(inbound) ->
+ "reading";
+header_prefix_tls_record(outbound) ->
+ "writing".
+
+
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(3),_/binary>>|_]) ->
+ io_lib:format("TLS 1.2 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(2),_/binary>>|_]) ->
+ io_lib:format("TLS 1.1 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(1),_/binary>>|_]) ->
+ io_lib:format("TLS 1.0 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(0),_/binary>>|_]) ->
+ io_lib:format("SSL 3.0 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(254),?BYTE(253),_/binary>>|_]) ->
+ io_lib:format("DTLS 1.2 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(254),?BYTE(255),_/binary>>|_]) ->
+ io_lib:format("DTLS 1.0 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(M),?BYTE(N),_/binary>>|_]) ->
+ io_lib:format("TLS/DTLS [0x0~B0~B] Record Protocol, ~s", [M, N, msg_type(B)]).
+
+
+msg_type(20) -> "change_cipher_spec";
+msg_type(21) -> "alert";
+msg_type(22) -> "handshake";
+msg_type(23) -> "application_data";
+msg_type(_) -> unknown.
+
+
+%%-------------------------------------------------------------------------
+%% Hex encoding functions
+%%-------------------------------------------------------------------------
+convert_to_hex(Protocol, BinMsg) ->
+ convert_to_hex(Protocol, BinMsg, [], [], 0).
+%%
+convert_to_hex(P, [], Row0, Acc, C) when C rem 16 =:= 0 ->
+ Row = lists:reverse(end_row(P, Row0)),
+ {lists:reverse(Acc) ++ Row ++ io_lib:nl(), C};
+convert_to_hex(P, [], Row0, Acc, C) ->
+ Row = lists:reverse(end_row(P, Row0)),
+ Padding = calculate_padding(Row0, Acc),
+ PaddedRow = string:pad(Row, Padding, leading, $ ),
+ {lists:reverse(Acc) ++ PaddedRow ++ io_lib:nl(), C};
+convert_to_hex(P, [H|T], Row, Acc, C) when is_list(H) ->
+ convert_to_hex(P, H ++ T, Row, Acc, C);
+convert_to_hex(P, [<<>>|T], Row, Acc, C) ->
+ convert_to_hex(P, T, Row, Acc, C);
+
+%% First line
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C =:= 0 ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, Row),
+ prepend_first_row(P, A, B, Acc, C),
+ C + 1);
+%% New line
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C rem 16 =:= 0 ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, []),
+ prepend_row(P, A, B, Row, Acc, C),
+ C + 1);
+%% Add 8th hex with extra whitespace
+%% 0000 - 16 03 02 00 bd 01 00 00 b9 ...
+%% ^^^^
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C rem 8 =:= 7 ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, Row),
+ prepend_eighths_hex(A, B, Acc),
+ C + 1);
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, Row),
+ prepend_hex(A, B, Acc),
+ C + 1);
+%% First line
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C =:= 0 ->
+ convert_to_hex(P, T,
+ update_row(H, Row),
+ prepend_first_row(P, H, Acc, C),
+ C + 1);
+%% New line
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C rem 16 =:= 0 ->
+ convert_to_hex(P, T,
+ update_row(H, []),
+ prepend_row(P, H, Row, Acc, C),
+ C + 1);
+%% Add 8th hex with extra whitespace
+%% 0000 - 16 03 02 00 bd 01 00 00 b9 ...
+%% ^^^^
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C rem 8 =:= 7 ->
+ convert_to_hex(P, T,
+ update_row(H, Row),
+ prepend_eighths_hex(H, Acc),
+ C + 1);
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H) ->
+ convert_to_hex(P, T,
+ update_row(H, Row),
+ prepend_hex(H, Acc),
+ C + 1).
+
+
+row_prefix(_ , N) ->
+ S = string:pad(string:to_lower(erlang:integer_to_list(N, 16)),4,leading,$0),
+ lists:reverse(lists:flatten(S ++ " - ")).
+
+
+end_row(_, Row) ->
+ Row ++ " ".
+
+
+%% Calculate padding of the "printable character" lines in order to be
+%% visually aligned.
+calculate_padding(Row, Acc) ->
+ %% Number of new line characters
+ NNL = (length(Acc) div 75) * length(io_lib:nl()),
+ %% Length of the last printed line
+ Length = (length(Acc) - NNL) rem 75,
+ %% Adjusted length of the last printed line
+ PaddedLength = 75 - (16 - length(Row)), %% Length
+ %% Padding
+ PaddedLength - Length.
+
+
+%%-------------------------------------------------------------------------
+%% Functions operating on reversed lists
+%%-------------------------------------------------------------------------
+update_row(B, Row) when is_binary(B) ->
+ case binary_to_list(B) of
+ [C] when 32 =< C, C =< 126 ->
+ [C|Row];
+ _Else ->
+ [$.|Row]
+ end;
+update_row(C, Row) when 32 =< C, C =< 126 ->
+ [C|Row];
+update_row(_, Row) ->
+ [$.|Row].
+
+
+prepend_first_row(P, A, B, Acc, C) ->
+ prepend_hex(A, B,row_prefix(P, C) ++ Acc).
+%%
+prepend_first_row(P, N, Acc, C) ->
+ prepend_hex(N,row_prefix(P, C) ++ Acc).
+
+prepend_row(P, A, B, Row, Acc, C) ->
+ prepend_hex(A, B,row_prefix(P, C) ++ io_lib:nl() ++ end_row(P, Row) ++ Acc).
+%%
+prepend_row(P, N, Row, Acc, C) ->
+ prepend_hex(N,row_prefix(P, C) ++ io_lib:nl() ++ end_row(P, Row) ++ Acc).
+
+
+
+prepend_hex(A, B, Acc) ->
+ [$ ,?DEC2HEX(B),?DEC2HEX(A)|Acc].
+%%
+prepend_hex(N, Acc) ->
+ " " ++ number_to_hex(N) ++ Acc.
+
+
+prepend_eighths_hex(A, B, Acc) ->
+ [$ ,$ ,?DEC2HEX(B),?DEC2HEX(A)|Acc].
+%%
+prepend_eighths_hex(N, Acc) ->
+ " " ++ number_to_hex(N) ++ Acc.
+
+number_to_hex(N) ->
+ case string:to_lower(erlang:integer_to_list(N, 16)) of
+ H when length(H) < 2 ->
+ lists:append(H, "0");
+ H ->
+ lists:reverse(H)
+ end.
+
+format_uknown_cipher_suite(<<?BYTE(X), ?BYTE(Y)>>) ->
+ "0x" ++ number_to_hex(X) ++ "0x" ++ number_to_hex(Y).
+
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index ca9aaf4660..456a560bf6 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% 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.
@@ -30,7 +30,7 @@
connection_init/3, cache_pem_file/2,
lookup_trusted_cert/4,
new_session_id/1, clean_cert_db/2,
- register_session/2, register_session/3, invalidate_session/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]).
@@ -42,6 +42,8 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
+
-include_lib("kernel/include/file.hrl").
-record(state, {
@@ -78,7 +80,7 @@
name(normal) ->
?MODULE;
name(dist) ->
- list_to_atom(atom_to_list(?MODULE) ++ "dist").
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
%%--------------------------------------------------------------------
-spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}.
@@ -127,7 +129,13 @@ cache_pem_file(File, DbHandle) ->
[Content] ->
{ok, Content};
undefined ->
- ssl_pem_cache:insert(File)
+ case ssl_pkix_db:decode_pem_file(File) of
+ {ok, Content} ->
+ ssl_pem_cache:insert(File, Content),
+ {ok, Content};
+ Error ->
+ Error
+ end
end.
%%--------------------------------------------------------------------
@@ -142,7 +150,7 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) ->
ssl_pkix_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer).
%%--------------------------------------------------------------------
--spec new_session_id(integer()) -> session_id().
+-spec new_session_id(integer()) -> ssl:session_id().
%%
%% Description: Creates a session id for the server.
%%--------------------------------------------------------------------
@@ -164,9 +172,11 @@ clean_cert_db(Ref, File) ->
%%
%% Description: Make the session available for reuse.
%%--------------------------------------------------------------------
--spec register_session(host(), inet:port_number(), #session{}) -> ok.
-register_session(Host, Port, Session) ->
- cast({register_session, Host, Port, Session}).
+-spec register_session(ssl:host(), inet:port_number(), #session{}, unique | true) -> ok.
+register_session(Host, Port, Session, true) ->
+ call({register_session, Host, Port, Session});
+register_session(Host, Port, Session, unique = Save) ->
+ cast({register_session, Host, Port, Session, Save}).
-spec register_session(inet:port_number(), #session{}) -> ok.
register_session(Port, Session) ->
@@ -177,7 +187,7 @@ register_session(Port, Session) ->
%% a the session has been marked "is_resumable = false" for some while
%% it will be safe to remove the data from the session database.
%%--------------------------------------------------------------------
--spec invalidate_session(host(), inet:port_number(), #session{}) -> ok.
+-spec invalidate_session(ssl:host(), inet:port_number(), #session{}) -> ok.
invalidate_session(Host, Port, Session) ->
load_mitigation(),
cast({invalidate_session, Host, Port, Session}).
@@ -295,7 +305,10 @@ 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}.
+ {reply, Id, State};
+handle_call({{register_session, Host, Port, Session},_}, _, State0) ->
+ State = client_register_session(Host, Port, Session, State0),
+ {reply, ok, State}.
%%--------------------------------------------------------------------
-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}.
@@ -305,8 +318,12 @@ handle_call({{new_session_id, Port}, _},
%%
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast({register_session, Host, Port, Session}, State0) ->
- State = ssl_client_register_session(Host, Port, Session, State0),
+handle_cast({register_session, Host, Port, Session, unique}, State0) ->
+ State = client_register_unique_session(Host, Port, Session, State0),
+ {noreply, State};
+
+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) ->
@@ -499,10 +516,10 @@ last_delay_timer({{_,_},_}, TRef, {LastServer, _}) ->
last_delay_timer({_,_}, TRef, {_, LastClient}) ->
{TRef, LastClient}.
-%% If we can not generate a not allready in use session ID in
+%% 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 can not find a session id in
+%% 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"
@@ -513,7 +530,7 @@ new_id(Port, Tries, Cache, CacheCb) ->
case CacheCb:lookup(Cache, {Port, Id}) of
undefined ->
Now = erlang:monotonic_time(),
- %% New sessions can not be set to resumable
+ %% 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,
@@ -534,10 +551,10 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) ->
ok
end.
-ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
- session_cache_cb = CacheCb,
- session_cache_client_max = Max,
- session_client_invalidator = Pid0} = State) ->
+client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = State) ->
TimeStamp = erlang:monotonic_time(),
NewSession = Session#session{time_stamp = TimeStamp},
@@ -551,6 +568,17 @@ ssl_client_register_session(Host, Port, Session, #state{session_cache_client = C
register_unique_session(Sessions, NewSession, {Host, Port}, State)
end.
+client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = 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,
@@ -563,7 +591,7 @@ server_register_session(Port, Session, #state{session_cache_server_max = Max,
do_register_session(Key, Session, Max, Pid, Cache, CacheCb) ->
try CacheCb:size(Cache) of
- Max ->
+ Size when Size >= Max ->
invalidate_session_cache(Pid, CacheCb, Cache);
_ ->
CacheCb:update(Cache, Key, Session),
diff --git a/lib/ssl/src/ssl_pem_cache.erl b/lib/ssl/src/ssl_pem_cache.erl
index 6cc0729208..41bca2f7b5 100644
--- a/lib/ssl/src/ssl_pem_cache.erl
+++ b/lib/ssl/src/ssl_pem_cache.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 20016-2017. All Rights Reserved.
+%% Copyright Ericsson AB 20016-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.
@@ -29,7 +29,7 @@
-export([start_link/1,
start_link_dist/1,
name/1,
- insert/1,
+ insert/2,
clear/0]).
% Spawn export
@@ -45,7 +45,7 @@
-record(state, {
pem_cache,
- last_pem_check :: erlang:timestamp(),
+ last_pem_check :: integer(),
clear :: integer()
}).
@@ -65,7 +65,7 @@
name(normal) ->
?MODULE;
name(dist) ->
- list_to_atom(atom_to_list(?MODULE) ++ "dist").
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
%%--------------------------------------------------------------------
-spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}.
@@ -90,19 +90,17 @@ start_link_dist(_) ->
%%--------------------------------------------------------------------
--spec insert(binary()) -> {ok, term()} | {error, reason()}.
+-spec insert(binary(), term()) -> ok | {error, reason()}.
%%
%% Description: Cache a pem file and return its content.
%%--------------------------------------------------------------------
-insert(File) ->
- {ok, PemBin} = file:read_file(File),
- Content = public_key:pem_decode(PemBin),
+insert(File, Content) ->
case bypass_cache() of
true ->
- {ok, Content};
+ ok;
false ->
cast({cache_pem, File, Content}),
- {ok, Content}
+ ok
end.
%%--------------------------------------------------------------------
@@ -136,8 +134,9 @@ init([Name]) ->
PemCache = ssl_pkix_db:create_pem_cache(Name),
Interval = pem_check_interval(),
erlang:send_after(Interval, self(), clear_pem_cache),
+ erlang:system_time(second),
{ok, #state{pem_cache = PemCache,
- last_pem_check = os:timestamp(),
+ last_pem_check = erlang:convert_time_unit(os:system_time(), native, second),
clear = Interval
}}.
@@ -185,7 +184,7 @@ handle_cast({invalidate_pem, File}, #state{pem_cache = Db} = State) ->
handle_info(clear_pem_cache, #state{pem_cache = PemCache,
clear = Interval,
last_pem_check = CheckPoint} = State) ->
- NewCheckPoint = os:timestamp(),
+ NewCheckPoint = erlang:convert_time_unit(os:system_time(), native, second),
start_pem_cache_validator(PemCache, CheckPoint),
erlang:send_after(Interval, self(), clear_pem_cache),
{noreply, State#state{last_pem_check = NewCheckPoint}};
@@ -231,24 +230,14 @@ init_pem_cache_validator([CacheName, PemCache, CheckPoint]) ->
CheckPoint, PemCache).
pem_cache_validate({File, _}, CheckPoint) ->
- case file:read_file_info(File, []) of
- {ok, #file_info{mtime = Time}} ->
- case is_before_checkpoint(Time, CheckPoint) of
- true ->
- ok;
- false ->
- invalidate_pem(File)
- end;
+ case file:read_file_info(File, [{time, posix}]) of
+ {ok, #file_info{mtime = Time}} when Time < CheckPoint ->
+ ok;
_ ->
invalidate_pem(File)
end,
CheckPoint.
-is_before_checkpoint(Time, CheckPoint) ->
- calendar:datetime_to_gregorian_seconds(
- calendar:now_to_datetime(CheckPoint)) -
- calendar:datetime_to_gregorian_seconds(Time) > 0.
-
pem_check_interval() ->
case application:get_env(ssl, ssl_pem_cache_clean) of
{ok, Interval} when is_integer(Interval) ->
diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl
index b28636569d..dec48fa914 100644
--- a/lib/ssl/src/ssl_pkix_db.erl
+++ b/lib/ssl/src/ssl_pkix_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% 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.
@@ -27,6 +27,7 @@
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/file.hrl").
+-include_lib("kernel/include/logger.hrl").
-export([create/1, create_pem_cache/1,
add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3,
@@ -76,10 +77,17 @@ remove(Dbs) ->
true = ets:delete(Db1);
(undefined) ->
ok;
- (ssl_pem_cache) ->
- ok;
- (ssl_pem_cache_dist) ->
- ok;
+ (Name) when is_atom(Name) ->
+ NormalName = ssl_pem_cache:name(normal),
+ DistName = ssl_pem_cache:name(dist),
+ case Name of
+ NormalName ->
+ ok;
+ DistName ->
+ ok;
+ _ ->
+ true = ets:delete(Name)
+ end;
(Db) ->
true = ets:delete(Db)
end, Dbs).
@@ -150,7 +158,7 @@ extract_trusted_certs(File) ->
{error, {badmatch, Error}}
end.
--spec decode_pem_file(binary()) -> {ok, term()}.
+-spec decode_pem_file(binary()) -> {ok, term()} | {error, term()}.
decode_pem_file(File) ->
case file:read_file(File) of
{ok, PemBin} ->
@@ -304,16 +312,21 @@ decode_certs(Ref, Cert) ->
error:_ ->
Report = io_lib:format("SSL WARNING: Ignoring a CA cert as "
"it could not be correctly decoded.~n", []),
- error_logger:info_report(Report),
+ ?LOG_NOTICE(Report),
undefined
end.
new_trusted_cert_entry(File, [CertsDb, RefsDb, _ | _]) ->
- Ref = make_ref(),
- init_ref_db(Ref, File, RefsDb),
- {ok, Content} = ssl_pem_cache:insert(File),
- add_certs_from_pem(Content, Ref, CertsDb),
- {ok, Ref}.
+ case decode_pem_file(File) of
+ {ok, Content} ->
+ Ref = make_ref(),
+ init_ref_db(Ref, File, RefsDb),
+ ok = ssl_pem_cache:insert(File, Content),
+ add_certs_from_pem(Content, Ref, CertsDb),
+ {ok, Ref};
+ Error ->
+ Error
+ end.
add_crls([_,_,_, {_, Mapping} | _], ?NO_DIST_POINT, CRLs) ->
[add_crls(CRL, Mapping) || CRL <- CRLs];
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index b10069c3cb..867d2cfc5a 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. 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.
@@ -25,13 +25,14 @@
-module(ssl_record).
-include("ssl_record.hrl").
+-include("ssl_connection.hrl").
-include("ssl_internal.hrl").
-include("ssl_cipher.hrl").
-include("ssl_alert.hrl").
%% Connection state handling
-export([initial_security_params/1, current_connection_state/2, pending_connection_state/2,
- activate_pending_connection_state/2,
+ activate_pending_connection_state/3,
set_security_params/3,
set_mac_secret/4,
set_master_secret/2,
@@ -39,35 +40,34 @@
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]).
+ empty_connection_state/2, initial_connection_state/2, record_protocol_role/1,
+ step_encryption_state/1]).
%% Compression
-export([compress/3, uncompress/3, compressions/0]).
%% Payload encryption/decryption
--export([cipher/4, decipher/4, is_correct_mac/2,
- cipher_aead/4, decipher_aead/4]).
-
-%% Encoding
--export([encode_plain_text/4]).
+-export([cipher/4, cipher/5, decipher/4,
+ cipher_aead/4, cipher_aead/5, decipher_aead/5,
+ is_correct_mac/2, nonce_seed/3]).
-export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]).
-type ssl_version() :: {integer(), integer()}.
-type ssl_atom_version() :: tls_record:tls_atom_version().
--type connection_states() :: term(). %% Map
--type connection_state() :: term(). %% Map
+-type connection_states() :: map(). %% Map
+-type connection_state() :: map(). %% Map
+
%%====================================================================
-%% Internal application API
+%% Connection state handling
%%====================================================================
-
%%--------------------------------------------------------------------
-spec current_connection_state(connection_states(), read | write) ->
connection_state().
%%
%% Description: Returns the instance of the connection_state map
-%% that is currently defined as the current conection state.
+%% that is currently defined as the current connection state.
%%--------------------------------------------------------------------
current_connection_state(ConnectionStates, read) ->
maps:get(current_read, ConnectionStates);
@@ -79,7 +79,7 @@ current_connection_state(ConnectionStates, write) ->
connection_state().
%%
%% Description: Returns the instance of the connection_state map
-%% that is pendingly defined as the pending conection state.
+%% that is pendingly defined as the pending connection state.
%%--------------------------------------------------------------------
pending_connection_state(ConnectionStates, read) ->
maps:get(pending_read, ConnectionStates);
@@ -87,7 +87,7 @@ pending_connection_state(ConnectionStates, write) ->
maps:get(pending_write, ConnectionStates).
%%--------------------------------------------------------------------
--spec activate_pending_connection_state(connection_states(), read | write) ->
+-spec activate_pending_connection_state(connection_states(), read | write, tls_connection | dtls_connection) ->
connection_states().
%%
%% Description: Creates a new instance of the connection_states record
@@ -95,13 +95,13 @@ pending_connection_state(ConnectionStates, write) ->
%%--------------------------------------------------------------------
activate_pending_connection_state(#{current_read := Current,
pending_read := Pending} = States,
- read) ->
+ read, Connection) ->
#{secure_renegotiation := SecureRenegotation} = Current,
#{beast_mitigation := BeastMitigation,
security_parameters := SecParams} = Pending,
NewCurrent = Pending#{sequence_number => 0},
ConnectionEnd = SecParams#security_parameters.connection_end,
- EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation),
+ EmptyPending = Connection:empty_connection_state(ConnectionEnd, BeastMitigation),
NewPending = EmptyPending#{secure_renegotiation => SecureRenegotation},
States#{current_read => NewCurrent,
pending_read => NewPending
@@ -109,19 +109,35 @@ activate_pending_connection_state(#{current_read := Current,
activate_pending_connection_state(#{current_write := Current,
pending_write := Pending} = States,
- write) ->
+ write, Connection) ->
NewCurrent = Pending#{sequence_number => 0},
#{secure_renegotiation := SecureRenegotation} = Current,
#{beast_mitigation := BeastMitigation,
security_parameters := SecParams} = Pending,
ConnectionEnd = SecParams#security_parameters.connection_end,
- EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation),
+ EmptyPending = Connection:empty_connection_state(ConnectionEnd, BeastMitigation),
NewPending = EmptyPending#{secure_renegotiation => SecureRenegotation},
States#{current_write => NewCurrent,
pending_write => NewPending
}.
%%--------------------------------------------------------------------
+-spec step_encryption_state(#state{}) -> #state{}.
+%%
+%% Description: Activates the next encyrption state (e.g. handshake
+%% encryption).
+%%--------------------------------------------------------------------
+step_encryption_state(#state{connection_states =
+ #{pending_read := PendingRead,
+ pending_write := PendingWrite} = ConnStates} = State) ->
+ NewRead = PendingRead#{sequence_number => 0},
+ NewWrite = PendingWrite#{sequence_number => 0},
+ State#state{connection_states =
+ ConnStates#{current_read => NewRead,
+ current_write => NewWrite}}.
+
+
+%%--------------------------------------------------------------------
-spec set_security_params(#security_parameters{}, #security_parameters{},
connection_states()) -> connection_states().
%%
@@ -271,26 +287,9 @@ set_pending_cipher_state(#{pending_read := Read,
pending_read => Read#{cipher_state => ServerState},
pending_write => Write#{cipher_state => ClientState}}.
-encode_plain_text(Type, Version, Data, #{compression_state := CompS0,
- security_parameters :=
- #security_parameters{
- cipher_type = ?AEAD,
- compression_algorithm = CompAlg}
- } = WriteState0) ->
- {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
- WriteState1 = WriteState0#{compression_state => CompS1},
- AAD = ssl_cipher:calc_aad(Type, Version, WriteState1),
- ssl_record:cipher_aead(Version, Comp, WriteState1, AAD);
-encode_plain_text(Type, Version, Data, #{compression_state := CompS0,
- security_parameters :=
- #security_parameters{compression_algorithm = CompAlg}
- }= WriteState0) ->
- {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
- WriteState1 = WriteState0#{compression_state => CompS1},
- MacHash = ssl_cipher:calc_mac_hash(Type, Version, Comp, WriteState1),
- ssl_record:cipher(Version, Comp, WriteState1, MacHash);
-encode_plain_text(_,_,_,CS) ->
- exit({cs, CS}).
+%%====================================================================
+%% Compression
+%%====================================================================
uncompress(?NULL, Data, CS) ->
{Data, CS}.
@@ -299,12 +298,16 @@ compress(?NULL, Data, CS) ->
{Data, CS}.
%%--------------------------------------------------------------------
--spec compressions() -> [binary()].
+-spec compressions() -> [integer()].
%%
%% Description: return a list of compressions supported (currently none)
%%--------------------------------------------------------------------
compressions() ->
- [?byte(?NULL)].
+ [?NULL].
+
+%%====================================================================
+%% Payload encryption/decryption
+%%====================================================================
%%--------------------------------------------------------------------
-spec cipher(ssl_version(), iodata(), connection_state(), MacHash::binary()) ->
@@ -318,30 +321,51 @@ cipher(Version, Fragment,
#security_parameters{bulk_cipher_algorithm =
BulkCipherAlgo}
} = WriteState0, MacHash) ->
-
+ %%
{CipherFragment, CipherS1} =
ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version),
{CipherFragment, WriteState0#{cipher_state => CipherS1}}.
+
%%--------------------------------------------------------------------
--spec cipher_aead(ssl_version(), iodata(), connection_state(), MacHash::binary()) ->
- {CipherFragment::binary(), connection_state()}.
+-spec cipher(ssl_version(), iodata(), #cipher_state{}, MacHash::binary(), #security_parameters{}) ->
+ {CipherFragment::binary(), #cipher_state{}}.
%%
%% Description: Payload encryption
%%--------------------------------------------------------------------
-cipher_aead(Version, Fragment,
+cipher(Version, Fragment, CipherS0, MacHash,
+ #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo}) ->
+ %%
+ ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version).
+
+%%--------------------------------------------------------------------
+-spec cipher_aead(ssl_version(), iodata(), connection_state(), AAD::binary()) ->
+ {CipherFragment::binary(), connection_state()}.
+
+%% Description: Payload encryption
+%% %%--------------------------------------------------------------------
+cipher_aead(_Version, Fragment,
#{cipher_state := CipherS0,
- sequence_number := SeqNo,
security_parameters :=
#security_parameters{bulk_cipher_algorithm =
BulkCipherAlgo}
} = WriteState0, AAD) ->
-
{CipherFragment, CipherS1} =
- ssl_cipher:cipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, Fragment, Version),
+ do_cipher_aead(BulkCipherAlgo, Fragment, CipherS0, AAD),
{CipherFragment, WriteState0#{cipher_state => CipherS1}}.
%%--------------------------------------------------------------------
--spec decipher(ssl_version(), binary(), connection_state(), boolean()) -> {binary(), binary(), connection_state} | #alert{}.
+-spec cipher_aead(ssl_version(), iodata(), #cipher_state{}, AAD::binary(), #security_parameters{}) ->
+ {CipherFragment::binary(), #cipher_state{}}.
+
+%% Description: Payload encryption
+%% %%--------------------------------------------------------------------
+cipher_aead(_Version, Fragment, CipherS, AAD,
+ #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo}) ->
+ do_cipher_aead(BulkCipherAlgo, Fragment, CipherS, AAD).
+
+%%--------------------------------------------------------------------
+-spec decipher(ssl_version(), binary(), connection_state(), boolean()) ->
+ {binary(), binary(), connection_state()} | #alert{}.
%%
%% Description: Payload decryption
%%--------------------------------------------------------------------
@@ -360,28 +384,37 @@ decipher(Version, CipherFragment,
Alert
end.
%%--------------------------------------------------------------------
--spec decipher_aead(ssl_version(), binary(), connection_state(), binary()) ->
- {binary(), binary(), connection_state()} | #alert{}.
+-spec decipher_aead(ssl_cipher:cipher_enum(), #cipher_state{}, binary(), binary(), ssl_record:ssl_version()) ->
+ binary() | #alert{}.
%%
-%% Description: Payload decryption
-%%--------------------------------------------------------------------
-decipher_aead(Version, CipherFragment,
- #{sequence_number := SeqNo,
- security_parameters :=
- #security_parameters{bulk_cipher_algorithm =
- BulkCipherAlgo},
- cipher_state := CipherS0
- } = ReadState, AAD) ->
- case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, CipherFragment, Version) of
- {PlainFragment, CipherS1} ->
- CS1 = ReadState#{cipher_state => CipherS1},
- {PlainFragment, CS1};
- #alert{} = Alert ->
- Alert
+%% Description: Decrypts the data and checks the associated data (AAD) MAC using
+%% cipher described by cipher_enum() and updating the cipher state.
+%% Use for suites that use authenticated encryption with associated data (AEAD)
+%%-------------------------------------------------------------------
+decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment, _) ->
+ try
+ Nonce = decrypt_nonce(Type, CipherState, CipherFragment),
+ {AAD, CipherText, CipherTag} = aead_ciphertext_split(Type, CipherState, CipherFragment, AAD0),
+ case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of
+ Content when is_binary(Content) ->
+ Content;
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
+ end
+ catch
+ _:_ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
+
+nonce_seed(?CHACHA20_POLY1305, Seed, CipherState) ->
+ ssl_cipher:nonce_seed(Seed, CipherState);
+nonce_seed(_,_, CipherState) ->
+ CipherState.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+
empty_connection_state(ConnectionEnd, BeastMitigation) ->
SecParams = empty_security_params(ConnectionEnd),
#{security_parameters => SecParams,
@@ -406,11 +439,13 @@ random() ->
Random_28_bytes = ssl_cipher:random_bytes(28),
<<?UINT32(Secs_since_1970), Random_28_bytes/binary>>.
+-compile({inline, [is_correct_mac/2]}).
is_correct_mac(Mac, Mac) ->
true;
is_correct_mac(_M,_H) ->
false.
+-compile({inline, [record_protocol_role/1]}).
record_protocol_role(client) ->
?CLIENT;
record_protocol_role(server) ->
@@ -434,3 +469,45 @@ initial_security_params(ConnectionEnd) ->
compression_algorithm = ?NULL},
ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL, SecParams).
+-define(end_additional_data(AAD, Len), << (begin(AAD)end)/binary, ?UINT16(begin(Len)end) >>).
+
+do_cipher_aead(?CHACHA20_POLY1305 = Type, Fragment, #cipher_state{key=Key, tag_len = TagLen} = CipherState, AAD0) ->
+ AAD = ?end_additional_data(AAD0, erlang:iolist_size(Fragment)),
+ Nonce = chacha_nonce(CipherState),
+ {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD, TagLen),
+ {<<Content/binary, CipherTag/binary>>, CipherState};
+do_cipher_aead(Type, Fragment, #cipher_state{key=Key, tag_len = TagLen, nonce = ExplicitNonce} = CipherState, AAD0) ->
+ AAD = ?end_additional_data(AAD0, erlang:iolist_size(Fragment)),
+ Nonce = encrypt_nonce(Type, CipherState),
+ {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD, TagLen),
+ {<<ExplicitNonce:64/integer, Content/binary, CipherTag/binary>>, CipherState#cipher_state{nonce = ExplicitNonce + 1}}.
+
+
+chacha_nonce(#cipher_state{nonce = Nonce, iv = IV}) ->
+ crypto:exor(<<?UINT32(0), Nonce/binary>>, IV).
+
+encrypt_nonce(Type, #cipher_state{iv = IV, nonce = ExplicitNonce}) when Type == ?AES_GCM;
+ Type == ?AES_CCM;
+ Type == ?AES_CCM_8 ->
+ <<Salt:4/bytes, _/binary>> = IV,
+ <<Salt/binary, ExplicitNonce:64/integer>>.
+
+decrypt_nonce(?CHACHA20_POLY1305, CipherState, _) ->
+ chacha_nonce(CipherState);
+decrypt_nonce(Type, #cipher_state{iv = <<Salt:4/bytes, _/binary>>}, <<ExplicitNonce:8/bytes, _/binary>>) when
+ Type == ?AES_GCM;
+ Type == ?AES_CCM;
+ Type == ?AES_CCM_8 ->
+ <<Salt/binary, ExplicitNonce/binary>>.
+
+-compile({inline, [aead_ciphertext_split/4]}).
+aead_ciphertext_split(?CHACHA20_POLY1305, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) ->
+ CipherLen = byte_size(CipherTextFragment) - Len,
+ <<CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment,
+ {?end_additional_data(AAD, CipherLen), CipherText, CipherTag};
+aead_ciphertext_split(Type, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) when Type == ?AES_GCM;
+ Type == ?AES_CCM;
+ Type == ?AES_CCM_8 ->
+ CipherLen = byte_size(CipherTextFragment) - (Len + 8), %% 8 is length of explicit Nonce
+ << _:8/bytes, CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment,
+ {?end_additional_data(AAD, CipherLen), CipherText, CipherTag}.
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index ed007f58d7..6d4d47cedb 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. 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.
@@ -74,7 +74,7 @@
-define(INITIAL_BYTES, 5).
-define(MAX_SEQENCE_NUMBER, 18446744073709551615). %% (1 bsl 64) - 1 = 18446744073709551615
-%% Sequence numbers can not wrap so when max is about to be reached we should renegotiate.
+%% Sequence numbers cannot wrap so when max is about to be reached we should renegotiate.
%% We will renegotiate a little before so that there will be sequence numbers left
%% for the rehandshake and a little data. Currently we decided to renegotiate a little more
%% often as we can have a cheaper test to check if it is time to renegotiate. It will still
@@ -96,6 +96,11 @@
-define(AES_CBC, 7).
-define(AES_GCM, 8).
-define(CHACHA20_POLY1305, 9).
+%% Following two are not defined in any RFC but we want to have the
+%% same type of handling internaly, all of these "bulk_cipher_algorithm"
+%% enums are only used internaly anyway.
+-define(AES_CCM, 10).
+-define(AES_CCM_8, 11).
%% CipherType
-define(STREAM, 0).
@@ -140,6 +145,9 @@
-define(ALERT, 21).
-define(HANDSHAKE, 22).
-define(APPLICATION_DATA, 23).
+-define(HEARTBEAT, 24).
+-define(KNOWN_RECORD_TYPE(Type),
+ (is_integer(Type) andalso (20 =< (Type)) andalso ((Type) =< 23))).
-define(MAX_PLAIN_TEXT_LENGTH, 16384).
-define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)).
-define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)).
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index c9607489e9..44305c65fe 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -27,6 +27,7 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
%% Internal application API
-export([is_new/2, client_id/4, server_id/6, valid_session/2]).
@@ -34,7 +35,7 @@
-type seconds() :: integer().
%%--------------------------------------------------------------------
--spec is_new(session_id(), session_id()) -> boolean().
+-spec is_new(ssl:session_id(), ssl:session_id()) -> boolean().
%%
%% Description: Checks if the session id decided by the server is a
%% new or resumed sesion id.
@@ -47,12 +48,19 @@ is_new(_ClientSuggestion, _ServerDecision) ->
true.
%%--------------------------------------------------------------------
--spec client_id({host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(),
+-spec client_id({ssl:host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(),
undefined | binary()) -> binary().
%%
%% Description: Should be called by the client side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
+client_id({Host, Port, #ssl_options{reuse_session = SessionId}}, Cache, CacheCb, _) when is_binary(SessionId)->
+ case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
+ undefined ->
+ <<>>;
+ #session{} ->
+ SessionId
+ end;
client_id(ClientInfo, Cache, CacheCb, OwnCert) ->
case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
no_session ->
@@ -91,7 +99,8 @@ server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-select_session({_, _, #ssl_options{reuse_sessions=false}}, _Cache, _CacheCb, _OwnCert) ->
+select_session({_, _, #ssl_options{reuse_sessions = Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true ->
+ %% If reuse_sessions == true | save a new session should be created
no_session;
select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
@@ -132,7 +141,7 @@ is_resumable(SuggestedSessionId, Port, #ssl_options{reuse_session = ReuseFun} =
false -> {false, undefined}
end;
undefined ->
- {false, undefined}
+ {false, undefined}
end.
resumable(new) ->
diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl
index b68c75a09b..5f96f905b1 100644
--- a/lib/ssl/src/ssl_session_cache_api.erl
+++ b/lib/ssl/src/ssl_session_cache_api.erl
@@ -23,14 +23,20 @@
-module(ssl_session_cache_api).
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
--type key() :: {{host(), inet:port_number()}, session_id()} | {inet:port_number(), session_id()}.
+-export_type([session_cache_key/0, session/0, partial_key/0, session_cache_ref/0]).
--callback init(list()) -> db_handle().
--callback terminate(db_handle()) -> any().
--callback lookup(db_handle(), key()) -> #session{} | undefined.
--callback update(db_handle(), key(), #session{}) -> any().
--callback delete(db_handle(), key()) -> any().
--callback foldl(fun(), term(), db_handle()) -> term().
--callback select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}].
--callback size(db_handle()) -> integer().
+-type session_cache_ref() :: any().
+-type session_cache_key() :: {partial_key(), ssl:session_id()}.
+-opaque session() :: #session{}.
+-opaque partial_key() :: {ssl:host(), inet:port_number()} | inet:port_number().
+
+-callback init(list()) -> session_cache_ref().
+-callback terminate(session_cache_ref()) -> any().
+-callback lookup(session_cache_ref(), session_cache_key()) -> #session{} | undefined.
+-callback update(session_cache_ref(), session_cache_key(), #session{}) -> any().
+-callback delete(session_cache_ref(), session_cache_key()) -> any().
+-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().
diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl
deleted file mode 100644
index 08947f24dd..0000000000
--- a/lib/ssl/src/ssl_tls_dist_proxy.erl
+++ /dev/null
@@ -1,479 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2011-2016. 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(ssl_tls_dist_proxy).
-
-
--export([listen/2, accept/2, connect/3, get_tcp_address/1]).
--export([init/1, start_link/0, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3, ssl_options/2]).
-
--include_lib("kernel/include/net_address.hrl").
-
--record(state,
- {listen,
- accept_loop
- }).
-
--define(PPRE, 4).
--define(PPOST, 4).
-
-
-%%====================================================================
-%% Internal application API
-%%====================================================================
-
-listen(Driver, Name) ->
- gen_server:call(?MODULE, {listen, Driver, Name}, infinity).
-
-accept(Driver, Listen) ->
- gen_server:call(?MODULE, {accept, Driver, Listen}, infinity).
-
-connect(Driver, Ip, Port) ->
- gen_server:call(?MODULE, {connect, Driver, Ip, Port}, infinity).
-
-
-do_listen(Options) ->
- {First,Last} = case application:get_env(kernel,inet_dist_listen_min) of
- {ok,N} when is_integer(N) ->
- case application:get_env(kernel,
- inet_dist_listen_max) of
- {ok,M} when is_integer(M) ->
- {N,M};
- _ ->
- {N,N}
- end;
- _ ->
- {0,0}
- end,
- do_listen(First, Last, listen_options([{backlog,128}|Options])).
-
-do_listen(First,Last,_) when First > Last ->
- {error,eaddrinuse};
-do_listen(First,Last,Options) ->
- case gen_tcp:listen(First, Options) of
- {error, eaddrinuse} ->
- do_listen(First+1,Last,Options);
- Other ->
- Other
- end.
-
-listen_options(Opts0) ->
- Opts1 =
- case application:get_env(kernel, inet_dist_use_interface) of
- {ok, Ip} ->
- [{ip, Ip} | Opts0];
- _ ->
- Opts0
- end,
- case application:get_env(kernel, inet_dist_listen_options) of
- {ok,ListenOpts} ->
- ListenOpts ++ Opts1;
- _ ->
- Opts1
- end.
-
-connect_options(Opts) ->
- case application:get_env(kernel, inet_dist_connect_options) of
- {ok,ConnectOpts} ->
- lists:ukeysort(1, ConnectOpts ++ Opts);
- _ ->
- Opts
- end.
-
-%%====================================================================
-%% gen_server callbacks
-%%====================================================================
-
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-
-init([]) ->
- process_flag(priority, max),
- {ok, #state{}}.
-
-handle_call({listen, Driver, Name}, _From, State) ->
- case gen_tcp:listen(0, [{active, false}, {packet,?PPRE}, {ip, loopback}]) of
- {ok, Socket} ->
- {ok, World} = do_listen([{active, false}, binary, {packet,?PPRE}, {reuseaddr, true},
- Driver:family()]),
- {ok, TcpAddress} = get_tcp_address(Socket),
- {ok, WorldTcpAddress} = get_tcp_address(World),
- {_,Port} = WorldTcpAddress#net_address.address,
- ErlEpmd = net_kernel:epmd_module(),
- case ErlEpmd:register_node(Name, Port, Driver) of
- {ok, Creation} ->
- {reply, {ok, {Socket, TcpAddress, Creation}},
- State#state{listen={Socket, World}}};
- {error, _} = Error ->
- {reply, Error, State}
- end;
- Error ->
- {reply, Error, State}
- end;
-
-handle_call({accept, _Driver, Listen}, {From, _}, State = #state{listen={_, World}}) ->
- Self = self(),
- ErtsPid = spawn_link(fun() -> accept_loop(Self, erts, Listen, From) end),
- WorldPid = spawn_link(fun() -> accept_loop(Self, world, World, Listen) end),
- {reply, ErtsPid, State#state{accept_loop={ErtsPid, WorldPid}}};
-
-handle_call({connect, Driver, Ip, Port}, {From, _}, State) ->
- Me = self(),
- Pid = spawn_link(fun() -> setup_proxy(Driver, Ip, Port, Me) end),
- receive
- {Pid, go_ahead, LPort} ->
- Res = {ok, Socket} = try_connect(LPort),
- case gen_tcp:controlling_process(Socket, From) of
- {error, badarg} = Error -> {reply, Error, State}; % From is dead anyway.
- ok ->
- flush_old_controller(From, Socket),
- {reply, Res, State}
- end;
- {Pid, Error} ->
- {reply, Error, State}
- end;
-
-handle_call(_What, _From, State) ->
- {reply, ok, State}.
-
-handle_cast(_What, State) ->
- {noreply, State}.
-
-handle_info(_What, State) ->
- {noreply, State}.
-
-terminate(_Reason, _St) ->
- ok.
-
-code_change(_OldVsn, St, _Extra) ->
- {ok, St}.
-
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-get_tcp_address(Socket) ->
- case inet:sockname(Socket) of
- {ok, Address} ->
- {ok, Host} = inet:gethostname(),
- NetAddress = #net_address{
- address = Address,
- host = Host,
- protocol = proxy,
- family = inet
- },
- {ok, NetAddress};
- {error, _} = Error -> Error
- end.
-
-accept_loop(Proxy, erts = Type, Listen, Extra) ->
- process_flag(priority, max),
- case gen_tcp:accept(Listen) of
- {ok, Socket} ->
- Extra ! {accept,self(),Socket,inet,proxy},
- receive
- {_Kernel, controller, Pid} ->
- inet:setopts(Socket, [nodelay()]),
- ok = gen_tcp:controlling_process(Socket, Pid),
- flush_old_controller(Pid, Socket),
- Pid ! {self(), controller};
- {_Kernel, unsupported_protocol} ->
- exit(unsupported_protocol)
- end;
- {error, closed} ->
- %% The listening socket is closed: the proxy process is
- %% shutting down. Exit normally, to avoid generating a
- %% spurious error report.
- exit(normal);
- Error ->
- exit(Error)
- end,
- accept_loop(Proxy, Type, Listen, Extra);
-
-accept_loop(Proxy, world = Type, Listen, Extra) ->
- process_flag(priority, max),
- case gen_tcp:accept(Listen) of
- {ok, Socket} ->
- Opts = get_ssl_options(server),
- wait_for_code_server(),
- case ssl:ssl_accept(Socket, Opts) of
- {ok, SslSocket} ->
- PairHandler =
- spawn_link(fun() ->
- setup_connection(SslSocket, Extra)
- end),
- ok = ssl:controlling_process(SslSocket, PairHandler),
- flush_old_controller(PairHandler, SslSocket);
- {error, {options, _}} = Error ->
- %% Bad options: that's probably our fault. Let's log that.
- error_logger:error_msg("Cannot accept TLS distribution connection: ~s~n",
- [ssl:format_error(Error)]),
- gen_tcp:close(Socket);
- _ ->
- gen_tcp:close(Socket)
- end;
- Error ->
- exit(Error)
- end,
- accept_loop(Proxy, Type, Listen, Extra).
-
-wait_for_code_server() ->
- %% This is an ugly hack. Upgrading a socket to TLS requires the
- %% crypto module to be loaded. Loading the crypto module triggers
- %% its on_load function, which calls code:priv_dir/1 to find the
- %% directory where its NIF library is. However, distribution is
- %% started earlier than the code server, so the code server is not
- %% necessarily started yet, and code:priv_dir/1 might fail because
- %% of that, if we receive an incoming connection on the
- %% distribution port early enough.
- %%
- %% If the on_load function of a module fails, the module is
- %% unloaded, and the function call that triggered loading it fails
- %% with 'undef', which is rather confusing.
- %%
- %% Thus, the ssl_tls_dist_proxy process will terminate, and be
- %% restarted by ssl_dist_sup. However, it won't have any memory
- %% of being asked by net_kernel to listen for incoming
- %% connections. Hence, the node will believe that it's open for
- %% distribution, but it actually isn't.
- %%
- %% So let's avoid that by waiting for the code server to start.
- case whereis(code_server) of
- undefined ->
- timer:sleep(10),
- wait_for_code_server();
- Pid when is_pid(Pid) ->
- ok
- end.
-
-try_connect(Port) ->
- case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,?PPRE}, nodelay()]) of
- R = {ok, _S} ->
- R;
- {error, _R} ->
- try_connect(Port)
- end.
-
-setup_proxy(Driver, Ip, Port, Parent) ->
- process_flag(trap_exit, true),
- Opts = connect_options(get_ssl_options(client)),
- case ssl:connect(Ip, Port, [{active, true}, binary, {packet,?PPRE}, nodelay(),
- Driver:family()] ++ Opts) of
- {ok, World} ->
- {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, {ip, loopback}, binary, {packet,?PPRE}]),
- {ok, #net_address{address={_,LPort}}} = get_tcp_address(ErtsL),
- Parent ! {self(), go_ahead, LPort},
- case gen_tcp:accept(ErtsL) of
- {ok, Erts} ->
- %% gen_tcp:close(ErtsL),
- loop_conn_setup(World, Erts);
- Err ->
- Parent ! {self(), Err}
- end;
- {error, {options, _}} = Err ->
- %% Bad options: that's probably our fault. Let's log that.
- error_logger:error_msg("Cannot open TLS distribution connection: ~s~n",
- [ssl:format_error(Err)]),
- Parent ! {self(), Err};
- Err ->
- Parent ! {self(), Err}
- end.
-
-
-%% we may not always want the nodelay behaviour
-%% %% for performance reasons
-
-nodelay() ->
- case application:get_env(kernel, dist_nodelay) of
- undefined ->
- {nodelay, true};
- {ok, true} ->
- {nodelay, true};
- {ok, false} ->
- {nodelay, false};
- _ ->
- {nodelay, true}
- end.
-
-setup_connection(World, ErtsListen) ->
- process_flag(trap_exit, true),
- {ok, TcpAddress} = get_tcp_address(ErtsListen),
- {_Addr,Port} = TcpAddress#net_address.address,
- {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,?PPRE}, nodelay()]),
- ssl:setopts(World, [{active,true}, {packet,?PPRE}, nodelay()]),
- loop_conn_setup(World, Erts).
-
-loop_conn_setup(World, Erts) ->
- receive
- {ssl, World, Data = <<$a, _/binary>>} ->
- gen_tcp:send(Erts, Data),
- ssl:setopts(World, [{packet,?PPOST}, nodelay()]),
- inet:setopts(Erts, [{packet,?PPOST}, nodelay()]),
- loop_conn(World, Erts);
- {tcp, Erts, Data = <<$a, _/binary>>} ->
- ssl:send(World, Data),
- ssl:setopts(World, [{packet,?PPOST}, nodelay()]),
- inet:setopts(Erts, [{packet,?PPOST}, nodelay()]),
- loop_conn(World, Erts);
- {ssl, World, Data = <<_, _/binary>>} ->
- gen_tcp:send(Erts, Data),
- loop_conn_setup(World, Erts);
- {tcp, Erts, Data = <<_, _/binary>>} ->
- ssl:send(World, Data),
- loop_conn_setup(World, Erts);
- {ssl, World, Data} ->
- gen_tcp:send(Erts, Data),
- loop_conn_setup(World, Erts);
- {tcp, Erts, Data} ->
- ssl:send(World, Data),
- loop_conn_setup(World, Erts);
- {tcp_closed, Erts} ->
- ssl:close(World);
- {ssl_closed, World} ->
- gen_tcp:close(Erts);
- {ssl_error, World, _} ->
-
- ssl:close(World)
- end.
-
-loop_conn(World, Erts) ->
- receive
- {ssl, World, Data} ->
- gen_tcp:send(Erts, Data),
- loop_conn(World, Erts);
- {tcp, Erts, Data} ->
- ssl:send(World, Data),
- loop_conn(World, Erts);
- {tcp_closed, Erts} ->
- ssl:close(World);
- {ssl_closed, World} ->
- gen_tcp:close(Erts);
- {ssl_error, World, _} ->
- ssl:close(World)
- end.
-
-get_ssl_options(Type) ->
- case init:get_argument(ssl_dist_opt) of
- {ok, Args} ->
- [{erl_dist, true} | ssl_options(Type, lists:append(Args))];
- _ ->
- [{erl_dist, true}]
- end.
-
-ssl_options(_,[]) ->
- [];
-ssl_options(server, ["client_" ++ _, _Value |T]) ->
- ssl_options(server,T);
-ssl_options(client, ["server_" ++ _, _Value|T]) ->
- ssl_options(client,T);
-ssl_options(server, ["server_certfile", Value|T]) ->
- [{certfile, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_certfile", Value | T]) ->
- [{certfile, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_cacertfile", Value|T]) ->
- [{cacertfile, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_cacertfile", Value|T]) ->
- [{cacertfile, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_keyfile", Value|T]) ->
- [{keyfile, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_keyfile", Value|T]) ->
- [{keyfile, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_password", Value|T]) ->
- [{password, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_password", Value|T]) ->
- [{password, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_verify", Value|T]) ->
- [{verify, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_verify", Value|T]) ->
- [{verify, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_verify_fun", Value|T]) ->
- [{verify_fun, verify_fun(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_verify_fun", Value|T]) ->
- [{verify_fun, verify_fun(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_crl_check", Value|T]) ->
- [{crl_check, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_crl_check", Value|T]) ->
- [{crl_check, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_crl_cache", Value|T]) ->
- [{crl_cache, termify(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_crl_cache", Value|T]) ->
- [{crl_cache, termify(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_reuse_sessions", Value|T]) ->
- [{reuse_sessions, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_reuse_sessions", Value|T]) ->
- [{reuse_sessions, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_secure_renegotiate", Value|T]) ->
- [{secure_renegotiate, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_secure_renegotiate", Value|T]) ->
- [{secure_renegotiate, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_depth", Value|T]) ->
- [{depth, list_to_integer(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_depth", Value|T]) ->
- [{depth, list_to_integer(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_hibernate_after", Value|T]) ->
- [{hibernate_after, list_to_integer(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_hibernate_after", Value|T]) ->
- [{hibernate_after, list_to_integer(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_ciphers", Value|T]) ->
- [{ciphers, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_ciphers", Value|T]) ->
- [{ciphers, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_dhfile", Value|T]) ->
- [{dhfile, Value} | ssl_options(server,T)];
-ssl_options(server, ["server_fail_if_no_peer_cert", Value|T]) ->
- [{fail_if_no_peer_cert, atomize(Value)} | ssl_options(server,T)];
-ssl_options(Type, Opts) ->
- error(malformed_ssl_dist_opt, [Type, Opts]).
-
-atomize(List) when is_list(List) ->
- list_to_atom(List);
-atomize(Atom) when is_atom(Atom) ->
- Atom.
-
-termify(String) when is_list(String) ->
- {ok, Tokens, _} = erl_scan:string(String ++ "."),
- {ok, Term} = erl_parse:parse_term(Tokens),
- Term.
-
-verify_fun(Value) ->
- case termify(Value) of
- {Mod, Func, State} when is_atom(Mod), is_atom(Func) ->
- Fun = fun Mod:Func/3,
- {Fun, State};
- _ ->
- error(malformed_ssl_dist_opt, [Value])
- end.
-
-flush_old_controller(Pid, Socket) ->
- receive
- {tcp, Socket, Data} ->
- Pid ! {tcp, Socket, Data},
- flush_old_controller(Pid, Socket);
- {tcp_closed, Socket} ->
- Pid ! {tcp_closed, Socket},
- flush_old_controller(Pid, Socket);
- {ssl, Socket, Data} ->
- Pid ! {ssl, Socket, Data},
- flush_old_controller(Pid, Socket);
- {ssl_closed, Socket} ->
- Pid ! {ssl_closed, Socket},
- flush_old_controller(Pid, Socket)
- after 0 ->
- ok
- end.
diff --git a/lib/ssl/src/ssl_v2.erl b/lib/ssl/src/ssl_v2.erl
deleted file mode 100644
index 37134cbe5d..0000000000
--- a/lib/ssl/src/ssl_v2.erl
+++ /dev/null
@@ -1,38 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2016. 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 sslv2 hello as clients supporting sslv2 and higher
-%% will send an sslv2 hello.
-%%----------------------------------------------------------------------
-
--module(ssl_v2).
-
--export([client_random/2]).
-
-client_random(ChallengeData, 32) ->
- ChallengeData;
-client_random(ChallengeData, N) when N > 32 ->
- <<NewChallengeData:32/binary, _/binary>> = ChallengeData,
- NewChallengeData;
-client_random(ChallengeData, N) ->
- Pad = list_to_binary(lists:duplicate(N, 0)),
- <<Pad/binary, ChallengeData/binary>>.
diff --git a/lib/ssl/src/ssl_v3.erl b/lib/ssl/src/ssl_v3.erl
index 82d165f995..4eab60b440 100644
--- a/lib/ssl/src/ssl_v3.erl
+++ b/lib/ssl/src/ssl_v3.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% 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.
@@ -131,7 +131,7 @@ setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) ->
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV}.
--spec suites() -> [ssl_cipher:cipher_suite()].
+-spec suites() -> [ssl_cipher_format:cipher_suite()].
suites() ->
[
diff --git a/lib/ssl/src/tls.erl b/lib/ssl/src/tls.erl
deleted file mode 100644
index aa41cd1ba6..0000000000
--- a/lib/ssl/src/tls.erl
+++ /dev/null
@@ -1,112 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2016. 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 : Reflect TLS specific API options (fairly simple wrapper at the moment)
-
--module(tls).
-
--include("ssl_api.hrl").
--include("ssl_internal.hrl").
-
--export([connect/2, connect/3, listen/2, accept/1, accept/2,
- handshake/1, handshake/2, handshake/3]).
-
-%%--------------------------------------------------------------------
-%%
-%% Description: Connect to an TLS server.
-%%--------------------------------------------------------------------
-
--spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
- {error, reason()}.
-
-connect(Socket, Options) when is_port(Socket) ->
- connect(Socket, Options, infinity).
-
--spec connect(host() | port(), [connect_option()] | inet:port_number(),
- timeout() | list()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
-connect(Socket, SslOptions, Timeout) when is_port(Socket) ->
- TLSOpts = [{protocol, tls} | SslOptions],
- ssl:connect(Socket, TLSOpts, Timeout);
-connect(Host, Port, Options) ->
- connect(Host, Port, Options, infinity).
-
--spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
-connect(Host, Port, Options, Timeout) ->
- TLSOpts = [{protocol, tls} | Options],
- ssl:connect(Host, Port, TLSOpts, Timeout).
-
-%%--------------------------------------------------------------------
--spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
-
-%%
-%% Description: Creates an ssl listen socket.
-%%--------------------------------------------------------------------
-listen(Port, Options) ->
- TLSOpts = [{protocol, tls} | Options],
- ssl:listen(Port, TLSOpts).
-
-%%--------------------------------------------------------------------
-%%
-%% Description: Performs transport accept on an ssl listen socket
-%%--------------------------------------------------------------------
--spec accept(#sslsocket{}) -> {ok, #sslsocket{}} |
- {error, reason()}.
-accept(ListenSocket) ->
- accept(ListenSocket, infinity).
-
--spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {error, reason()}.
-accept(Socket, Timeout) ->
- ssl:transport_accept(Socket, Timeout).
-
-%%--------------------------------------------------------------------
-%%
-%% Description: Performs accept on an ssl listen socket. e.i. performs
-%% ssl handshake.
-%%--------------------------------------------------------------------
-
--spec handshake(#sslsocket{}) -> ok | {error, reason()}.
-
-handshake(ListenSocket) ->
- handshake(ListenSocket, infinity).
-
-
--spec handshake(#sslsocket{} | port(), timeout()| [ssl_option()
- | transport_option()]) ->
- ok | {ok, #sslsocket{}} | {error, reason()}.
-
-handshake(#sslsocket{} = Socket, Timeout) ->
- ssl:ssl_accept(Socket, Timeout);
-
-handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
- handshake(ListenSocket, SslOptions, infinity).
-
-
--spec handshake(port(), [ssl_option()| transport_option()], timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
-handshake(Socket, SslOptions, Timeout) when is_port(Socket) ->
- ssl:ssl_accept(Socket, SslOptions, Timeout).
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 831bbefc59..3998f03519 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. 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.
@@ -17,7 +17,6 @@
%%
%% %CopyrightEnd%
%%
-
%%
%%----------------------------------------------------------------------
%% Purpose: Handles an ssl connection, e.i. both the setup
@@ -32,128 +31,416 @@
-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("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]).
-
--export([encode_data/3, encode_alert/3]).
+-export([start_fsm/8, start_link/8, init/1, pids/1]).
%% State transition handling
--export([next_record/1, next_event/3, next_event/4]).
+-export([next_event/3, next_event/4,
+ handle_protocol_record/3]).
%% Handshake handling
--export([renegotiate/2, send_handshake/2,
+-export([renegotiation/2, renegotiate/2, send_handshake/2,
+ send_handshake_flight/1,
queue_handshake/2, queue_change_cipher/2,
- reinit_handshake_data/1, select_sni_extension/1]).
+ reinit/1, reinit_handshake_data/1, select_sni_extension/1,
+ empty_connection_state/2]).
%% Alert and close handling
--export([send_alert/2, close/5]).
+-export([send_alert/2, send_alert_in_connection/2,
+ send_sync_alert/2,
+ close/5, protocol_name/0]).
%% Data handling
--export([passive_receive/2, next_record_if_active/1, handle_common_event/4, send/3,
- socket/5]).
+-export([socket/4, setopts/3, getopts/3]).
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
- connection/3]).
+ hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+ 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([encode_handshake/4]).
+
+-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]).
+
%%====================================================================
%% Internal application API
%%====================================================================
+%%====================================================================
+%% Setup
+%%====================================================================
start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts,
- User, {CbModule, _,_, _} = CbInfo,
+ User, {CbModule, _,_, _, _} = CbInfo,
Timeout) ->
try
- {ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket,
+ {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, CbModule, Tracker),
- ok = ssl_connection:handshake(SslSocket, Timeout),
- {ok, SslSocket}
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker),
+ ssl_connection:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
Error
end;
start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = Opts,
- User, {CbModule, _,_, _} = CbInfo,
+ User, {CbModule, _,_, _, _} = CbInfo,
Timeout) ->
try
- {ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket,
+ {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, CbModule, Tracker),
- ok = ssl_connection:handshake(SslSocket, Timeout),
- {ok, SslSocket}
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker),
+ 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, {SslOpts, _, _} = Options, User, CbInfo]) ->
+ process_flag(trap_exit, true),
+ link(Sender),
+ case SslOpts#ssl_options.erl_dist of
+ true ->
+ process_flag(priority, max);
+ _ ->
+ ok
+ end,
+ 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)
+ 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 = #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 = 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
+ 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_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, 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)
+ 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} = State1} ->
+ TimerAction = case Caller of
+ undefined -> %% Passive recv complete cancel timer
+ [{{timeout, recv}, infinity, timeout}];
+ _ ->
+ []
+ end,
+ {next_state, StateName, State, Actions} = next_event(StateName, Record, State1, TimerAction),
+ ssl_connection:hibernate_after(StateName, State, Actions)
+ 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, State1} ->
+ {next_state, StateName, State, Actions} = next_event(StateName, Record, State1),
+ ssl_connection:hibernate_after(StateName, State, Actions)
+ 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},
+ ssl_options = Options} = State0) ->
+ try
+ EffectiveVersion = effective_version(Version, Options),
+ {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
+ %% 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{negotiated_version = Version,
- tls_handshake_history = Hist0,
- flight_buffer = Flight0,
- ssl_options = #ssl_options{v2_hello_compatible = V2HComp},
+
+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 = SslOpts,
connection_states = ConnectionStates0} = State0) ->
{BinHandshake, ConnectionStates, Hist} =
- encode_handshake(Handshake, Version, ConnectionStates0, Hist0, V2HComp),
+ encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Handshake),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinHandshake),
+
State0#state{connection_states = ConnectionStates,
- tls_handshake_history = Hist,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
flight_buffer = Flight0 ++ [BinHandshake]}.
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = Flight} = State0) ->
- send(Transport, Socket, Flight),
+ tls_socket:send(Transport, Socket, Flight),
{State0#state{flight_buffer = []}, []}.
-queue_change_cipher(Msg, #state{negotiated_version = Version,
- flight_buffer = Flight0,
- connection_states = ConnectionStates0} = State0) ->
+
+queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = Flight0,
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates0} = State0) ->
{BinChangeCipher, ConnectionStates} =
encode_change_cipher(Msg, Version, ConnectionStates0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinChangeCipher),
State0#state{connection_states = ConnectionStates,
flight_buffer = Flight0 ++ [BinChangeCipher]}.
-send_alert(Alert, #state{negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
- connection_states = ConnectionStates0} = State0) ->
- {BinMsg, ConnectionStates} =
- encode_alert(Alert, Version, ConnectionStates0),
- send(Transport, Socket, BinMsg),
- State0#state{connection_states = ConnectionStates}.
+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) ->
+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{
- premaster_secret = undefined,
- public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history()
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
+ public_key_info = undefined,
+ premaster_secret = undefined}
}.
-select_sni_extension(#client_hello{extensions = HelloExtensions}) ->
- HelloExtensions#hello_extensions.sni;
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
select_sni_extension(_) ->
undefined.
-encode_data(Data, Version, ConnectionStates0)->
- tls_record:encode_data(Data, Version, ConnectionStates0).
+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()) ->
@@ -164,37 +451,77 @@ encode_data(Data, Version, ConnectionStates0)->
encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
tls_record:encode_alert_record(Alert, Version, ConnectionStates).
-%%====================================================================
-%% tls_connection_sup API
-%%====================================================================
+send_alert(Alert, #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates0} = StateData0) ->
+ {BinMsg, ConnectionStates} =
+ encode_alert(Alert, Version, ConnectionStates0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, 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.
-%%--------------------------------------------------------------------
--spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) ->
- {ok, pid()} | ignore | {error, reason()}.
-%%
-%% Description: Creates a gen_fsm process which calls Module:init/1 to
-%% initialize. To ensure a synchronized start-up procedure, this function
-%% does not return until Module:init/1 has returned.
-%%--------------------------------------------------------------------
-start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
+%% 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, _,_) ->
+ Transport:close(Socket).
+protocol_name() ->
+ "TLS".
-init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
- process_flag(trap_exit, true),
- State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
- try
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- gen_statem:enter_loop(?MODULE, [], init, State)
- catch throw:Error ->
- gen_statem:enter_loop(?MODULE, [], error, {Error, State0})
- end.
+%%====================================================================
+%% Data handling
+%%====================================================================
-callback_mode() ->
- state_functions.
+socket(Pids, Transport, Socket, Tracker) ->
+ tls_socket:socket(Pids, Transport, Socket, ?MODULE, Tracker).
-socket(Pid, Transport, Socket, Connection, Tracker) ->
- tls_socket:socket(Pid, Transport, Socket, Connection, Tracker).
+setopts(Transport, Socket, Other) ->
+ tls_socket:setopts(Transport, Socket, Other).
+getopts(Transport, Socket, Tag) ->
+ tls_socket:getopts(Transport, Socket, Tag).
%%--------------------------------------------------------------------
%% State functions
@@ -206,47 +533,55 @@ socket(Pid, Transport, Socket, Connection, Tracker) ->
%%--------------------------------------------------------------------
init({call, From}, {start, Timeout},
- #state{host = Host, port = Port, role = client,
- ssl_options = #ssl_options{v2_hello_compatible = V2HComp} = SslOpts,
+ #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 = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- transport_cb = Transport, socket = Socket,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
- Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
+ KeyShare = maybe_generate_client_shares(SslOpts),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
-
- Version = Hello#client_hello.client_version,
- HelloVersion = tls_record:lowest_protocol_version(SslOpts#ssl_options.versions),
+ Cache, CacheCb, Renegotiation, Cert, KeyShare),
+
+ HelloVersion = tls_record:hello_version(SslOpts#ssl_options.versions),
Handshake0 = ssl_handshake:init_handshake_history(),
{BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0, V2HComp),
- send(Transport, Socket, BinMsg),
- State1 = State0#state{connection_states = ConnectionStates,
- negotiated_version = Version, %% Requested version
- session =
- Session0#session{session_id = Hello#client_hello.session_id},
- tls_handshake_history = Handshake,
- start_or_recv_from = From,
- timer = Timer},
- {Record, State} = next_record(State1),
- next_event(hello, Record, State);
+ encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Hello),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg),
+
+ State = State0#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version
+ session =
+ Session0#session{session_id = Hello#client_hello.session_id},
+ 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(ssl_connection, init, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec 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, From}, {start, _Timeout}, {Error, State}) ->
- {stop_and_reply, normal, {reply, From, {error, Error}}, State};
-error({call, From}, Msg, State) ->
- handle_call(Msg, From, error, State);
+error({call, _} = Call, Msg, State) ->
+ gen_handshake(?FUNCTION_NAME, Call, Msg, State);
error(_, _, _) ->
{keep_state_and_data, [postpone]}.
@@ -256,78 +591,122 @@ error(_, _, _) ->
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+hello(internal, #client_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #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, #server_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #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,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
- negotiated_protocol = CurrentProtocol,
- key_algorithm = KeyExAlg,
+ 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 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);
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
-
- gen_handshake(ssl_connection, hello, internal, {common_client_hello, Type, ServerHelloExt},
- State#state{connection_states = ConnectionStates,
- negotiated_version = Version,
- hashsign_algorithm = HashSign,
- session = Session,
- negotiated_protocol = Protocol})
+ case choose_tls_version(SslOpts, Hello) of
+ 'tls_v1.3' ->
+ %% 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
+ })
+ end
+
end;
-hello(internal, #server_hello{} = Hello,
+hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
- negotiated_version = ReqVersion,
- role = client,
- renegotiation = {Renegotiation, _},
+ 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 ->
- ssl_connection:handle_own_alert(Alert, ReqVersion, hello, State);
+ #alert{} = Alert -> %%TODO
+ ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
+ State#state{connection_env =
+ 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, State);
+ %% TLS 1.3
+ {next_state, wait_sh} ->
+ %% Continue in TLS 1.3 'wait_sh' state
+ {next_state, wait_sh, State, [{next_event, internal, Hello}]}
end;
hello(info, Event, State) ->
- gen_info(Event, hello, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
hello(Type, Event, State) ->
- gen_handshake(ssl_connection, hello, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
+user_hello(Type, Event, State) ->
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
abbreviated(info, Event, State) ->
- gen_info(Event, abbreviated, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
abbreviated(Type, Event, State) ->
- gen_handshake(ssl_connection, abbreviated, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
certify(info, Event, State) ->
- gen_info(Event, certify, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
certify(Type, Event, State) ->
- gen_handshake(ssl_connection, certify, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
cipher(info, Event, State) ->
- gen_info(Event, cipher, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- gen_handshake(ssl_connection, cipher, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -335,392 +714,475 @@ cipher(Type, Event, State) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
connection(info, Event, State) ->
- gen_info(Event, connection, State);
+ gen_info(Event, ?FUNCTION_NAME, State);
+connection({call, From}, {user_renegotiate, WriteState},
+ #state{connection_states = ConnectionStates} = State) ->
+ {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}},
+ [{next_event,{call, From}, renegotiate}]};
+connection({call, From},
+ {close, {Pid, _Timeout}},
+ #state{connection_env = #connection_env{terminated = closed} =CEnv} = State) ->
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{terminated = true,
+ downgrade = {Pid, From}}},
+ [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
+connection({call, From},
+ {close,{Pid, Timeout}},
+ #state{connection_states = ConnectionStates,
+ protocol_specific = #{sender := Sender},
+ 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}},
+ [{timeout, Timeout, downgrade}]};
+ {error, timeout} ->
+ {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
+ end;
connection(internal, #hello_request{},
- #state{role = client, host = Host, port = Port,
+ #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,
- session_cache = Cache, session_cache_cb = CacheCb,
- ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}} = State0) ->
- Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
- {State1, Actions} = send_handshake(Hello, State0),
- {Record, State} =
- next_record(
- State1#state{session = Session0#session{session_id
- = Hello#client_hello.session_id}}),
- next_event(hello, Record, State, Actions);
+ ssl_options = SslOpts,
+ protocol_specific = #{sender := Pid},
+ connection_states = ConnectionStates} = State0) ->
+ try tls_sender:peer_renegotiate(Pid) of
+ {ok, Write} ->
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
+ Cache, CacheCb, Renegotiation, Cert, undefined),
+ {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}}),
+ next_event(hello, no_record, State#state{session = Session0#session{session_id
+ = Hello#client_hello.session_id}}, Actions)
+ catch
+ _:_ ->
+ {stop, {shutdown, sender_blocked}, State0}
+ 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, _}},
+ session = #session{own_certificate = Cert} = Session0,
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates} = State0) ->
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
+ Cache, CacheCb, Renegotiation, Cert, undefined),
+
+ {State, Actions} = send_handshake(Hello, State0),
+ next_event(hello, no_record, State#state{session = Session0#session{session_id
+ = Hello#client_hello.session_id}}, Actions);
connection(internal, #client_hello{} = Hello,
- #state{role = server, allow_renegotiate = true} = State0) ->
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv,
+ connection_states = CS,
+ protocol_specific = #{sender := Sender}
+ } = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {Record, State} = next_record(State0#state{allow_renegotiate = false,
- renegotiation = {true, peer}}),
- next_event(hello, Record, State, [{next_event, internal, Hello}]);
+ {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}]);
connection(internal, #client_hello{},
- #state{role = server, allow_renegotiate = false} = State0) ->
+ #state{static_env = #static_env{role = server},
+ 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(connection, Record, State);
+ send_alert_in_connection(Alert, State0),
+ State = reinit_handshake_data(State0),
+ next_event(?FUNCTION_NAME, no_record, State);
+
+connection(internal, #new_session_ticket{}, State) ->
+ %% TLS 1.3
+ %% Drop NewSessionTicket (currently not supported)
+ next_event(?FUNCTION_NAME, no_record, State);
+
connection(Type, Event, State) ->
- ssl_connection:connection(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
%%--------------------------------------------------------------------
-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket},
+ connection_env = #connection_env{downgrade = {Pid, From}}} = State) ->
+ tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
+ Transport:controlling_process(Socket, Pid),
+ {stop_and_reply, {shutdown, downgrade},[{reply, From, {ok, Socket}}], State};
+downgrade(timeout, downgrade, #state{ connection_env = #connection_env{downgrade = {_, From}}} = State) ->
+ {stop_and_reply, {shutdown, normal},[{reply, From, {error, timeout}}], State};
+downgrade(info, {CloseTag, Socket},
+ #state{static_env = #static_env{socket = Socket,
+ close_tag = CloseTag},
+ connection_env = #connection_env{downgrade = {_, From}}} =
+ State) ->
+ {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State};
+downgrade(info, Info, State) ->
+ handle_info(Info, ?FUNCTION_NAME, State);
downgrade(Type, Event, State) ->
- ssl_connection:downgrade(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
%%--------------------------------------------------------------------
-%% Event handling functions called by state functions to handle
-%% common or unexpected events for the state.
+%% TLS 1.3 state functions
%%--------------------------------------------------------------------
-handle_call(Event, From, StateName, State) ->
- ssl_connection:handle_call(Event, From, StateName, State, ?MODULE).
-
-%% raw data from socket, unpack records
-handle_info({Protocol, _, Data}, StateName,
- #state{data_tag = Protocol} = State0) ->
- case next_tls_record(Data, State0) of
- {Record, State} ->
- next_event(StateName, Record, State);
- #alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- {stop, {shutdown, own_alert}}
- end;
-handle_info({CloseTag, Socket}, StateName,
- #state{socket = Socket, close_tag = CloseTag,
- socket_options = #socket_options{active = Active},
- protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
- negotiated_version = Version} = State) ->
+%%--------------------------------------------------------------------
+-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).
- %% 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.
+%%--------------------------------------------------------------------
+-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).
- case (Active == false) andalso (CTs =/= []) of
- false ->
- case Version of
- {1, N} when N >= 1 ->
- 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,
+%%--------------------------------------------------------------------
+-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).
- ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- {stop, {shutdown, transport_closed}};
- 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.
- next_event(StateName, no_record, State)
- end;
-handle_info(Msg, StateName, State) ->
- ssl_connection:handle_info(Msg, StateName, 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).
-handle_common_event(internal, #alert{} = Alert, StateName,
- #state{negotiated_version = Version} = State) ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, 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).
-%%% TLS record protocol level handshake messages
-handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
- StateName, #state{protocol_buffers =
- #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
- negotiated_version = Version,
- ssl_options = Options} = State0) ->
- try
- {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options),
- State1 =
- State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
- case Packets of
- [] ->
- assert_buffer_sanity(Buf, Options),
- {Record, State} = next_record(State1),
- next_event(StateName, Record, State);
- _ ->
- Events = tls_handshake_events(Packets),
- case StateName of
- connection ->
- ssl_connection:hibernate_after(StateName, State1, Events);
- _ ->
- {next_state, StateName,
- State1#state{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 application data messages
-handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]};
-%%% TLS record protocol level change cipher messages
-handle_common_event(internal, #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_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{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)
+%%--------------------------------------------------------------------
+-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).
- end;
-%% Ignore unknown TLS record level protocol messages
-handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, 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).
-send(Transport, Socket, Data) ->
- tls_socket:send(Transport, Socket, Data).
+%%--------------------------------------------------------------------
+-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).
+
+%--------------------------------------------------------------------
%% gen_statem callbacks
%%--------------------------------------------------------------------
+callback_mode() ->
+ state_functions.
+
+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);
terminate(Reason, StateName, State) ->
- catch ssl_connection:terminate(Reason, StateName, State).
+ catch ssl_connection:terminate(Reason, StateName, State),
+ ensure_sender_terminate(Reason, State).
format_status(Type, Data) ->
ssl_connection:format_status(Type, Data).
-%%--------------------------------------------------------------------
-%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
-code_change(_OldVsn, StateName, State0, {Direction, From, To}) ->
- State = convert_state(State0, Direction, From, To),
- {ok, StateName, State};
code_change(_OldVsn, StateName, State, _) ->
{ok, StateName, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-encode_handshake(Handshake, Version, ConnectionStates0, Hist0, V2HComp) ->
- Frag = tls_handshake:encode_handshake(Handshake, Version),
- Hist = ssl_handshake:update_handshake_history(Hist0, Frag, V2HComp),
- {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).
-
-initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User,
- {CbModule, DataTag, CloseTag, ErrorTag}) ->
- #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions,
+initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User,
+ {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ #ssl_options{beast_mitigation = BeastMitigation,
+ erl_dist = IsErlDist} = 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,
-
- Monitor = erlang:monitor(process, User),
-
- #state{socket_options = SocketOptions,
- ssl_options = SSLOptions,
- session = #session{is_resumable = new},
- transport_cb = CbModule,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- connection_states = ConnectionStates,
- protocol_buffers = #protocol_buffers{},
- user_application = {Monitor, User},
- user_data_buffer = <<>>,
- session_cache_cb = SessionCacheCb,
- renegotiation = {false, first},
- allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
- start_or_recv_from = undefined,
- protocol_cb = ?MODULE,
- tracker = Tracker,
- flight_buffer = []
- }.
-
-next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0,
- tls_cipher_texts = CT0} = Buffers} = State0) ->
- case tls_record:get_tls_records(Data, Buf0) of
+ 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 = ?MODULE,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ passive_tag = PassiveTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ session_cache_cb = SessionCacheCb,
+ tracker = Tracker
+ },
+ #state{
+ static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first},
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation
+ },
+ connection_env = #connection_env{user_application = {UserMonitor, User}},
+ socket_options = SocketOptions,
+ ssl_options = SSLOptions,
+ session = #session{is_resumable = new},
+ 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
+ }
+ }.
+
+initialize_tls_sender(#state{static_env = #static_env{
+ role = Role,
+ transport_cb = Transport,
+ socket = Socket,
+ tracker = Tracker
+ },
+ connection_env = #connection_env{negotiated_version = Version},
+ socket_options = SockOpts,
+ ssl_options = #ssl_options{renegotiate_at = RenegotiateAt,
+ log_level = LogLevel},
+ connection_states = #{current_write := ConnectionWriteState},
+ protocol_specific = #{sender := Sender}}) ->
+ Init = #{current_write => ConnectionWriteState,
+ role => Role,
+ socket => Socket,
+ socket_options => SockOpts,
+ tracker => Tracker,
+ transport_cb => Transport,
+ negotiated_version => Version,
+ renegotiate_at => RenegotiateAt,
+ 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 =
+ %% TLS 1.3 Client/Server
+ %% - Ignore TLSPlaintext.legacy_record_version
+ %% - Verify that TLSCiphertext.legacy_record_version is set to 0x0303 for all records
+ %% other than an initial ClientHello, where it MAY also be 0x0301.
+ case StateName of
+ hello ->
+ [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(State0#state{protocol_buffers =
+ next_record(StateName, State0#state{protocol_buffers =
Buffers#protocol_buffers{tls_record_buffer = Buf1,
tls_cipher_texts = CT1}});
#alert{} = Alert ->
- Alert
- end.
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
-next_record(#state{protocol_buffers =
- #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]}
- = Buffers,
- connection_states = ConnStates0,
- ssl_options = #ssl_options{padding_check = Check}} = State) ->
- case tls_record:decode_cipher_text(CT, ConnStates0, Check) of
- {Plain, ConnStates} ->
- {Plain, State#state{protocol_buffers =
- Buffers#protocol_buffers{tls_cipher_texts = Rest},
- connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end;
-next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []},
- socket = Socket,
- transport_cb = Transport} = State) ->
- tls_socket:setopts(Transport, Socket, [{active,once}]),
- {no_record, State};
-next_record(State) ->
- {no_record, State}.
-
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
- {no_record ,State};
-
-next_record_if_active(State) ->
- next_record(State).
-
-passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
- case Buffer of
- <<>> ->
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State);
- _ ->
- {Record, State} = ssl_connection:read_application_data(<<>>, State0),
- next_event(StateName, Record, State)
+ handle_record_alert(Alert, State0)
end.
-next_event(StateName, Record, State) ->
- next_event(StateName, Record, State, []).
-next_event(connection = StateName, no_record, State0, Actions) ->
- case next_record_if_active(State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {#ssl_tls{} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#alert{} = Alert, State} ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
- end;
-next_event(StateName, Record, State, Actions) ->
- case Record of
- no_record ->
- {next_state, StateName, State, Actions};
- #ssl_tls{} = Record ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- #alert{} = Alert ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
- 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}} = State0) ->
+ case next_tls_record(Data, StateName, State0) of
+ {Record, State} ->
+ next_event(StateName, Record, State);
+ #alert{} = Alert ->
+ ssl_connection:handle_normal_shutdown(Alert, 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{socket = Socket, close_tag = CloseTag},
+ connection_env = #connection_env{negotiated_version = Version},
+ socket_options = #socket_options{active = Active},
+ protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
+ user_data_buffer = {_,BufferSize,_},
+ protocol_specific = PS} = State) ->
-renegotiate(#state{role = client} = State, Actions) ->
- %% Handle same way as if server requested
- %% the renegotiation
- Hs0 = ssl_handshake:init_handshake_history(),
- {next_state, connection, State#state{tls_handshake_history = Hs0},
- [{next_event, internal, #hello_request{}} | Actions]};
+ %% 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.
-renegotiate(#state{role = server,
- socket = Socket,
- transport_cb = Transport,
- 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),
- send(Transport, Socket, BinMsg),
- State1 = State0#state{connection_states =
- ConnectionStates,
- tls_handshake_history = Hs0},
- {Record, State} = next_record(State1),
- next_event(hello, Record, State, Actions).
+ case (Active == false) andalso ((CTs =/= []) or (BufferSize =/= 0)) of
+ false ->
+ case Version of
+ {1, N} when N >= 1 ->
+ 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,
+
+ ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), 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) ->
+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}.
-%% 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, _,_) ->
- Transport:close(Socket).
-
-convert_state(#state{ssl_options = Options} = State, up, "5.3.5", "5.3.6") ->
- State#state{ssl_options = convert_options_partial_chain(Options, up)};
-convert_state(#state{ssl_options = Options} = State, down, "5.3.6", "5.3.5") ->
- State#state{ssl_options = convert_options_partial_chain(Options, down)}.
-
-convert_options_partial_chain(Options, up) ->
- {Head, Tail} = lists:split(5, tuple_to_list(Options)),
- list_to_tuple(Head ++ [{partial_chain, fun(_) -> unknown_ca end}] ++ Tail);
-convert_options_partial_chain(Options, down) ->
- list_to_tuple(proplists:delete(partial_chain, tuple_to_list(Options))).
-
-gen_handshake(GenConnection, StateName, Type, Event,
- #state{negotiated_version = Version} = State) ->
- try GenConnection:StateName(Type, Event, State, ?MODULE) of
+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
@@ -729,19 +1191,33 @@ gen_handshake(GenConnection, StateName, Type, Event,
malformed_handshake_data),
Version, StateName, State)
end.
-
-gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) ->
+
+
+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)
+ end.
+
+
+gen_info(Event, connection = 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(Event, StateName, #state{negotiated_version = Version} = State) ->
+gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try handle_info(Event, StateName, State) of
Result ->
Result
@@ -751,6 +1227,29 @@ gen_info(Event, StateName, #state{negotiated_version = Version} = State) ->
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
@@ -761,7 +1260,8 @@ unprocessed_events(Events) ->
erlang:length(Events)-1.
-assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, #ssl_options{max_handshake_size = Max}) when
+assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>,
+ #ssl_options{max_handshake_size = Max}) when
Length =< Max ->
case size(Rest) of
N when N < Length ->
@@ -781,3 +1281,48 @@ assert_buffer_sanity(Bin, _) ->
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}}) ->
+ %% Make sure TLS sender dies when connection process is terminated normally
+ %% This is needed if the tls_sender is blocked in prim_inet:send
+ Kill = fun() ->
+ receive
+ after 5000 ->
+ catch (exit(Sender, kill))
+ end
+ end,
+ spawn(Kill).
+
+maybe_generate_client_shares(#ssl_options{
+ 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(#ssl_options{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';
+ _Else ->
+ 'tls_v1.2'
+ end;
+choose_tls_version(_, _) ->
+ 'tls_v1.2'.
+
+
+effective_version(undefined, #ssl_options{versions = [Version|_]}) ->
+ Version;
+effective_version(Version, _) ->
+ Version.
diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl
index 0af2258932..9063b1b736 100644
--- a/lib/ssl/src/tls_connection.hrl
+++ b/lib/ssl/src/tls_connection.hrl
@@ -30,7 +30,6 @@
-include("tls_record.hrl").
-record(protocol_buffers, {
- tls_packets = [], %% :: [#ssl_tls{}], % Not yet handled decode SSL/TLS packets.
tls_record_buffer = <<>>, %% :: binary(), % Buffer of incomplete records
tls_handshake_buffer = <<>>, %% :: binary(), % Buffer of incomplete handshakes
tls_cipher_texts = [] %%:: [binary()]
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
new file mode 100644
index 0000000000..117e4f059d
--- /dev/null
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -0,0 +1,244 @@
+%%
+%% %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: TODO
+%%----------------------------------------------------------------------
+
+%% RFC 8446
+%% A.1. Client
+%%
+%% START <----+
+%% Send ClientHello | | Recv HelloRetryRequest
+%% [K_send = early data] | |
+%% v |
+%% / WAIT_SH ----+
+%% | | Recv ServerHello
+%% | | K_recv = handshake
+%% Can | V
+%% send | WAIT_EE
+%% early | | Recv EncryptedExtensions
+%% data | +--------+--------+
+%% | Using | | Using certificate
+%% | PSK | v
+%% | | WAIT_CERT_CR
+%% | | Recv | | Recv CertificateRequest
+%% | | Certificate | v
+%% | | | WAIT_CERT
+%% | | | | Recv Certificate
+%% | | v v
+%% | | WAIT_CV
+%% | | | Recv CertificateVerify
+%% | +> WAIT_FINISHED <+
+%% | | Recv Finished
+%% \ | [Send EndOfEarlyData]
+%% | K_send = handshake
+%% | [Send Certificate [+ CertificateVerify]]
+%% Can send | Send Finished
+%% app data --> | K_send = K_recv = application
+%% after here v
+%% CONNECTED
+%%
+%% A.2. Server
+%%
+%% START <-----+
+%% Recv ClientHello | | Send HelloRetryRequest
+%% v |
+%% RECVD_CH ----+
+%% | Select parameters
+%% v
+%% NEGOTIATED
+%% | Send ServerHello
+%% | K_send = handshake
+%% | Send EncryptedExtensions
+%% | [Send CertificateRequest]
+%% Can send | [Send Certificate + CertificateVerify]
+%% app data | Send Finished
+%% after --> | K_send = application
+%% here +--------+--------+
+%% No 0-RTT | | 0-RTT
+%% | |
+%% K_recv = handshake | | K_recv = early data
+%% [Skip decrypt errors] | +------> WAIT_EOED -+
+%% | | Recv | | Recv EndOfEarlyData
+%% | | early data | | K_recv = handshake
+%% | +------------+ |
+%% | |
+%% +> WAIT_FLIGHT2 <--------+
+%% |
+%% +--------+--------+
+%% No auth | | Client auth
+%% | |
+%% | v
+%% | WAIT_CERT
+%% | Recv | | Recv Certificate
+%% | empty | v
+%% | Certificate | WAIT_CV
+%% | | | Recv
+%% | v | CertificateVerify
+%% +-> WAIT_FINISHED <---+
+%% | Recv Finished
+%% | K_recv = application
+%% v
+%% CONNECTED
+
+-module(tls_connection_1_3).
+
+-include("ssl_alert.hrl").
+-include("ssl_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
+ ]).
+
+
+start(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+start(internal, #client_hello{} = Hello, State0, _Module) ->
+ case tls_handshake_1_3:do_start(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ {State, start} ->
+ {next_state, start, State, []};
+ {State, negotiated} ->
+ {next_state, negotiated, State, [{next_event, internal, start_handshake}]}
+ end;
+start(internal, #server_hello{} = ServerHello, State0, _Module) ->
+ case tls_handshake_1_3:do_start(ServerHello, State0) of
+ #alert{} = Alert ->
+ ssl_connection: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).
+
+
+negotiated(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+negotiated(internal, Message, State0, _Module) ->
+ case tls_handshake_1_3:do_negotiated(Message, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
+ {State, NextState} ->
+ {next_state, NextState, State, []}
+ end.
+
+
+wait_cert(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert(internal,
+ #certificate_1_3{} = Certificate, State0, _Module) ->
+ 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);
+ {State, NextState} ->
+ tls_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_cv(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cv(internal,
+ #certificate_verify_1_3{} = CertificateVerify, State0, _Module) ->
+ 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);
+ {State, NextState} ->
+ tls_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_finished(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_finished(internal,
+ #finished{} = Finished, State0, Module) ->
+ case tls_handshake_1_3:do_wait_finished(Finished, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0);
+ State1 ->
+ {Record, State} = ssl_connection:prepare_connection(State1, Module),
+ tls_connection:next_event(connection, Record, State)
+ end;
+wait_finished(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+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) ->
+ case tls_handshake_1_3:do_wait_sh(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_connection: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)
+ end;
+wait_sh(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+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) ->
+ case tls_handshake_1_3:do_wait_ee(EE, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0);
+ {State1, NextState} ->
+ tls_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_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) ->
+ 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);
+ {State1, NextState} ->
+ tls_connection:next_event(NextState, no_record, State1)
+ end;
+wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0, _Module) ->
+ 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);
+ {State1, NextState} ->
+ tls_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).
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 80d0239498..37265e0759 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% 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.
@@ -26,23 +26,33 @@
-module(tls_handshake).
-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
-include("tls_record.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_api.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
--export([client_hello/8, hello/4,
- get_tls_handshake/4, encode_handshake/2, decode_handshake/4]).
+%% Handshake handling
+-export([client_hello/9, hello/4]).
+
+%% Handshake encoding
+-export([encode_handshake/2]).
+
+%% Handshake decodeing
+-export([get_tls_handshake/4, decode_handshake/3]).
-type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake().
%%====================================================================
-%% Internal application API
+%% Handshake handling
%%====================================================================
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), ssl_record:connection_states(),
- #ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
+-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
+ #ssl_options{}, integer(), atom(), boolean(), der_cert(),
+ #key_share_client_hello{} | undefined) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
@@ -52,23 +62,32 @@ client_hello(Host, Port, ConnectionStates,
ciphers = UserSuites,
fallback = Fallback
} = SslOpts,
- Cache, CacheCb, Renegotiation, OwnCert) ->
+ Cache, CacheCb, Renegotiation, OwnCert, KeyShare) ->
Version = tls_record:highest_protocol_version(Versions),
- #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read),
+
+ %% In TLS 1.3, the client indicates its version preferences in 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.
+ LegacyVersion =
+ case tls_record:is_higher(Version, {3,2}) of
+ true ->
+ {3,3};
+ false ->
+ Version
+ end,
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
- Extensions = ssl_handshake:client_hello_extensions(Host, Version,
+ Extensions = ssl_handshake:client_hello_extensions(Version,
AvailableCipherSuites,
- SslOpts, ConnectionStates, Renegotiation),
- CipherSuites =
- case Fallback of
- true ->
- [?TLS_FALLBACK_SCSV | ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation)];
- false ->
- ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation)
- end,
+ SslOpts, ConnectionStates,
+ Renegotiation,
+ KeyShare),
+ CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback),
Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
#client_hello{session_id = Id,
- client_version = Version,
+ client_version = LegacyVersion,
cipher_suites = CipherSuites,
compression_methods = ssl_record:compressions(),
random = SecParams#security_parameters.client_random,
@@ -79,30 +98,138 @@ client_hello(Host, Port, ConnectionStates,
-spec hello(#server_hello{} | #client_hello{}, #ssl_options{},
ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(),
atom(), ssl_record:connection_states(),
- binary() | undefined, ssl_cipher:key_algo()},
+ binary() | undefined, ssl:kex_algo()},
boolean()) ->
- {tls_record:tls_version(), session_id(),
+ {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,
- #hello_extensions{}, {ssl_cipher:hash(), ssl_cipher:sign_algo()} | undefined} |
- #alert{}.
+ HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
+ undefined} | {atom(), atom()} |#alert{}.
%%
-%% Description: Handles a recieved hello message
+%% Description: Handles a received hello message
+%%--------------------------------------------------------------------
+
+
+%% TLS 1.3 - Section 4.1.3
+%% TLS 1.3 clients receiving a ServerHello indicating TLS 1.2 or below
+%% MUST check that the last eight bytes are not equal to either of these
+%% values.
+hello(#server_hello{server_version = {Major, Minor},
+ random = <<_:24/binary,Down:8/binary>>},
+ #ssl_options{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
+
+ (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client
+ (Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior
+ Down =:= ?RANDOM_OVERRIDE_TLS11) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+
+%% TLS 1.2 clients SHOULD also check that the last eight bytes are not
+%% 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>>},
+ #ssl_options{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) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+
+
+%% TLS 1.3 - 4.2.1. Supported Versions
+%% If the "supported_versions" extension in the ServerHello contains a
+%% version not offered by the client or contains a version prior to TLS
+%% 1.3, the client MUST abort the handshake with an "illegal_parameter"
+%% alert.
%%--------------------------------------------------------------------
-hello(#server_hello{server_version = Version, random = Random,
+%% TLS 1.2 Client
+%%
+%% - If "supported_version" is present (ServerHello):
+%% - Abort handshake with an "illegal_parameter" alert
+hello(#server_hello{server_version = LegacyVersion,
+ random = Random,
cipher_suite = CipherSuite,
compression_method = Compression,
- session_id = SessionId, extensions = HelloExt},
+ session_id = SessionId,
+ extensions = #{server_hello_selected_version :=
+ #server_hello_selected_version{selected_version = Version} = HelloExt}
+ },
+ #ssl_options{versions = SupportedVersions} = SslOpt,
+ ConnectionStates0, Renegotiation) ->
+ %% 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.
+ %% The "supported_versions" extension is supported from TLS 1.2.
+ case LegacyVersion > {3,3} orelse
+ LegacyVersion =:= {3,3} andalso Version < {3,3} of
+ true ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ false ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ case Version of
+ {3,3} ->
+ %% TLS 1.2 ServerHello with "supported_versions" (special case)
+ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
+ Compression, HelloExt, SslOpt,
+ ConnectionStates0, Renegotiation);
+ {3,4} ->
+ %% TLS 1.3
+ {next_state, wait_sh}
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
+ end
+ end;
+
+hello(#server_hello{server_version = Version,
+ random = Random,
+ cipher_suite = CipherSuite,
+ compression_method = Compression,
+ session_id = SessionId,
+ extensions = HelloExt},
#ssl_options{versions = SupportedVersions} = SslOpt,
ConnectionStates0, Renegotiation) ->
case tls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation);
+ Compression, HelloExt, SslOpt,
+ ConnectionStates0, Renegotiation);
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
end;
+
+
+%% TLS 1.2 Server
+%% - If "supported_versions" is present (ClientHello):
+%% - Select version from "supported_versions" (ignore ClientHello.legacy_version)
+%% - If server only supports versions greater than "supported_versions":
+%% - Abort handshake with a "protocol_version" alert (*)
+%% - If "supported_versions" is absent (ClientHello):
+%% - Negotiate the minimum of ClientHello.legacy_version and TLS 1.2 (**)
+%% - If server only supports versions greater than ClientHello.legacy_version:
+%% - Abort handshake with a "protocol_version" alert
+%%
+%% (*) Sends alert even if there is a gap in supported versions
+%% e.g. Server 1.0,1.2 Client 1.1,1.3
+%% (**) Current implementation can negotiate a version not supported by the client
+%% e.g. Server 1.0,1.2 Client 1.1 -> ServerHello 1.0
+hello(#client_hello{client_version = _ClientVersion,
+ cipher_suites = CipherSuites,
+ extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }} = Hello,
+ #ssl_options{versions = Versions} = SslOpts,
+ Info, Renegotiation) ->
+ try
+ Version = ssl_handshake:select_supported_version(ClientVersions, Versions),
+ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation)
+ catch
+ _:_ ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)
+ end;
hello(#client_hello{client_version = ClientVersion,
cipher_suites = CipherSuites} = Hello,
@@ -110,36 +237,40 @@ hello(#client_hello{client_version = ClientVersion,
Info, Renegotiation) ->
try
Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions),
- case ssl_cipher:is_fallback(CipherSuites) of
- true ->
- Highest = tls_record:highest_protocol_version(Versions),
- case tls_record:is_higher(Highest, Version) of
- true ->
- ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK);
- false ->
- handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
- end;
- false ->
- handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
- end
+ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation)
catch
+ error:{case_clause,{asn1, Asn1Reason}} ->
+ %% ASN-1 decode of certificate somehow failed
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {failed_to_decode_own_certificate, Asn1Reason});
_:_ ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)
end.
+
+%%--------------------------------------------------------------------
+%%% Handshake encodeing
+%%--------------------------------------------------------------------
+
%%--------------------------------------------------------------------
--spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist().
+-spec encode_handshake(tls_handshake() | tls_handshake_1_3:tls_handshake_1_3(),
+ tls_record:tls_version()) -> iolist().
%%
%% Description: Encode a handshake packet
-%%--------------------------------------------------------------------x
+%%--------------------------------------------------------------------
encode_handshake(Package, Version) ->
{MsgType, Bin} = enc_handshake(Package, Version),
Len = byte_size(Bin),
[MsgType, ?uint24(Len), Bin].
+
+%%--------------------------------------------------------------------
+%%% Handshake decodeing
+%%--------------------------------------------------------------------
+
%%--------------------------------------------------------------------
--spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist(), #ssl_options{}) ->
- {[tls_handshake()], binary()}.
+-spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist(),
+ #ssl_options{}) ->
+ {[{tls_handshake(), binary()}], binary()}.
%%
%% Description: Given buffered and new data from ssl_record, collects
%% and returns it as a list of handshake messages, also returns leftover
@@ -153,37 +284,47 @@ get_tls_handshake(Version, Data, Buffer, Options) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-handle_client_hello(Version, #client_hello{session_id = SugesstedId,
- cipher_suites = CipherSuites,
- compression_methods = Compressions,
- random = Random,
- extensions = #hello_extensions{elliptic_curves = Curves,
- signature_algs = ClientHashSigns} = HelloExt},
+handle_client_hello(Version,
+ #client_hello{session_id = SugesstedId,
+ cipher_suites = CipherSuites,
+ compression_methods = Compressions,
+ random = Random,
+ extensions = HelloExt},
#ssl_options{versions = Versions,
signature_algs = SupportedHashSigns,
eccs = SupportedECCs,
honor_ecc_order = ECCOrder} = SslOpts,
- {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) ->
+ {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _},
+ Renegotiation) ->
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),
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),
+ = ssl_handshake:select_session(SugesstedId, CipherSuites,
+ AvailableHashSigns, Compressions,
+ Port, Session0#session{ecc = ECCCurve},
+ Version, SslOpts, Cache, CacheCb, Cert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers);
_ ->
- {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
- case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of
+ #{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ case ssl_handshake:select_hashsign({ClientHashSigns, ClientSignatureSchemes},
+ Cert, KeyExAlg,
+ SupportedHashSigns,
+ Version) of
#alert{} = Alert ->
Alert;
HashSign ->
- handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt,
- SslOpts, Session1, ConnectionStates0,
+ handle_client_hello_extensions(Version, Type, Random,
+ CipherSuites, HelloExt,
+ SslOpts, Session1,
+ ConnectionStates0,
Renegotiation, HashSign)
end
end;
@@ -191,87 +332,54 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId,
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
end.
-get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
- Body:Length/binary,Rest/binary>>,
- #ssl_options{v2_hello_compatible = V2Hello} = Opts, Acc) ->
- Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
- try decode_handshake(Version, Type, Body, V2Hello) of
- Handshake ->
- get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
- catch
- _:_ ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, handshake_decode_error))
- end;
-get_tls_handshake_aux(_Version, Data, _, Acc) ->
- {lists:reverse(Acc), Data}.
-
-decode_handshake(_, ?HELLO_REQUEST, <<>>, _) ->
- #hello_request{};
-
-decode_handshake(_Version, ?CLIENT_HELLO, Bin, true) ->
- try decode_hello(Bin) of
- Hello ->
- Hello
- catch
- _:_ ->
- decode_v2_hello(Bin)
- end;
-decode_handshake(_Version, ?CLIENT_HELLO, Bin, false) ->
- decode_hello(Bin);
+handle_client_hello_extensions(Version, Type, Random, CipherSuites,
+ HelloExt, SslOpts, Session0, ConnectionStates0,
+ Renegotiation, HashSign) ->
+ try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites,
+ HelloExt, Version, SslOpts,
+ Session0, ConnectionStates0,
+ Renegotiation) of
+ {Session, ConnectionStates, Protocol, ServerHelloExt} ->
+ {Version, {Type, Session}, ConnectionStates, Protocol,
+ ServerHelloExt, HashSign}
+ catch throw:Alert ->
+ Alert
+ end.
-decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
- ?BYTE(SID_length), Session_ID:SID_length/binary,
- ?UINT16(Cs_length), CipherSuites:Cs_length/binary,
- ?BYTE(Cm_length), Comp_methods:Cm_length/binary,
- Extensions/binary>>, _) ->
-
- DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}),
- #client_hello{
- client_version = {Major,Minor},
- random = Random,
- session_id = Session_ID,
- cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites),
- compression_methods = Comp_methods,
- extensions = DecodedExtensions
- };
-decode_handshake(Version, Tag, Msg, _) ->
- ssl_handshake:decode_handshake(Version, Tag, Msg).
+handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
+ Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
+ 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}
+ catch throw:Alert ->
+ Alert
+ end.
-decode_hello(<<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
- ?BYTE(SID_length), Session_ID:SID_length/binary,
- ?UINT16(Cs_length), CipherSuites:Cs_length/binary,
- ?BYTE(Cm_length), Comp_methods:Cm_length/binary,
- Extensions/binary>>) ->
- DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}),
-
- #client_hello{
- client_version = {Major,Minor},
- random = Random,
- session_id = Session_ID,
- cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites),
- compression_methods = Comp_methods,
- extensions = DecodedExtensions
- }.
-%% The server must be able to receive such messages, from clients that
-%% are willing to use ssl v3 or higher, but have ssl v2 compatibility.
-decode_v2_hello(<<?BYTE(Major), ?BYTE(Minor),
- ?UINT16(CSLength), ?UINT16(0),
- ?UINT16(CDLength),
- CipherSuites:CSLength/binary,
- ChallengeData:CDLength/binary>>) ->
- #client_hello{client_version = {Major, Minor},
- random = ssl_v2:client_random(ChallengeData, CDLength),
- session_id = 0,
- cipher_suites = ssl_handshake:decode_suites('3_bytes', CipherSuites),
- compression_methods = [?NULL],
- extensions = #hello_extensions{}
- }.
+do_hello(undefined, _Versions, _CipherSuites, _Hello, _SslOpts, _Info, _Renegotiation) ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION);
+do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) ->
+ case ssl_cipher:is_fallback(CipherSuites) of
+ true ->
+ Highest = tls_record:highest_protocol_version(Versions),
+ case tls_record:is_higher(Highest, Version) of
+ true ->
+ ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK);
+ false ->
+ handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
+ end;
+ false ->
+ handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
+ end.
-enc_handshake(#hello_request{}, _Version) ->
+%%--------------------------------------------------------------------
+enc_handshake(#hello_request{}, {3, N}) when N < 4 ->
{?HELLO_REQUEST, <<>>};
-enc_handshake(#client_hello{client_version = {Major, Minor},
+enc_handshake(#client_hello{client_version = {Major, Minor} = Version,
random = Random,
session_id = SessionID,
cipher_suites = CipherSuites,
@@ -282,39 +390,53 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, Version),
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
?UINT16(CsLength), BinCipherSuites/binary,
?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
-
+enc_handshake(HandshakeMsg, {3, 4}) ->
+ tls_handshake_1_3:encode_handshake(HandshakeMsg);
enc_handshake(HandshakeMsg, Version) ->
ssl_handshake:encode_handshake(HandshakeMsg, Version).
+%%--------------------------------------------------------------------
+get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
+ Body:Length/binary,Rest/binary>>,
+ Opts, Acc) ->
+ Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
+ try decode_handshake(Version, Type, Body) of
+ Handshake ->
+ ssl_logger:debug(Opts#ssl_options.log_level, inbound, 'handshake', Handshake),
+ get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
+ catch
+ _:_ ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, handshake_decode_error))
+ end;
+get_tls_handshake_aux(_Version, Data, _, Acc) ->
+ {lists:reverse(Acc), Data}.
-handle_client_hello_extensions(Version, Type, Random, CipherSuites,
- HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) ->
- try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites,
- HelloExt, Version, SslOpts,
- Session0, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
- {Session, ConnectionStates, Protocol, ServerHelloExt} ->
- {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
- catch throw:Alert ->
- Alert
- end.
-
-
-handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
- Compression, HelloExt, Version,
- SslOpt, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
- {ConnectionStates, ProtoExt, Protocol} ->
- {Version, SessionId, ConnectionStates, ProtoExt, Protocol}
- end.
-
+decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 ->
+ #hello_request{};
+decode_handshake(Version, ?CLIENT_HELLO,
+ <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ ?UINT16(Cs_length), CipherSuites:Cs_length/binary,
+ ?BYTE(Cm_length), Comp_methods:Cm_length/binary,
+ Extensions/binary>>) ->
+ Exts = ssl_handshake:decode_vector(Extensions),
+ DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, {Major, Minor},
+ client_hello),
+ #client_hello{
+ client_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites),
+ compression_methods = erlang:binary_to_list(Comp_methods),
+ extensions = DecodedExtensions
+ };
+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).
diff --git a/lib/ssl/src/tls_handshake.hrl b/lib/ssl/src/tls_handshake.hrl
index f6644f64af..fc67bb61fd 100644
--- a/lib/ssl/src/tls_handshake.hrl
+++ b/lib/ssl/src/tls_handshake.hrl
@@ -32,6 +32,7 @@
client_version,
random,
session_id, % opaque SessionID<0..32>
+ cookie, % opaque<2..2^16-1>
cipher_suites, % cipher_suites<2..2^16-1>
compression_methods, % compression_methods<1..2^8-1>,
%% Extensions
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
new file mode 100644
index 0000000000..c29366e717
--- /dev/null
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -0,0 +1,1898 @@
+%%
+%% %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: Help funtions for handling the TLS 1.3 (specific parts of)
+%%% TLS handshake protocol
+%%----------------------------------------------------------------------
+
+-module(tls_handshake_1_3).
+
+-include("tls_handshake_1_3.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Encode
+-export([encode_handshake/1, decode_handshake/2]).
+
+%% Create handshake messages
+-export([certificate/5,
+ certificate_verify/4,
+ encrypted_extensions/1]).
+
+-export([do_start/2,
+ do_negotiated/2,
+ do_wait_cert/2,
+ do_wait_cv/2,
+ do_wait_finished/2,
+ do_wait_sh/2,
+ do_wait_ee/2,
+ do_wait_cert_cr/2]).
+
+
+%% crypto:hash(sha256, "HelloRetryRequest").
+-define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17,
+ 190,29,140,2,30,101,184,145,
+ 194,162,17,22,122,187,140,94,
+ 7,158,9,226,200,168,51,156>>).
+
+%%====================================================================
+%% Create handshake messages
+%%====================================================================
+
+server_hello(MsgType, SessionId, KeyShare, ConnectionStates) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ Extensions = server_hello_extensions(MsgType, KeyShare),
+ #server_hello{server_version = {3,3}, %% legacy_version
+ cipher_suite = SecParams#security_parameters.cipher_suite,
+ compression_method = 0, %% legacy attribute
+ random = server_hello_random(MsgType, SecParams),
+ session_id = SessionId,
+ extensions = Extensions
+ }.
+
+
+%% The server's extensions MUST contain "supported_versions".
+%% Additionally, it SHOULD contain the minimal set of extensions
+%% necessary for the client to generate a correct ClientHello pair. As
+%% with the ServerHello, a HelloRetryRequest MUST NOT contain any
+%% extensions that were not first offered by the client in its
+%% ClientHello, with the exception of optionally the "cookie" (see
+%% Section 4.2.2) extension.
+server_hello_extensions(hello_retry_request = MsgType, KeyShare) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions = #{server_hello_selected_version => SupportedVersions},
+ ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
+server_hello_extensions(MsgType, KeyShare) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions = #{server_hello_selected_version => SupportedVersions},
+ ssl_handshake:add_server_share(MsgType, Extensions, KeyShare).
+
+
+server_hello_random(server_hello, #security_parameters{server_random = Random}) ->
+ Random;
+%% 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
+%% SHA-256 of "HelloRetryRequest":
+%%
+%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
+%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
+server_hello_random(hello_retry_request, _) ->
+ ?HELLO_RETRY_REQUEST_RANDOM.
+
+
+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{
+ extensions = Extensions
+ }.
+
+
+certificate_request(SignAlgs0, SignAlgsCert0) ->
+ %% Input arguments contain TLS 1.2 algorithms due to backward compatibility
+ %% reasons. These {Hash, Algo} tuples must be filtered before creating the
+ %% the extensions.
+ SignAlgs = filter_tls13_algs(SignAlgs0),
+ SignAlgsCert = filter_tls13_algs(SignAlgsCert0),
+ Extensions0 = add_signature_algorithms(#{}, SignAlgs),
+ Extensions = add_signature_algorithms_cert(Extensions0, SignAlgsCert),
+ #certificate_request_1_3{
+ certificate_request_context = <<>>,
+ extensions = Extensions}.
+
+
+add_signature_algorithms(Extensions, SignAlgs) ->
+ Extensions#{signature_algorithms =>
+ #signature_algorithms{signature_scheme_list = SignAlgs}}.
+
+
+add_signature_algorithms_cert(Extensions, undefined) ->
+ Extensions;
+add_signature_algorithms_cert(Extensions, SignAlgsCert) ->
+ Extensions#{signature_algorithms_cert =>
+ #signature_algorithms_cert{signature_scheme_list = SignAlgsCert}}.
+
+
+filter_tls13_algs(undefined) -> undefined;
+filter_tls13_algs(Algo) ->
+ lists:filter(fun is_atom/1, Algo).
+
+
+%% enum {
+%% X509(0),
+%% RawPublicKey(2),
+%% (255)
+%% } CertificateType;
+%%
+%% struct {
+%% select (certificate_type) {
+%% case RawPublicKey:
+%% /* From RFC 7250 ASN.1_subjectPublicKeyInfo */
+%% opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;
+%%
+%% case X509:
+%% opaque cert_data<1..2^24-1>;
+%% };
+%% Extension extensions<0..2^16-1>;
+%% } CertificateEntry;
+%%
+%% struct {
+%% opaque certificate_request_context<0..2^8-1>;
+%% CertificateEntry certificate_list<0..2^24-1>;
+%% } Certificate;
+certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) ->
+ case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
+ {ok, _, Chain} ->
+ CertList = chain_to_cert_list(Chain),
+ %% If this message is in response to a CertificateRequest, the value of
+ %% certificate_request_context in that message. Otherwise (in the case
+ %%of server authentication), this field SHALL be zero length.
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList}};
+ {error, Error} when Role =:= server ->
+ {error, {no_suitable_certificates, Error}};
+ {error, _Error} when Role =:= client ->
+ %% The client MUST send a Certificate message if and only if the server
+ %% has requested client authentication via a CertificateRequest message
+ %% (Section 4.3.2). If the server requests client authentication but no
+ %% suitable certificate is available, the client MUST send a Certificate
+ %% message containing no certificates (i.e., with the "certificate_list"
+ %% field having length 0).
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []}}
+ end.
+
+
+certificate_verify(PrivateKey, SignatureScheme,
+ #state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages, _}}}, Role) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ {HashAlgo, _, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ Context = lists:reverse(Messages),
+
+ %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+ ContextString = context_string(Role),
+
+ %% Digital signatures use the hash function defined by the selected signature
+ %% scheme.
+ case sign(THash, ContextString, HashAlgo, PrivateKey) of
+ {ok, Signature} ->
+ {ok, #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature
+ }};
+ {error, badarg} ->
+ {error, badarg}
+
+ end.
+
+
+finished(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages, _}}}) ->
+ #{security_parameters := SecParamsR,
+ cipher_state := #cipher_state{finished_key = FinishedKey}} =
+ ssl_record:current_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ VerifyData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
+
+ #finished{
+ verify_data = VerifyData
+ }.
+
+
+%%====================================================================
+%% Encode handshake
+%%====================================================================
+
+encode_handshake(#certificate_request_1_3{
+ certificate_request_context = Context,
+ extensions = Exts})->
+ EncContext = encode_cert_req_context(Context),
+ BinExts = encode_extensions(Exts),
+ {?CERTIFICATE_REQUEST, <<EncContext/binary, BinExts/binary>>};
+encode_handshake(#certificate_1_3{
+ certificate_request_context = Context,
+ certificate_list = Entries}) ->
+ EncContext = encode_cert_req_context(Context),
+ EncEntries = encode_cert_entries(Entries),
+ {?CERTIFICATE, <<EncContext/binary, EncEntries/binary>>};
+encode_handshake(#certificate_verify_1_3{
+ algorithm = Algorithm,
+ signature = Signature}) ->
+ EncAlgo = encode_algorithm(Algorithm),
+ EncSign = encode_signature(Signature),
+ {?CERTIFICATE_VERIFY, <<EncAlgo/binary, EncSign/binary>>};
+encode_handshake(#encrypted_extensions{extensions = Exts})->
+ {?ENCRYPTED_EXTENSIONS, encode_extensions(Exts)};
+encode_handshake(#new_session_ticket{
+ ticket_lifetime = LifeTime,
+ ticket_age_add = Age,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
+ extensions = Exts}) ->
+ TicketSize = byte_size(Ticket),
+ BinExts = encode_extensions(Exts),
+ {?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age),
+ ?BYTE(Nonce), ?UINT16(TicketSize), Ticket/binary,
+ BinExts/binary>>};
+encode_handshake(#end_of_early_data{}) ->
+ {?END_OF_EARLY_DATA, <<>>};
+encode_handshake(#key_update{request_update = Update}) ->
+ {?KEY_UPDATE, <<?BYTE(Update)>>};
+encode_handshake(HandshakeMsg) ->
+ ssl_handshake:encode_handshake(HandshakeMsg, {3,4}).
+
+
+%%====================================================================
+%% Decode handshake
+%%====================================================================
+
+
+decode_handshake(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ Cipher_suite:2/binary, ?BYTE(Comp_method),
+ ?UINT16(ExtLen), Extensions:ExtLen/binary>>)
+ when Random =:= ?HELLO_RETRY_REQUEST_RANDOM ->
+ HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, {3,4}, {Major, Minor},
+ hello_retry_request),
+ #server_hello{
+ server_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method,
+ extensions = HelloExtensions};
+decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(0), ?UINT16(Size), EncExts:Size/binary>>) ->
+ Exts = decode_extensions(EncExts, certificate_request),
+ #certificate_request_1_3{
+ certificate_request_context = <<>>,
+ extensions = Exts};
+decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(CSize), Context:CSize/binary,
+ ?UINT16(Size), EncExts:Size/binary>>) ->
+ Exts = decode_extensions(EncExts, certificate_request),
+ #certificate_request_1_3{
+ certificate_request_context = Context,
+ extensions = Exts};
+decode_handshake(?CERTIFICATE, <<?BYTE(0), ?UINT24(Size), Certs:Size/binary>>) ->
+ CertList = decode_cert_entries(Certs),
+ #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList
+ };
+decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary,
+ ?UINT24(Size), Certs:Size/binary>>) ->
+ CertList = decode_cert_entries(Certs),
+ #certificate_1_3{
+ certificate_request_context = Context,
+ certificate_list = CertList
+ };
+decode_handshake(?CERTIFICATE_VERIFY, <<?UINT16(EncAlgo), ?UINT16(Size), Signature:Size/binary>>) ->
+ Algorithm = ssl_cipher:signature_scheme(EncAlgo),
+ #certificate_verify_1_3{
+ algorithm = Algorithm,
+ signature = Signature};
+decode_handshake(?ENCRYPTED_EXTENSIONS, <<?UINT16(Size), EncExts:Size/binary>>) ->
+ #encrypted_extensions{
+ extensions = decode_extensions(EncExts, encrypted_extensions)
+ };
+decode_handshake(?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age),
+ ?BYTE(Nonce), ?UINT16(TicketSize), Ticket:TicketSize/binary,
+ BinExts/binary>>) ->
+ Exts = decode_extensions(BinExts, encrypted_extensions),
+ #new_session_ticket{ticket_lifetime = LifeTime,
+ ticket_age_add = Age,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
+ extensions = Exts};
+decode_handshake(?END_OF_EARLY_DATA, _) ->
+ #end_of_early_data{};
+decode_handshake(?KEY_UPDATE, <<?BYTE(Update)>>) ->
+ #key_update{request_update = Update};
+decode_handshake(Tag, HandshakeMsg) ->
+ ssl_handshake:decode_handshake({3,4}, Tag, HandshakeMsg).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+encode_cert_req_context(<<>>) ->
+ <<?BYTE(0)>>;
+encode_cert_req_context(Bin) ->
+ Size = byte_size(Bin),
+ <<?BYTE(Size), Bin/binary>>.
+
+encode_cert_entries(Entries) ->
+ CertEntryList = encode_cert_entries(Entries, []),
+ Size = byte_size(CertEntryList),
+ <<?UINT24(Size), CertEntryList/binary>>.
+
+encode_cert_entries([], Acc) ->
+ iolist_to_binary(lists:reverse(Acc));
+encode_cert_entries([#certificate_entry{data = Data,
+ extensions = Exts} | Rest], Acc) ->
+ DSize = byte_size(Data),
+ BinExts = encode_extensions(Exts),
+ encode_cert_entries(Rest,
+ [<<?UINT24(DSize), Data/binary, BinExts/binary>> | Acc]).
+
+encode_algorithm(Algo) ->
+ Scheme = ssl_cipher:signature_scheme(Algo),
+ <<?UINT16(Scheme)>>.
+
+encode_signature(Signature) ->
+ Size = byte_size(Signature),
+ <<?UINT16(Size), Signature/binary>>.
+
+decode_cert_entries(Entries) ->
+ decode_cert_entries(Entries, []).
+
+decode_cert_entries(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_cert_entries(<<?UINT24(DSize), Data:DSize/binary, ?UINT16(Esize), BinExts:Esize/binary,
+ Rest/binary>>, Acc) ->
+ Exts = decode_extensions(BinExts, certificate_request),
+ decode_cert_entries(Rest, [#certificate_entry{data = Data,
+ extensions = Exts} | Acc]).
+
+encode_extensions(Exts)->
+ ssl_handshake:encode_extensions(extensions_list(Exts)).
+decode_extensions(Exts, MessageType) ->
+ ssl_handshake:decode_extensions(Exts, {3,4}, MessageType).
+
+extensions_list(HelloExtensions) ->
+ [Ext || {_, Ext} <- maps:to_list(HelloExtensions)].
+
+
+%% TODO: add extensions!
+chain_to_cert_list(L) ->
+ chain_to_cert_list(L, []).
+%%
+chain_to_cert_list([], Acc) ->
+ lists:reverse(Acc);
+chain_to_cert_list([H|T], Acc) ->
+ chain_to_cert_list(T, [certificate_entry(H)|Acc]).
+
+
+certificate_entry(DER) ->
+ #certificate_entry{
+ data = DER,
+ extensions = #{} %% Extensions not supported.
+ }.
+
+%% The digital signature is then computed over the concatenation of:
+%% - A string that consists of octet 32 (0x20) repeated 64 times
+%% - The context string
+%% - A single 0 byte which serves as the separator
+%% - The content to be signed
+%%
+%% For example, if the transcript hash was 32 bytes of 01 (this length
+%% would make sense for SHA-256), the content covered by the digital
+%% signature for a server CertificateVerify would be:
+%%
+%% 2020202020202020202020202020202020202020202020202020202020202020
+%% 2020202020202020202020202020202020202020202020202020202020202020
+%% 544c5320312e332c207365727665722043657274696669636174655665726966
+%% 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, badarg}
+ end;
+sign(THash, Context, HashAlgo, PrivateKey) ->
+ 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
+ Signature ->
+ {ok, Signature}
+ catch
+ error:badarg ->
+ {error, badarg}
+ end.
+
+
+verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
+ Content = build_content(Context, THash),
+ try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of
+ Result ->
+ {ok, Result}
+ catch
+ error:badarg ->
+ {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
+ Result ->
+ {ok, Result}
+ catch
+ error:badarg ->
+ {error, badarg}
+ end.
+
+
+build_content(Context, THash) ->
+ Prefix = binary:copy(<<32>>, 64),
+ <<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
+
+
+%%====================================================================
+%% Handle handshake messages
+%%====================================================================
+
+
+%% TLS Server
+do_start(#client_hello{cipher_suites = ClientCiphers,
+ session_id = SessionId,
+ extensions = Extensions} = _Hello,
+ #state{connection_states = _ConnectionStates0,
+ ssl_options = #ssl_options{ciphers = ServerCiphers,
+ signature_algs = ServerSignAlgs,
+ supported_groups = ServerGroups0,
+ alpn_preferred_protocols = ALPNPreferredProtocols,
+ honor_cipher_order = HonorCipherOrder},
+ session = #session{own_certificate = Cert}} = State0) ->
+ 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),
+
+ 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)),
+
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% Handle ALPN extension if ALPN is configured
+ ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
+
+ %% If the server does not select a PSK, then the server independently selects a
+ %% cipher suite, an (EC)DHE group and key share for key establishment,
+ %% 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),
+
+ %% 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)),
+
+ %% Select client public key. If no public key found in ClientShares or
+ %% ClientShares is empty, trigger HelloRetryRequest as we were able
+ %% to find an acceptable set of parameters but the ClientHello does not
+ %% contain sufficient information.
+ {Group, ClientPubKey} = get_client_public_key(Groups, ClientShares),
+
+ %% Generate server_share
+ KeyShare = ssl_cipher:generate_server_share(Group),
+
+ State1 = update_start_state(State0,
+ #{cipher => Cipher,
+ key_share => KeyShare,
+ session_id => SessionId,
+ group => Group,
+ sign_alg => SelectedSignAlg,
+ peer_public_key => ClientPubKey,
+ alpn => ALPNProtocol}),
+
+ %% 4.1.4. Hello Retry Request
+ %%
+ %% The server will send this message in response to a ClientHello
+ %% 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.
+ Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId))
+
+ %% TODO: session handling
+
+ catch
+ {Ref, {insufficient_security, no_suitable_groups}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
+ {Ref, illegal_parameter} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ {Ref, no_suitable_cipher} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
+ {Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
+ {Ref, {insufficient_security, no_suitable_public_key}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key);
+ {Ref, no_application_protocol} ->
+ ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)
+ end;
+%% TLS Client
+do_start(#server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions} = _ServerHello,
+ #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, _},
+ tls_handshake_history = _HHistory} = HsEnv,
+ connection_env = CEnv,
+ ssl_options = #ssl_options{ciphers = ClientCiphers,
+ supported_groups = ClientGroups0} = SslOpts,
+ session = #session{own_certificate = Cert} = Session0,
+ connection_states = ConnectionStates0
+ } = State0) ->
+ ClientGroups = get_supported_groups(ClientGroups0),
+
+ {Ref,Maybe} = maybe(),
+ try
+ ServerKeyShare = maps:get(key_share, Extensions, undefined),
+ SelectedGroup = get_selected_group(ServerKeyShare),
+
+ %% Upon receipt of this extension in a HelloRetryRequest, the client
+ %% MUST verify that (1) the selected_group field corresponds to a group
+ %% which was provided in the "supported_groups" extension in the
+ %% original ClientHello and (2) the selected_group field does not
+ %% correspond to a group which was provided in the "key_share" extension
+ %% in the original ClientHello. If either of these checks fails, then
+ %% the client MUST abort the handshake with an "illegal_parameter"
+ %% alert.
+ Maybe(validate_selected_group(SelectedGroup, ClientGroups)),
+
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+
+ %% Otherwise, when sending the new ClientHello, the client MUST
+ %% replace the original "key_share" extension with one containing only a
+ %% new KeyShareEntry for the group indicated in the selected_group field
+ %% of the triggering HelloRetryRequest.
+ ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
+ Cache, CacheCb, Renegotiation, Cert, ClientKeyShare),
+
+ HelloVersion = tls_record:hello_version(SslOpts#ssl_options.versions),
+
+ %% Update state
+ State1 = update_start_state(State0,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare,
+ session_id => SessionId,
+ group => SelectedGroup}),
+
+ %% Replace ClientHello1 with a special synthetic handshake message
+ State2 = replace_ch1_with_message_hash(State1),
+ #state{handshake_env = #handshake_env{tls_handshake_history = HHistory}} = State2,
+
+ {BinMsg, ConnectionStates, Handshake} =
+ tls_connection:encode_handshake(Hello, HelloVersion, ConnectionStates0, HHistory),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Hello),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg),
+
+ State = State2#state{
+ connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version
+ session = Session0#session{session_id = Hello#client_hello.session_id},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
+ key_share = ClientKeyShare},
+
+ {State, wait_sh}
+
+ catch
+ {Ref, {illegal_parameter, Reason}} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason)
+ end.
+
+
+do_negotiated(start_handshake,
+ #state{connection_states = ConnectionStates0,
+ session = #session{session_id = SessionId,
+ own_certificate = OwnCert,
+ ecc = SelectedGroup,
+ sign_alg = SignatureScheme,
+ dh_public_value = ClientPublicKey},
+ ssl_options = #ssl_options{} = SslOpts,
+ key_share = KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ connection_env = #connection_env{private_key = CertPrivateKey},
+ static_env = #static_env{
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ ServerPrivateKey = get_server_private_key(KeyShare),
+
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% Create server_hello
+ %% Extensions: supported_versions, key_share, (pre_shared_key)
+ ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0),
+
+ {State1, _} = tls_connection:send_handshake(ServerHello, State0),
+
+ State2 =
+ calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, State1),
+
+ State3 = ssl_record:step_encryption_state(State2),
+
+ %% Create EncryptedExtensions
+ EncryptedExtensions = encrypted_extensions(State2),
+
+ %% Encode EncryptedExtensions
+ State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
+
+ %% Create and send CertificateRequest ({verify, verify_peer})
+ {State5, NextState} = maybe_send_certificate_request(State4, SslOpts),
+
+ %% Create Certificate
+ Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server)),
+
+ %% Encode Certificate
+ State6 = tls_connection:queue_handshake(Certificate, State5),
+
+ %% Create CertificateVerify
+ CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme,
+ State6, server)),
+ %% Encode CertificateVerify
+ State7 = tls_connection:queue_handshake(CertificateVerify, State6),
+
+ %% Create Finished
+ Finished = finished(State7),
+
+ %% Encode Finished
+ State8 = tls_connection:queue_handshake(Finished, State7),
+
+ %% Send first flight
+ {State9, _} = tls_connection:send_handshake_flight(State8),
+
+ {State9, NextState}
+
+ catch
+ {Ref, badarg} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg});
+ {Ref, {no_suitable_certificates, Reason}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason})
+ end.
+
+
+do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ Maybe(process_certificate(Certificate, State0))
+ catch
+ {Ref, {certificate_required, State}} ->
+ {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State};
+ {Ref, {{certificate_unknown, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason), State};
+ {Ref, {{internal_error, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), State};
+ {Ref, {{handshake_failure, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason), State};
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State};
+ {#alert{} = Alert, State} ->
+ {Alert, State}
+ end.
+
+
+do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ State1 = Maybe(verify_signature_algorithm(State0, CertificateVerify)),
+ Maybe(verify_certificate_verify(State1, CertificateVerify))
+ catch
+ {Ref, {{bad_certificate, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, {bad_certificate, Reason}), State};
+ {Ref, {badarg, State}} ->
+ {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {verify, badarg}), State};
+ {Ref, {{handshake_failure, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State}
+ end.
+
+%% TLS Server
+do_wait_finished(#finished{verify_data = VerifyData},
+ #state{static_env = #static_env{role = server}} = State0) ->
+
+ {Ref,Maybe} = maybe(),
+
+ try
+ Maybe(validate_client_finished(State0, VerifyData)),
+
+ State1 = calculate_traffic_secrets(State0),
+
+ %% Configure traffic keys
+ ssl_record:step_encryption_state(State1)
+
+
+ catch
+ {Ref, decrypt_error} ->
+ ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)
+ end;
+%% TLS Client
+do_wait_finished(#finished{verify_data = _VerifyData},
+ #state{static_env = #static_env{role = client}} = State0) ->
+
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% Maybe(validate_client_finished(State0, VerifyData)),
+
+ %% Maybe send Certificate + CertificateVerify
+ State1 = Maybe(maybe_queue_cert_cert_cv(State0)),
+
+ Finished = finished(State1),
+
+ %% Encode Finished
+ State2 = tls_connection:queue_handshake(Finished, State1),
+
+ %% Send first flight
+ {State3, _} = tls_connection:send_handshake_flight(State2),
+
+ State4 = calculate_traffic_secrets(State3),
+
+ %% Configure traffic keys
+ ssl_record:step_encryption_state(State4)
+
+ catch
+ {Ref, decrypt_error} ->
+ ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error);
+ {Ref, badarg} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg});
+ {Ref, {no_suitable_certificates, Reason}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason})
+ end.
+
+
+do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions} = ServerHello,
+ #state{key_share = ClientKeyShare0,
+ ssl_options = #ssl_options{ciphers = ClientCiphers,
+ supported_groups = ClientGroups0}} = State0) ->
+ ClientGroups = get_supported_groups(ClientGroups0),
+ ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
+ ClientKeyShare = get_key_shares(ClientKeyShare0),
+
+ {Ref,Maybe} = maybe(),
+ try
+ %% Go to state 'start' if server replies with 'HelloRetryRequest'.
+ Maybe(maybe_hello_retry_request(ServerHello, State0)),
+
+ ServerKeyShare = get_key_shares(ServerKeyShare0),
+
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+ Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)),
+
+ %% Get server public key
+ {SelectedGroup, ServerPublicKey} = get_server_public_key(ServerKeyShare),
+
+ {_, ClientPrivateKey} = get_client_private_key([SelectedGroup], ClientKeyShare),
+
+ %% Update state
+ State1 = update_start_state(State0,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare0,
+ session_id => SessionId,
+ group => SelectedGroup,
+ peer_public_key => ServerPublicKey}),
+
+ State2 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, State1),
+
+ State3 = ssl_record:step_encryption_state(State2),
+
+ {State3, wait_ee}
+
+ catch
+ {Ref, {State, StateName, ServerHello}} ->
+ {State, StateName, ServerHello};
+ {Ref, {insufficient_security, no_suitable_groups}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
+ {Ref, illegal_parameter} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ {Ref, no_suitable_cipher} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
+ {Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
+ {Ref, {insufficient_security, no_suitable_public_key}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
+ end.
+
+
+do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
+
+ ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
+ ALPNProtocol = get_alpn(ALPNProtocol0),
+
+ {Ref,_Maybe} = maybe(),
+
+ try
+ %% Update state
+ #state{handshake_env = HsEnv} = State0,
+ State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
+
+ {State1, wait_cert_cr}
+ catch
+ {Ref, {insufficient_security, no_suitable_groups}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
+ {Ref, illegal_parameter} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ {Ref, no_suitable_cipher} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
+ {Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
+ {Ref, {insufficient_security, no_suitable_public_key}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
+ end.
+
+
+do_wait_cert_cr(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ Maybe(process_certificate(Certificate, State0))
+ catch
+ {Ref, {certificate_required, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required);
+ {Ref, {{certificate_unknown, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason);
+ {Ref, {{internal_error, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason);
+ {Ref, {{handshake_failure, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State}
+ end;
+do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ Maybe(process_certificate_request(CertificateRequest, State0))
+ catch
+ {Ref, {certificate_required, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required);
+ {Ref, {{certificate_unknown, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason);
+ {Ref, {illegal_parameter, Reason}} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason);
+ {Ref, {{internal_error, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason);
+ {Ref, {{handshake_failure, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason)
+ end.
+
+
+
+%% TODO: Remove this function!
+%% not_implemented(State, Reason) ->
+%% {error, {not_implemented, State, Reason}}.
+
+%% not_implemented(update_secrets, State0, Reason) ->
+%% State1 = calculate_traffic_secrets(State0),
+%% State = ssl_record:step_encryption_state(State1),
+%% {error, {not_implemented, State, Reason}}.
+
+
+%% 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
+%% SHA-256 of "HelloRetryRequest":
+%%
+%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
+%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
+%%
+%% Upon receiving a message with type server_hello, implementations MUST
+%% first examine the Random value and, if it matches this value, process
+%% it as described in Section 4.1.4).
+maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, State0) ->
+ {error, {State0, start, ServerHello}};
+maybe_hello_retry_request(_, _) ->
+ ok.
+
+
+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},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ static_env = #static_env{
+ role = client,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ %% Create Certificate
+ Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)),
+
+ %% Encode Certificate
+ State1 = tls_connection:queue_handshake(Certificate, State0),
+
+ %% Maybe create and queue CertificateVerify
+ State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
+ {ok, State}
+ catch
+ {Ref, badarg} ->
+ {error, badarg}
+ end.
+
+
+%% Clients MUST send this message whenever authenticating via a certificate
+%% (i.e., when the Certificate message is non-empty).
+maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) ->
+ {ok, State};
+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}
+ } = State) ->
+ {Ref,Maybe} = maybe(),
+ try
+ CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)),
+ {ok, tls_connection:queue_handshake(CertificateVerify, State)}
+ catch
+ {Ref, badarg} ->
+ {error, badarg}
+ end.
+
+
+%% Recipients of Finished messages MUST verify that the contents are
+%% correct and if incorrect MUST terminate the connection with a
+%% "decrypt_error" alert.
+validate_client_finished(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages0, _}}}, VerifyData) ->
+ #{security_parameters := SecParamsR,
+ cipher_state := #cipher_state{finished_key = FinishedKey}} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ %% Drop the client's finished message, it is not part of the handshake context
+ %% when the client calculates its finished message.
+ [_|Messages] = Messages0,
+
+ ControlData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
+ compare_verify_data(ControlData, VerifyData).
+
+
+compare_verify_data(Data, Data) ->
+ ok;
+compare_verify_data(_, _) ->
+ {error, decrypt_error}.
+
+
+send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0,
+ no_suitable_key, KeyShare, SessionId) ->
+ ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0),
+ {State1, _} = tls_connection:send_handshake(ServerHello, State0),
+
+ %% Update handshake history
+ State2 = replace_ch1_with_message_hash(State1),
+
+ {ok, {State2, start}};
+send_hello_retry_request(State0, _, _, _) ->
+ %% Suitable key found.
+ {ok, {State0, negotiated}}.
+
+
+maybe_send_certificate_request(State, #ssl_options{verify = verify_none}) ->
+ {State, wait_finished};
+maybe_send_certificate_request(State, #ssl_options{
+ verify = verify_peer,
+ signature_algs = SignAlgs,
+ signature_algs_cert = SignAlgsCert}) ->
+ CertificateRequest = certificate_request(SignAlgs, SignAlgsCert),
+ {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}.
+
+
+process_certificate_request(#certificate_request_1_3{},
+ #state{session = #session{own_certificate = 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) ->
+ 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'
+ {ok, {State#state{client_certificate_requested = true,
+ session = Session#session{own_certificate = undefined}}, wait_cert}}
+ end.
+
+
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #ssl_options{
+ fail_if_no_peer_cert = false}} = State) ->
+ {ok, {State, wait_finished}};
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #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
+ %% secrets.
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {certificate_required, State}};
+process_certificate(#certificate_1_3{certificate_list = Certs0},
+ #state{ssl_options =
+ #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, {{handshake_failure,
+ "Client certificate uses unsupported signature algorithm"}, 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
+%% already stepped into the 'connection' state.
+update_encryption_state(server, State0) ->
+ State1 = calculate_traffic_secrets(State0),
+ ssl_record:step_encryption_state(State1);
+update_encryption_state(client, State) ->
+ State.
+
+
+validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHandle, Role, Host) ->
+ ServerName = ssl_handshake:server_name(SslOptions#ssl_options.server_name_indication, Host, Role),
+ [PeerCert | ChainCerts ] = Certs,
+ try
+ {TrustedCert, CertPath} =
+ ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef,
+ SslOptions#ssl_options.partial_chain),
+ ValidationFunAndState =
+ ssl_handshake:validation_fun_and_state(SslOptions#ssl_options.verify_fun, Role,
+ CertDbHandle, CertDbRef, ServerName,
+ SslOptions#ssl_options.customize_hostname_check,
+ SslOptions#ssl_options.crl_check, CRLDbHandle, CertPath),
+ Options = [{max_path_length, SslOptions#ssl_options.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, {certificate_unknown, {failed_to_decode_certificate, Asn1Reason}}};
+ error:OtherReason ->
+ {error, {internal_error, {unexpected_error, OtherReason}}}
+ end.
+
+
+store_peer_cert(#state{session = Session,
+ handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) ->
+ State#state{session = Session#session{peer_certificate = PeerCert},
+ 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).
+
+
+%% 4.4.1. The Transcript Hash
+%%
+%% As an exception to this general rule, when the server responds to a
+%% ClientHello with a HelloRetryRequest, the value of ClientHello1 is
+%% replaced with a special synthetic handshake message of handshake type
+%% "message_hash" containing Hash(ClientHello1). I.e.,
+%%
+%% Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
+%% Hash(message_hash || /* Handshake type */
+%% 00 00 Hash.length || /* Handshake message length (bytes) */
+%% Hash(ClientHello1) || /* Hash of ClientHello1 */
+%% HelloRetryRequest || ... || Mn)
+%%
+%% NOTE: Hash.length is used in practice (openssl) and not message length!
+%% It is most probably a fault in the RFC.
+replace_ch1_with_message_hash(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history =
+ {[HRR,CH1|HHistory], LM}} = HSEnv} = State0) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+ MessageHash = message_hash(CH1, HKDFAlgo),
+ State0#state{handshake_env =
+ HSEnv#handshake_env{
+ tls_handshake_history =
+ {[HRR,MessageHash|HHistory], LM}}}.
+
+
+message_hash(ClientHello1, HKDFAlgo) ->
+ [?MESSAGE_HASH,
+ 0,0,ssl_cipher:hash_size(HKDFAlgo),
+ crypto:hash(HKDFAlgo, ClientHello1)].
+
+
+calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup,
+ #state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}} = State0) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo,
+ cipher_suite = CipherSuite} = SecParamsR,
+
+ %% Calculate handshake_secret
+ PSK = binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo)),
+ EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
+
+ IKM = calculate_shared_secret(PublicKey, PrivateKey, SelectedGroup),
+ HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
+
+ %% 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),
+
+ %% Calculate Finished Keys
+ ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo),
+ WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo),
+
+ update_pending_connection_states(State0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey).
+
+
+calculate_traffic_secrets(#state{
+ static_env = #static_env{role = Role},
+ connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}} = State0) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo,
+ cipher_suite = CipherSuite,
+ master_secret = HandshakeSecret} = SecParamsR,
+
+ MasterSecret =
+ tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
+
+ %% Get the correct list messages for the handshake context.
+ Messages = get_handshake_context(Role, HHistory),
+
+ %% Calculate [sender]_application_traffic_secret_0
+ ClientAppTrafficSecret0 =
+ tls_v1:client_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
+ ServerAppTrafficSecret0 =
+ 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),
+
+ update_pending_connection_states(State0, MasterSecret,
+ ReadKey, ReadIV, undefined,
+ WriteKey, WriteIV, undefined).
+
+
+get_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
+ get_private_key(ServerShare).
+
+get_private_key(#key_share_entry{
+ key_exchange = #'ECPrivateKey'{} = PrivateKey}) ->
+ PrivateKey;
+get_private_key(#key_share_entry{
+ key_exchange =
+ {_, PrivateKey}}) ->
+ PrivateKey.
+
+%% X25519, X448
+calculate_shared_secret(OthersKey, MyKey, Group)
+ when is_binary(OthersKey) andalso is_binary(MyKey) andalso
+ (Group =:= x25519 orelse Group =:= x448)->
+ crypto:compute_key(ecdh, OthersKey, MyKey, Group);
+%% FFDHE
+calculate_shared_secret(OthersKey, MyKey, Group)
+ when is_binary(OthersKey) andalso is_binary(MyKey) ->
+ Params = #'DHParameter'{prime = P} = ssl_dh_groups:dh_params(Group),
+ S = public_key:compute_key(OthersKey, MyKey, Params),
+ Size = byte_size(binary:encode_unsigned(P)),
+ ssl_cipher:add_zero_padding(S, Size);
+%% ECDHE
+calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
+ when is_binary(OthersKey) ->
+ Point = #'ECPoint'{point = OthersKey},
+ public_key:compute_key(Point, MyKey).
+
+
+update_pending_connection_states(#state{
+ static_env = #static_env{role = server},
+ connection_states =
+ CS = #{pending_read := PendingRead0,
+ pending_write := PendingWrite0}} = State,
+ HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey) ->
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey),
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
+ WriteKey, WriteIV, WriteFinishedKey),
+ State#state{connection_states = CS#{pending_read => PendingRead,
+ pending_write => PendingWrite}};
+update_pending_connection_states(#state{
+ static_env = #static_env{role = client},
+ connection_states =
+ CS = #{pending_read := PendingRead0,
+ pending_write := PendingWrite0}} = State,
+ HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey) ->
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret,
+ WriteKey, WriteIV, WriteFinishedKey),
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey),
+ State#state{connection_states = CS#{pending_read => PendingRead,
+ pending_write => PendingWrite}}.
+
+
+update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0},
+ HandshakeSecret, Key, IV, FinishedKey) ->
+ %% Store secret
+ SecurityParameters = SecurityParameters0#security_parameters{
+ master_secret = HandshakeSecret},
+ ConnectionState#{security_parameters => SecurityParameters,
+ cipher_state => cipher_init(Key, IV, FinishedKey)}.
+
+
+update_start_state(State, Map) ->
+ Cipher = maps:get(cipher, Map, undefined),
+ KeyShare = maps:get(key_share, Map, undefined),
+ SessionId = maps:get(session_id, Map, undefined),
+ Group = maps:get(group, Map, undefined),
+ SelectedSignAlg = maps:get(sign_alg, Map, undefined),
+ PeerPublicKey = maps:get(peer_public_key, Map, undefined),
+ ALPNProtocol = maps:get(alpn, Map, undefined),
+ update_start_state(State, Cipher, KeyShare, SessionId,
+ Group, SelectedSignAlg, PeerPublicKey,
+ ALPNProtocol).
+%%
+update_start_state(#state{connection_states = ConnectionStates0,
+ handshake_env = #handshake_env{} = HsEnv,
+ connection_env = CEnv,
+ session = Session} = State,
+ Cipher, KeyShare, SessionId,
+ Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol) ->
+ #{security_parameters := SecParamsR0} = PendingRead =
+ maps:get(pending_read, ConnectionStates0),
+ #{security_parameters := SecParamsW0} = PendingWrite =
+ maps:get(pending_write, ConnectionStates0),
+ SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher),
+ SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher),
+ ConnectionStates =
+ ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR},
+ pending_write => PendingWrite#{security_parameters => SecParamsW}},
+ State#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol},
+ key_share = KeyShare,
+ session = Session#session{session_id = SessionId,
+ ecc = Group,
+ sign_alg = SelectedSignAlg,
+ dh_public_value = PeerPublicKey,
+ cipher_suite = Cipher},
+ connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
+
+
+cipher_init(Key, IV, FinishedKey) ->
+ #cipher_state{key = Key,
+ iv = IV,
+ finished_key = FinishedKey,
+ tag_len = 16}.
+
+
+%% Get handshake context for verification of CertificateVerify.
+%%
+%% Verify CertificateVerify:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% CertificateRequest (server) (13)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Certificate (client) (11)
+%% CertificateVerify (client) (15) - Drop! Not included in calculations!
+get_handshake_context_cv({[<<15,_/binary>>|Messages], _}) ->
+ Messages.
+
+
+%% Get handshake context for traffic key calculation.
+%%
+%% Client is authenticated with certificate:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% CertificateRequest (server) (13)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Certificate (client) (11) - Drop! Not included in calculations!
+%% CertificateVerify (client) (15) - Drop! Not included in calculations!
+%% Finished (client) (20) - Drop! Not included in calculations!
+%%
+%% Client is authenticated but sends empty certificate:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% CertificateRequest (server) (13)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Certificate (client) (11) - Drop! Not included in calculations!
+%% Finished (client) (20) - Drop! Not included in calculations!
+%%
+%% Client is not authenticated:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Finished (client) (20) - Drop! Not included in calculations!
+%%
+%% Drop all client messages from the front of the iolist using the property that
+%% incoming messages are binaries.
+get_handshake_context(server, {Messages, _}) ->
+ get_handshake_context_server(Messages);
+get_handshake_context(client, {Messages, _}) ->
+ get_handshake_context_client(Messages).
+
+get_handshake_context_server([H|T]) when is_binary(H) ->
+ get_handshake_context_server(T);
+get_handshake_context_server(L) ->
+ L.
+
+
+get_handshake_context_client([H|T]) when is_list(H) ->
+ get_handshake_context_client(T);
+get_handshake_context_client(L) ->
+ L.
+
+
+%% If the CertificateVerify message is sent by a server, the signature
+%% algorithm MUST be one offered in the client's "signature_algorithms"
+%% extension unless no valid certificate chain can be produced without
+%% unsupported algorithms
+%%
+%% If sent by a client, the signature algorithm used in the signature
+%% MUST be one of those present in the supported_signature_algorithms
+%% field of the "signature_algorithms" extension in the
+%% CertificateRequest message.
+verify_signature_algorithm(#state{
+ static_env = #static_env{role = Role},
+ ssl_options =
+ #ssl_options{
+ signature_algs = LocalSignAlgs}} = State0,
+ #certificate_verify_1_3{algorithm = PeerSignAlg}) ->
+ case lists:member(PeerSignAlg, LocalSignAlgs) of
+ true ->
+ {ok, maybe_update_selected_sign_alg(State0, PeerSignAlg, Role)};
+ false ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {{handshake_failure,
+ "CertificateVerify uses unsupported signature algorithm"}, State}}
+ end.
+
+
+maybe_update_selected_sign_alg(#state{session = Session} = State, SignAlg, client) ->
+ State#state{session = Session#session{sign_alg = SignAlg}};
+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,
+ #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature}) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ {HashAlgo, _, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ Messages = get_handshake_context_cv(HHistory),
+
+ Context = lists:reverse(Messages),
+
+ %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+
+ ContextString = peer_context_string(Role),
+
+ %% Digital signatures use the hash function defined by the selected signature
+ %% scheme.
+ case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of
+ {ok, true} ->
+ {ok, {State0, wait_finished}};
+ {ok, false} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {{handshake_failure, "Failed to verify CertificateVerify"}, State}};
+ {error, badarg} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {badarg, State}}
+ end.
+
+
+context_string(server) ->
+ <<"TLS 1.3, server CertificateVerify">>;
+context_string(client) ->
+ <<"TLS 1.3, client CertificateVerify">>.
+
+
+%% Return context string for verifing peer signature
+peer_context_string(server) ->
+ <<"TLS 1.3, client CertificateVerify">>;
+peer_context_string(client) ->
+ <<"TLS 1.3, server CertificateVerify">>.
+
+
+%% If there is no overlap between the received
+%% "supported_groups" and the groups supported by the server, then the
+%% server MUST abort the handshake with a "handshake_failure" or an
+%% "insufficient_security" alert.
+select_common_groups(_, []) ->
+ {error, {insufficient_security, no_suitable_groups}};
+select_common_groups(ServerGroups, ClientGroups) ->
+ Fun = fun(E) -> lists:member(E, ClientGroups) end,
+ case lists:filter(Fun, ServerGroups) of
+ [] ->
+ {error, {insufficient_security, no_suitable_groups}};
+ L ->
+ {ok, L}
+ end.
+
+
+%% RFC 8446 - 4.2.8. Key Share
+%% This vector MAY be empty if the client is requesting a
+%% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a
+%% group offered in the "supported_groups" extension and MUST appear in
+%% the same order. However, the values MAY be a non-contiguous subset
+%% of the "supported_groups" extension and MAY omit the most preferred
+%% groups.
+%%
+%% Clients can offer as many KeyShareEntry values as the number of
+%% supported groups it is offering, each representing a single set of
+%% key exchange parameters.
+%%
+%% Clients MUST NOT offer multiple KeyShareEntry values
+%% for the same group. Clients MUST NOT offer any KeyShareEntry values
+%% for groups not listed in the client's "supported_groups" extension.
+%% Servers MAY check for violations of these rules and abort the
+%% handshake with an "illegal_parameter" alert if one is violated.
+validate_client_key_share(_ ,[]) ->
+ ok;
+validate_client_key_share([], _) ->
+ {error, illegal_parameter};
+validate_client_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
+ validate_client_key_share(ClientGroups, ClientShares);
+validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) ->
+ validate_client_key_share(ClientGroups, ClientShares).
+
+
+%% Verify that selected group is offered by the client.
+validate_server_key_share([G|_ClientGroups], {_, G, _}) ->
+ ok;
+validate_server_key_share([_|ClientGroups], {_, _, _} = ServerKeyShare) ->
+ validate_server_key_share(ClientGroups, ServerKeyShare).
+
+
+validate_selected_group(SelectedGroup, [SelectedGroup|_]) ->
+ {error, {illegal_parameter,
+ "Selected group sent by the server shall not correspond to a group"
+ " which was provided in the key_share extension"}};
+validate_selected_group(SelectedGroup, ClientGroups) ->
+ case lists:member(SelectedGroup, ClientGroups) of
+ true ->
+ ok;
+ false ->
+ {error, {illegal_parameter,
+ "Selected group sent by the server shall correspond to a group"
+ " which was provided in the supported_groups extension"}}
+ end.
+
+
+get_client_public_key([Group|_] = Groups, ClientShares) ->
+ get_client_public_key(Groups, ClientShares, Group).
+%%
+get_client_public_key(_, [], PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+get_client_public_key([], _, PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+get_client_public_key([Group|Groups], ClientShares, PreferredGroup) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, {_, _, ClientPublicKey}} ->
+ {Group, ClientPublicKey};
+ false ->
+ get_client_public_key(Groups, ClientShares, PreferredGroup)
+ end.
+
+get_client_private_key([Group|_] = Groups, ClientShares) ->
+ get_client_private_key(Groups, ClientShares, Group).
+%%
+get_client_private_key(_, [], PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+get_client_private_key([], _, PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+get_client_private_key([Group|Groups], ClientShares, PreferredGroup) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, {_, _, {_, ClientPrivateKey}}} ->
+ {Group, ClientPrivateKey};
+ {value, {_, _, #'ECPrivateKey'{} = ClientPrivateKey}} ->
+ {Group, ClientPrivateKey};
+ false ->
+ get_client_private_key(Groups, ClientShares, PreferredGroup)
+ end.
+
+
+get_server_public_key({key_share_entry, Group, PublicKey}) ->
+ {Group, PublicKey}.
+
+
+%% RFC 7301 - Application-Layer Protocol Negotiation Extension
+%% It is expected that a server will have a list of protocols that it
+%% supports, in preference order, and will only select a protocol if the
+%% client supports it. In that case, the server SHOULD select the most
+%% highly preferred protocol that it supports and that is also
+%% advertised by the client. In the event that the server supports no
+%% protocols that the client advertises, then the server SHALL respond
+%% with a fatal "no_application_protocol" alert.
+handle_alpn(undefined, _) ->
+ {ok, undefined};
+handle_alpn([], _) ->
+ {error, no_application_protocol};
+handle_alpn([_|_], undefined) ->
+ {ok, undefined};
+handle_alpn([ServerProtocol|T], ClientProtocols) ->
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true ->
+ {ok, ServerProtocol};
+ false ->
+ handle_alpn(T, ClientProtocols)
+ end.
+
+
+select_cipher_suite(_, [], _) ->
+ {error, no_suitable_cipher};
+%% If honor_cipher_order is set to true, use the server's preference for
+%% cipher suite selection.
+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
+ lists:member(Cipher, ServerCiphers) of
+ true ->
+ {ok, Cipher};
+ false ->
+ select_cipher_suite(false, ClientCiphers, ServerCiphers)
+ end.
+
+
+%% RFC 8446 4.1.3 ServerHello
+%% A client which receives a cipher suite that was not offered MUST abort the
+%% handshake with an "illegal_parameter" alert.
+validate_cipher_suite(Cipher, ClientCiphers) ->
+ case lists:member(Cipher, ClientCiphers) of
+ true ->
+ ok;
+ false ->
+ {error, illegal_parameter}
+ end.
+
+
+%% RFC 8446 (TLS 1.3)
+%% TLS 1.3 provides two extensions for indicating which signature
+%% algorithms may be used in digital signatures. The
+%% "signature_algorithms_cert" extension applies to signatures in
+%% certificates and the "signature_algorithms" extension, which
+%% originally appeared in TLS 1.2, applies to signatures in
+%% CertificateVerify messages.
+%%
+%% If no "signature_algorithms_cert" extension is
+%% present, then the "signature_algorithms" extension also applies to
+%% signatures appearing in certificates.
+
+%% Check if the signature algorithm of the server certificate is supported
+%% by the client.
+check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, undefined) ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs);
+check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgsCert).
+
+
+%% DSA keys are not supported by TLS 1.3
+select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) ->
+ {error, {insufficient_security, no_suitable_public_key}};
+select_sign_algo(_, [], _) ->
+ {error, {insufficient_security, no_suitable_signature_algorithm}};
+select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
+ {_, S, _} = ssl_cipher:scheme_to_components(C),
+ %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
+ %% TLS handshake messages: filter sha-1 and rsa_pkcs1.
+ %%
+ %% RSASSA-PSS RSAE algorithms: If the public key is carried in an X.509
+ %% certificate, it MUST use the rsaEncryption OID.
+ %% 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 =:= ecdsa andalso S =:= ecdsa))
+ andalso
+ lists:member(C, ServerSignAlgs) of
+ true ->
+ {ok, C};
+ false ->
+ select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)
+ end.
+
+
+do_check_cert_sign_algo(_, _, []) ->
+ {error, {insufficient_security, no_suitable_signature_algorithm}};
+do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) ->
+ {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme),
+ case compare_sign_algos(SignAlgo, SignHash, Sign, Hash) of
+ true ->
+ ok;
+ _Else ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, T)
+ end.
+
+
+%% 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;
+%% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or
+%% signature schemes.
+compare_sign_algos(rsa, Hash, Algo, Hash)
+ when Algo =:= rsa_pss_rsae orelse
+ Algo =:= rsa_pkcs1 ->
+ true;
+compare_sign_algos(Algo, Hash, Algo, Hash) ->
+ true;
+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}.
+
+
+%% Note: copied from ssl_handshake
+public_key_algo(?'id-RSASSA-PSS') ->
+ rsa_pss;
+public_key_algo(?rsaEncryption) ->
+ rsa;
+public_key_algo(?'id-ecPublicKey') ->
+ ecdsa;
+public_key_algo(?'id-dsa') ->
+ dsa.
+
+get_signature_scheme_list(undefined) ->
+ undefined;
+get_signature_scheme_list(#signature_algorithms_cert{
+ signature_scheme_list = ClientSignatureSchemes}) ->
+ ClientSignatureSchemes;
+get_signature_scheme_list(#signature_algorithms{
+ signature_scheme_list = ClientSignatureSchemes}) ->
+ %% Filter unassigned and legacy elements
+ lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end,
+ ClientSignatureSchemes).
+
+get_supported_groups(#supported_groups{supported_groups = Groups}) ->
+ Groups.
+
+get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
+ ClientShares;
+get_key_shares(#key_share_server_hello{server_share = ServerShare}) ->
+ ServerShare.
+
+get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) ->
+ SelectedGroup.
+
+get_alpn(ALPNProtocol0) ->
+ case ssl_handshake:decode_alpn(ALPNProtocol0) of
+ undefined ->
+ undefined;
+ [ALPNProtocol] ->
+ ALPNProtocol
+ end.
+
+maybe() ->
+ Ref = erlang:make_ref(),
+ Ok = fun(ok) -> ok;
+ ({ok,R}) -> R;
+ ({error,Reason}) ->
+ throw({Ref,Reason})
+ end,
+ {Ref,Ok}.
diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl
new file mode 100644
index 0000000000..eb85f216c8
--- /dev/null
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -0,0 +1,261 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-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: Record and constant defenitions for the TLS-handshake protocol
+%% see RFC 8446. Also includes supported hello extensions.
+%%----------------------------------------------------------------------
+
+-ifndef(tls_handshake_1_3).
+-define(tls_handshake_1_3, true).
+
+%% Common to TLS-1.3 and previous TLS versions
+%% Some defenitions may not exist in TLS-1.3 this is
+%% handled elsewhere
+-include("tls_handshake.hrl").
+
+%% New handshake types in TLS-1.3 RFC 8446 B.3
+-define(NEW_SESSION_TICKET, 4).
+-define(END_OF_EARLY_DATA, 5).
+-define(ENCRYPTED_EXTENSIONS, 8).
+-define(KEY_UPDATE, 24).
+%% %% Not really a message but special way to handle handshake hashes
+%% %% when a "hello-retry-request" (special server_hello) is sent
+-define(MESSAGE_HASH, 254).
+
+%% %% RFC 8446 B.3.1.
+%% %% New extension types in TLS-1.3
+-define(PRE_SHARED_KEY_EXT, 41).
+-define(EARLY_DATA_EXT, 42).
+%%-define(SUPPORTED_VERSIONS_EXT, 43). %% Updates TLS 1.2 so defined in ssl_handshake.hrl
+-define(COOKIE_EXT, 44).
+-define(PSK_KEY_EXCHANGE_MODES_EXT, 45).
+-define(CERTIFICATE_AUTHORITIES_EXT, 47).
+-define(OID_FILTERS_EXT, 48).
+-define(POST_HANDSHAKE_AUTH_EXT, 49).
+%% -define(SIGNATURE_ALGORITHMS_CERT_EXT, 50). %% Updates TLS 1.2 so defined in ssl_handshake.hrl
+-define(KEY_SHARE_EXT, 51).
+
+%% RFC 8446 B.3.1
+-record(key_share_entry, {
+ group, %NamedGroup
+ key_exchange %key_exchange<1..2^16-1>;
+ }).
+-record(key_share_client_hello, {
+ client_shares %% KeyShareEntry client_shares<0..2^16-1>;
+ }).
+-record(key_share_hello_retry_request, {
+ selected_group %% NamedGroup
+ }).
+-record(key_share_server_hello, {
+ server_share %% KeyShareEntry server_share;
+ }).
+
+-record(uncompressed_point_representation, {
+ legacy_form = 4, % uint8 legacy_form = 4;
+ x, % opaque X[coordinate_length];
+ y % opaque Y[coordinate_length];
+ }).
+
+%% RFC 8446 4.2.9. Pre-Shared Key Exchange Modes
+
+%% enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
+-define(PSK_KE, 0).
+-define(PSK_DHE_KE, 1).
+
+-record(psk_key_exchange_modes, {
+ ke_modes % ke_modes<1..255>
+ }).
+
+%% 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)
+ }).
+
+%% RFC 8446 4.2.11. Pre-Shared Key Extension
+-record(psk_identity,
+ {
+ identity, % opaque identity<1..2^16-1>
+ obfuscated_ticket_age % uint32
+ }).
+
+-record(offered_psks,
+ {
+ identities, % PskIdentity identities<7..2^16-1>;
+ binders % PskBinderEntry binders<33..2^16-1>; opaque PskBinderEntry<32..255>
+ }).
+
+%% struct {
+%% select (Handshake.msg_type) {
+%% case client_hello: OfferedPsks;
+%% case server_hello: uint16 selected_identity;
+%% };
+%% } PreSharedKeyExtension;
+-record(pre_shared_key_client_hello,
+ {
+ offered_psks
+ }).
+
+-record(pre_shared_key_server_hello,
+ {
+ selected_identity
+ }).
+
+%% RFC 8446 B.3.1.2.
+-record(cookie, {
+ cookie %cookie<1..2^16-1>;
+ }).
+
+%%% RFC 8446 B.3.1.3. Signature Algorithm Extension
+%% Signature Schemes
+%% RSASSA-PKCS1-v1_5 algorithms
+-define(RSA_PKCS1_SHA256, 16#0401).
+-define(RSA_PKCS1_SHA384, 16#0501).
+-define(RSA_PKCS1_SHA512, 16#0601).
+
+%% ECDSA algorithms
+-define(ECDSA_SECP256R1_SHA256, 16#0403).
+-define(ECDSA_SECP384R1_SHA384, 16#0503).
+-define(ECDSA_SECP521R1_SHA512, 16#0603).
+
+%% RSASSA-PSS algorithms with public key OID rsaEncryption
+-define(RSA_PSS_RSAE_SHA256, 16#0804).
+-define(RSA_PSS_RSAE_SHA384, 16#0805).
+-define(RSA_PSS_RSAE_SHA512, 16#0806).
+
+%% EdDSA algorithms
+-define(ED25519, 16#0807).
+-define(ED448, 16#0808).
+
+%% RSASSA-PSS algorithms with public key OID RSASSA-PSS
+-define(RSA_PSS_PSS_SHA256, 16#0809).
+-define(RSA_PSS_PSS_SHA384, 16#080a).
+-define(RSA_PSS_PSS_SHA512, 16#080b).
+
+%% Legacy algorithms
+-define(RSA_PKCS1_SHA1, 16#201).
+-define(ECDSA_SHA1, 16#0203).
+
+%% RFC 8446 B.3.1.4. Supported Groups Extension
+%% Elliptic Curve Groups (ECDHE)
+-define(SECP256R1, 16#0017).
+-define(SECP384R1, 16#0018).
+-define(SECP521R1, 16#0019).
+-define(X25519, 16#001D).
+-define(X448, 16#001E).
+
+%% RFC 8446 Finite Field Groups (DHE)
+-define(FFDHE2048, 16#0100).
+-define(FFDHE3072, 16#0101).
+-define(FFDHE4096, 16#0102).
+-define(FFDHE6144, 16#0103).
+-define(FFDHE8192 ,16#0104).
+
+-record(named_group_list, {
+ named_group_list %named_group_list<2..2^16-1>;
+ }).
+
+%% RFC 8446 B.3.2 Server Parameters Messages
+%% opaque DistinguishedName<1..2^16-1>;XS
+-record(certificate_authoritie_sextension, {
+ authorities %DistinguishedName authorities<3..2^16-1>;
+ }).
+
+-record(oid_filter, {
+ certificate_extension_oid, % opaque certificate_extension_oid<1..2^8-1>;
+ certificate_extension_values % opaque certificate_extension_values<0..2^16-1>;
+ }).
+
+-record(oid_filter_extension, {
+ filters %OIDFilter filters<0..2^16-1>;
+ }).
+-record(post_handshake_auth, {
+ }).
+
+-record(encrypted_extensions, {
+ extensions %extensions<0..2^16-1>;
+ }).
+
+-record(certificate_request_1_3, {
+ certificate_request_context, % opaque certificate_request_context<0..2^8-1>;
+ extensions %Extension extensions<2..2^16-1>;
+ }).
+
+%% RFC 8446 B.3.3 Authentication Messages
+
+%% Certificate Type
+-define(X509, 0).
+-define(OpenPGP_RESERVED, 1).
+-define(RawPublicKey, 2).
+
+-record(certificate_entry, {
+ data,
+ %% select (certificate_type) {
+ %% case RawPublicKey:
+ %% /* From RFC 7250 ASN.1_subjectPublicKeyInfo */
+ %% opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;
+ %%
+ %% case X509:
+ %% opaque cert_data<1..2^24-1>;
+ %% };
+ extensions %% Extension extensions<0..2^16-1>;
+ }).
+
+-record(certificate_1_3, {
+ certificate_request_context, % opaque certificate_request_context<0..2^8-1>;
+ certificate_list % CertificateEntry certificate_list<0..2^24-1>;
+ }).
+
+-record(certificate_verify_1_3, {
+ algorithm, % SignatureScheme
+ signature % signature<0..2^16-1>
+ }).
+
+%% RFC 8446 B.3.4. Ticket Establishment
+-record(new_session_ticket, {
+ ticket_lifetime, %unit32
+ ticket_age_add, %unit32
+ ticket_nonce, %opaque ticket_nonce<0..255>;
+ ticket, %opaque ticket<1..2^16-1>
+ extensions %extensions<0..2^16-2>
+ }).
+
+%% RFC 8446 B.3.5. Updating Keys
+-record(end_of_early_data, {
+ }).
+
+-define(UPDATE_NOT_REQUESTED, 0).
+-define(UPDATE_REQUESTED, 1).
+
+-record(key_update, {
+ request_update
+ }).
+
+-type tls_handshake_1_3() :: #encrypted_extensions{} |
+ #certificate_request_1_3{} |
+ #certificate_1_3{} |
+ #certificate_verify_1_3{}.
+
+-export_type([tls_handshake_1_3/0]).
+
+-endif. % -ifdef(tls_handshake_1_3).
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 993a1622fe..2aeab98929 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. 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.
@@ -30,23 +30,27 @@
-include("ssl_alert.hrl").
-include("tls_handshake.hrl").
-include("ssl_cipher.hrl").
+-include_lib("kernel/include/logger.hrl").
%% Handling of incoming data
--export([get_tls_records/2, init_connection_states/2]).
+-export([get_tls_records/4, init_connection_states/2]).
%% Encoding TLS records
-export([encode_handshake/3, encode_alert_record/3,
encode_change_cipher_spec/2, encode_data/3]).
-export([encode_plain_text/4]).
+%% Decoding
+-export([decode_cipher_text/4]).
+
+%% Logging helper
+-export([build_tls_record/1]).
+
%% 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_acceptable_version/1, is_acceptable_version/2]).
-
-%% Decoding
--export([decode_cipher_text/3]).
+ is_acceptable_version/1, is_acceptable_version/2, hello_version/1]).
-export_type([tls_version/0, tls_atom_version/0]).
@@ -56,13 +60,12 @@
-compile(inline).
%%====================================================================
-%% Internal application API
+%% Handling of incoming data
%%====================================================================
%%--------------------------------------------------------------------
-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) ->
ssl_record:connection_states().
-%% %
- %
+%%
%% Description: Creates a connection_states record with appropriate
%% values for the initial SSL connection setup.
%%--------------------------------------------------------------------
@@ -76,16 +79,27 @@ init_connection_states(Role, BeastMitigation) ->
pending_write => Pending}.
%%--------------------------------------------------------------------
--spec get_tls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}.
+-spec get_tls_records(
+ binary(),
+ [tls_version()] | tls_version(),
+ Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}},
+ #ssl_options{}) ->
+ {Records :: [#ssl_tls{}],
+ Buffer :: {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}} |
+ #alert{}.
%%
%% and returns it as a list of tls_compressed binaries also returns leftover
%% Description: Given old buffer and new data from TCP, packs up a records
%% data
%%--------------------------------------------------------------------
-get_tls_records(Data, <<>>) ->
- get_tls_records_aux(Data, []);
-get_tls_records(Data, Buffer) ->
- get_tls_records_aux(list_to_binary([Buffer, 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).
+
+%%====================================================================
+%% Encoding
+%%====================================================================
%%--------------------------------------------------------------------
-spec encode_handshake(iolist(), tls_version(), ssl_record:connection_states()) ->
@@ -93,6 +107,8 @@ get_tls_records(Data, Buffer) ->
%
%% Description: Encodes a handshake message to send on the ssl-socket.
%%--------------------------------------------------------------------
+encode_handshake(Frag, {3, 4}, ConnectionStates) ->
+ tls_record_1_3:encode_handshake(Frag, ConnectionStates);
encode_handshake(Frag, Version,
#{current_write :=
#{beast_mitigation := BeastMitigation,
@@ -101,8 +117,8 @@ encode_handshake(Frag, Version,
ConnectionStates) ->
case iolist_size(Frag) of
N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
- encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates);
+ Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation),
+ encode_fragments(?HANDSHAKE, Version, Data, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
end.
@@ -113,6 +129,8 @@ encode_handshake(Frag, Version,
%%
%% Description: Encodes an alert message to send on the ssl-socket.
%%--------------------------------------------------------------------
+encode_alert_record(Alert, {3, 4}, ConnectionStates) ->
+ tls_record_1_3:encode_alert_record(Alert, ConnectionStates);
encode_alert_record(#alert{level = Level, description = Description},
Version, ConnectionStates) ->
encode_plain_text(?ALERT, Version, <<?BYTE(Level), ?BYTE(Description)>>,
@@ -128,19 +146,94 @@ encode_change_cipher_spec(Version, ConnectionStates) ->
encode_plain_text(?CHANGE_CIPHER_SPEC, Version, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_data(binary(), tls_version(), ssl_record:connection_states()) ->
- {iolist(), ssl_record:connection_states()}.
+-spec encode_data([binary()], tls_version(), ssl_record:connection_states()) ->
+ {[[binary()]], ssl_record:connection_states()}.
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_data(Frag, Version,
+encode_data(Data, {3, 4}, ConnectionStates) ->
+ tls_record_1_3:encode_data(Data, ConnectionStates);
+encode_data(Data, Version,
#{current_write := #{beast_mitigation := BeastMitigation,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
- Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
- encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates).
+ Fragments = split_iovec(Data, Version, BCA, BeastMitigation),
+ encode_fragments(?APPLICATION_DATA, Version, Fragments, ConnectionStates).
+%%====================================================================
+%% Decoding
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec decode_cipher_text(tls_version(), #ssl_tls{}, ssl_record:connection_states(), boolean()) ->
+ {#ssl_tls{}, ssl_record:connection_states()}| #alert{}.
+%%
+%% Description: Decode cipher text
+%%--------------------------------------------------------------------
+decode_cipher_text({3,4}, CipherTextRecord, ConnectionStates, _) ->
+ tls_record_1_3:decode_cipher_text(CipherTextRecord, ConnectionStates);
+decode_cipher_text(_, CipherTextRecord,
+ #{current_read :=
+ #{sequence_number := Seq,
+ security_parameters :=
+ #security_parameters{cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo},
+ cipher_state := CipherS0
+ }
+ } = ConnectionStates0, _) ->
+ SeqBin = <<?UINT64(Seq)>>,
+ #ssl_tls{type = Type, version = {MajVer,MinVer} = Version, fragment = Fragment} = CipherTextRecord,
+ StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>,
+ CipherS = ssl_record:nonce_seed(BulkCipherAlgo, SeqBin, CipherS0),
+ case ssl_record:decipher_aead(
+ BulkCipherAlgo, CipherS, StartAdditionalData, Fragment, Version)
+ of
+ PlainFragment when is_binary(PlainFragment) ->
+ #{current_read :=
+ #{security_parameters := SecParams,
+ compression_state := CompressionS0} = ReadState0} = ConnectionStates0,
+ {Plain, CompressionS} = ssl_record:uncompress(SecParams#security_parameters.compression_algorithm,
+ PlainFragment, CompressionS0),
+ ConnectionStates = ConnectionStates0#{
+ current_read => ReadState0#{
+ cipher_state => CipherS,
+ sequence_number => Seq + 1,
+ compression_state => CompressionS}},
+ {CipherTextRecord#ssl_tls{fragment = Plain}, ConnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end;
+
+decode_cipher_text(_, #ssl_tls{version = Version,
+ fragment = CipherFragment} = CipherTextRecord,
+ #{current_read := ReadState0} = ConnnectionStates0, PaddingCheck) ->
+ case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of
+ {PlainFragment, Mac, ReadState1} ->
+ MacHash = ssl_cipher:calc_mac_hash(CipherTextRecord#ssl_tls.type, Version, PlainFragment, ReadState1),
+ case ssl_record:is_correct_mac(Mac, MacHash) of
+ true ->
+ #{sequence_number := Seq,
+ compression_state := CompressionS0,
+ security_parameters :=
+ #security_parameters{compression_algorithm = CompAlg}} = ReadState0,
+ {Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
+ PlainFragment, CompressionS0),
+ ConnnectionStates =
+ ConnnectionStates0#{current_read =>
+ ReadState1#{sequence_number => Seq + 1,
+ compression_state => CompressionS1}},
+ {CipherTextRecord#ssl_tls{fragment = Plain}, ConnnectionStates};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+ #alert{} = Alert ->
+ Alert
+ end.
+
+%%====================================================================
+%% Protocol version handling
+%%====================================================================
%%--------------------------------------------------------------------
-spec protocol_version(tls_atom_version() | tls_version()) ->
@@ -149,6 +242,8 @@ encode_data(Frag, Version,
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
%%--------------------------------------------------------------------
+protocol_version('tlsv1.3') ->
+ {3, 4};
protocol_version('tlsv1.2') ->
{3, 3};
protocol_version('tlsv1.1') ->
@@ -159,6 +254,8 @@ protocol_version(sslv3) ->
{3, 0};
protocol_version(sslv2) -> %% Backwards compatibility
{2, 0};
+protocol_version({3, 4}) ->
+ 'tlsv1.3';
protocol_version({3, 3}) ->
'tlsv1.2';
protocol_version({3, 2}) ->
@@ -277,11 +374,7 @@ supported_protocol_versions([_|_] = Vsns) ->
NewVsns
end
end.
-%%--------------------------------------------------------------------
-%%
-%% Description: ssl version 2 is not acceptable security risks are too big.
-%%
-%%--------------------------------------------------------------------
+
-spec is_acceptable_version(tls_version()) -> boolean().
is_acceptable_version({N,_})
when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION ->
@@ -296,6 +389,12 @@ is_acceptable_version({N,_} = Version, Versions)
is_acceptable_version(_,_) ->
false.
+-spec hello_version([tls_version()]) -> tls_version().
+hello_version([Highest|_]) when Highest >= {3,3} ->
+ Highest;
+hello_version(Versions) ->
+ lowest_protocol_version(Versions).
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -312,70 +411,244 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
server_verify_data => undefined
}.
-get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
-get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length),
- Data:Length/binary, Rest/binary>>, Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
-get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary,
- Rest/binary>>, Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
-get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
- get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
-%% Matches an ssl v2 client hello message.
-%% The server must be able to receive such messages, from clients that
-%% are willing to use ssl v3 or higher, but have ssl v2 compatibility.
-get_tls_records_aux(<<1:1, Length0:15, Data0:Length0/binary, Rest/binary>>,
- Acc) ->
- case Data0 of
- <<?BYTE(?CLIENT_HELLO), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>> ->
- Length = Length0-1,
- <<?BYTE(_), Data1:Length/binary>> = Data0,
- Data = <<?BYTE(?CLIENT_HELLO), ?UINT24(Length), Data1/binary>>,
- get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
- _ ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
-
+%% Used by logging to recreate the received bytes
+build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fragment}) ->
+ Length = byte_size(Fragment),
+ <<?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).
+
+%% Generic code path
+decode_tls_records(Versions, {_,Size,_} = Q0, 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);
+ 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);
+ 1 =< Size ->
+ {<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0),
+ validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, undefined, undefined);
+ true ->
+ validate_tls_records_type(Versions, Q0, SslOpts, Acc, undefined, undefined, undefined)
+ end;
+decode_tls_records(Versions, {_,Size,_} = Q0, 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);
+ 2 =< Size ->
+ {<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0),
+ validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ true ->
+ validate_tls_record_version(Versions, Q0, SslOpts, Acc, Type, undefined, undefined)
+ end;
+decode_tls_records(Versions, {_,Size,_} = Q0, 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);
+ true ->
+ validate_tls_record_length(Versions, Q0, 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).
+
+validate_tls_records_type(_Versions, Q, _SslOpts, Acc, undefined, _Version, _Length) ->
+ {lists:reverse(Acc),
+ {undefined, Q}};
+validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
+ if
+ ?KNOWN_RECORD_TYPE(Type) ->
+ validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ true ->
+ %% Not ?KNOWN_RECORD_TYPE(Type)
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
+ end.
-get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
- ?UINT16(Length), _/binary>>,
- _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
- ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
+validate_tls_record_version(_Versions, Q, _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) ->
+ 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);
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+ {3, 4} when Version =:= {3, 3} ->
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ Version ->
+ %% Exact version match
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end.
-get_tls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc)
- when Length0 > ?MAX_CIPHER_TEXT_LENGTH ->
- ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
+validate_tls_record_length(_Versions, Q, _SslOpts, Acc, Type, Version, undefined) ->
+ {lists:reverse(Acc),
+ {#ssl_tls{type = Type, version = Version, fragment = undefined}, Q}};
+validate_tls_record_length(Versions, {_,Size0,_} = Q0, SslOpts, Acc, Type, Version, Length) ->
+ if
+ Length =< ?MAX_CIPHER_TEXT_LENGTH ->
+ if
+ Length =< Size0 ->
+ %% Complete record
+ {Fragment, Q} = binary_from_front(Length, Q0),
+ Record = #ssl_tls{type = Type, version = Version, fragment = Fragment},
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', Record),
+ decode_tls_records(Versions, Q, SslOpts, [Record|Acc], undefined, undefined, undefined);
+ true ->
+ {lists:reverse(Acc),
+ {#ssl_tls{type = Type, version = Version, fragment = Length}, Q0}}
+ end;
+ true ->
+ ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW)
+ end.
-get_tls_records_aux(Data, Acc) ->
- case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
- true ->
- {lists:reverse(Acc), Data};
- false ->
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
- end.
-encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) ->
- {CipherFragment, Write1} = ssl_record:encode_plain_text(Type, Version, Data, Write0),
- {CipherText, Write} = encode_tls_cipher_text(Type, Version, CipherFragment, Write1),
- {CipherText, ConnectionStates#{current_write => Write}}.
+binary_from_front(0, Q) ->
+ {<<>>, Q};
+binary_from_front(SplitSize, {Front,Size,Rear}) when SplitSize =< Size ->
+ binary_from_front(SplitSize, Front, Size, Rear, []).
+%%
+%% SplitSize > 0 and there is at least SplitSize bytes buffered in Front and Rear
+binary_from_front(SplitSize, [], Size, Rear, Acc) ->
+ case Rear of
+ %% Avoid lists:reverse/1 for simple cases.
+ %% Case clause for [] to avoid infinite loop.
+ [_] ->
+ binary_from_front(SplitSize, Rear, Size, [], Acc);
+ [Bin2,Bin1] ->
+ binary_from_front(SplitSize, [Bin1,Bin2], Size, [], Acc);
+ [Bin3,Bin2,Bin1] ->
+ binary_from_front(SplitSize, [Bin1,Bin2,Bin3], Size, [], Acc);
+ [_,_,_|_] ->
+ binary_from_front(SplitSize, lists:reverse(Rear), Size, [], Acc)
+ end;
+binary_from_front(SplitSize, [Bin|Front], Size, Rear, []) ->
+ %% Optimize the frequent case when the accumulator is empty
+ BinSize = byte_size(Bin),
+ if
+ SplitSize < BinSize ->
+ {RetBin, Rest} = erlang:split_binary(Bin, SplitSize),
+ {RetBin, {[Rest|Front],Size - SplitSize,Rear}};
+ BinSize < SplitSize ->
+ binary_from_front(SplitSize - BinSize, Front, Size, Rear, [Bin]);
+ true -> % Perfect fit
+ {Bin, {Front,Size - SplitSize,Rear}}
+ end;
+binary_from_front(SplitSize, [Bin|Front], Size, Rear, Acc) ->
+ BinSize = byte_size(Bin),
+ if
+ SplitSize < BinSize ->
+ {Last, Rest} = erlang:split_binary(Bin, SplitSize),
+ RetBin = iolist_to_binary(lists:reverse(Acc, [Last])),
+ {RetBin, {[Rest|Front],Size - byte_size(RetBin),Rear}};
+ BinSize < SplitSize ->
+ binary_from_front(SplitSize - BinSize, Front, Size, Rear, [Bin|Acc]);
+ true -> % Perfect fit
+ RetBin = iolist_to_binary(lists:reverse(Acc, [Bin])),
+ {RetBin, {Front,Size - byte_size(RetBin),Rear}}
+ end.
+%%--------------------------------------------------------------------
+encode_plain_text(Type, Version, Data, ConnectionStates0) ->
+ {[CipherText],ConnectionStates} = encode_fragments(Type, Version, [Data], ConnectionStates0),
+ {CipherText,ConnectionStates}.
+%%--------------------------------------------------------------------
+encode_fragments(Type, Version, Data,
+ #{current_write := #{compression_state := CompS,
+ cipher_state := CipherS,
+ sequence_number := Seq}} = ConnectionStates) ->
+ encode_fragments(Type, Version, Data, ConnectionStates, CompS, CipherS, Seq, []).
+%%
+encode_fragments(_Type, _Version, [], #{current_write := WriteS} = CS,
+ CompS, CipherS, Seq, CipherFragments) ->
+ {lists:reverse(CipherFragments),
+ CS#{current_write := WriteS#{compression_state := CompS,
+ cipher_state := CipherS,
+ sequence_number := Seq}}};
+encode_fragments(Type, Version, [Text|Data],
+ #{current_write := #{security_parameters :=
+ #security_parameters{cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BCAlg,
+ compression_algorithm = CompAlg} = SecPars}} = CS,
+ CompS0, CipherS0, Seq, CipherFragments) ->
+ {CompText, CompS} = ssl_record:compress(CompAlg, Text, CompS0),
+ SeqBin = <<?UINT64(Seq)>>,
+ CipherS1 = ssl_record:nonce_seed(BCAlg, SeqBin, CipherS0),
+ {MajVer, MinVer} = Version,
+ VersionBin = <<?BYTE(MajVer), ?BYTE(MinVer)>>,
+ StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), VersionBin/binary>>,
+ {CipherFragment,CipherS} = ssl_record:cipher_aead(Version, CompText, CipherS1, StartAdditionalData, SecPars),
+ Length = byte_size(CipherFragment),
+ CipherHeader = <<?BYTE(Type), VersionBin/binary, ?UINT16(Length)>>,
+ encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1,
+ [[CipherHeader, CipherFragment] | CipherFragments]);
+encode_fragments(Type, Version, [Text|Data],
+ #{current_write := #{security_parameters :=
+ #security_parameters{compression_algorithm = CompAlg,
+ mac_algorithm = MacAlgorithm} = SecPars,
+ mac_secret := MacSecret}} = CS,
+ CompS0, CipherS0, Seq, CipherFragments) ->
+ {CompText, CompS} = ssl_record:compress(CompAlg, Text, CompS0),
+ MacHash = ssl_cipher:calc_mac_hash(Type, Version, CompText, MacAlgorithm, MacSecret, Seq),
+ {CipherFragment,CipherS} = ssl_record:cipher(Version, CompText, CipherS0, MacHash, SecPars),
+ Length = byte_size(CipherFragment),
+ {MajVer, MinVer} = Version,
+ CipherHeader = <<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>,
+ encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1,
+ [[CipherHeader, CipherFragment] | CipherFragments]);
+encode_fragments(_Type, _Version, _Data, CS, _CompS, _CipherS, _Seq, _CipherFragments) ->
+ exit({cs, CS}).
+%%--------------------------------------------------------------------
+
+%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
+%% not vulnerable to this attack.
+split_iovec(Data, Version, BCA, one_n_minus_one)
+ when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
+ {3, 0} == Version) ->
+ {Part, RestData} = split_iovec(Data, 1, []),
+ [Part|split_iovec(RestData)];
+%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
+%% splitting.
+split_iovec(Data, Version, BCA, zero_n)
+ 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).
+
+split_iovec([]) ->
+ [];
+split_iovec(Data) ->
+ {Part,Rest} = split_iovec(Data, ?MAX_PLAIN_TEXT_LENGTH, []),
+ [Part|split_iovec(Rest)].
+%%
+split_iovec([Bin|Data] = Bin_Data, SplitSize, Acc) ->
+ BinSize = byte_size(Bin),
+ if
+ BinSize =< SplitSize ->
+ split_iovec(Data, SplitSize - BinSize, [Bin|Acc]);
+ SplitSize == 0 ->
+ {lists:reverse(Acc), Bin_Data};
+ SplitSize < BinSize ->
+ {Last, Rest} = erlang:split_binary(Bin, SplitSize),
+ {lists:reverse(Acc, [Last]), [Rest|Data]}
+ end;
+split_iovec([], _SplitSize, Acc) ->
+ {lists:reverse(Acc),[]}.
+
+%%--------------------------------------------------------------------
lowest_list_protocol_version(Ver, []) ->
Ver;
lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
@@ -386,11 +659,6 @@ highest_list_protocol_version(Ver, []) ->
highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
-encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment, #{sequence_number := Seq} = Write) ->
- Length = erlang:iolist_size(Fragment),
- {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment],
- Write#{sequence_number => Seq +1}}.
-
highest_protocol_version() ->
highest_protocol_version(supported_protocol_versions()).
@@ -401,92 +669,4 @@ sufficient_tlsv1_2_crypto_support() ->
CryptoSupport = crypto:supports(),
proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)).
-encode_iolist(Type, Data, Version, ConnectionStates0) ->
- {ConnectionStates, EncodedMsg} =
- lists:foldl(fun(Text, {CS0, Encoded}) ->
- {Enc, CS1} =
- encode_plain_text(Type, Version, Text, CS0),
- {CS1, [Enc | Encoded]}
- end, {ConnectionStates0, []}, Data),
- {lists:reverse(EncodedMsg), ConnectionStates}.
-
-%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
-%% not vulnerable to this attack.
-split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when
- BCA =/= ?RC4 andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
- do_split_bin(Rest, ChunkSize, [[FirstByte]]);
-%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
-%% splitting.
-split_bin(Bin, ChunkSize, Version, BCA, zero_n) when
- BCA =/= ?RC4 andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
- do_split_bin(Bin, ChunkSize, [[<<>>]]);
-split_bin(Bin, ChunkSize, _, _, _) ->
- do_split_bin(Bin, ChunkSize, []).
-
-do_split_bin(<<>>, _, Acc) ->
- lists:reverse(Acc);
-do_split_bin(Bin, ChunkSize, Acc) ->
- case Bin of
- <<Chunk:ChunkSize/binary, Rest/binary>> ->
- do_split_bin(Rest, ChunkSize, [Chunk | Acc]);
- _ ->
- lists:reverse(Acc, [Bin])
- end.
-
-%%--------------------------------------------------------------------
--spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states(), boolean()) ->
- {#ssl_tls{}, ssl_record:connection_states()}| #alert{}.
-%%
-%% Description: Decode cipher text
-%%--------------------------------------------------------------------
-decode_cipher_text(#ssl_tls{type = Type, version = Version,
- fragment = CipherFragment} = CipherText,
- #{current_read :=
- #{compression_state := CompressionS0,
- sequence_number := Seq,
- security_parameters :=
- #security_parameters{
- cipher_type = ?AEAD,
- compression_algorithm = CompAlg}
- } = ReadState0} = ConnnectionStates0, _) ->
- AAD = ssl_cipher:calc_aad(Type, Version, ReadState0),
- case ssl_record:decipher_aead(Version, CipherFragment, ReadState0, AAD) of
- {PlainFragment, ReadState1} ->
- {Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
- PlainFragment, CompressionS0),
- ConnnectionStates = ConnnectionStates0#{
- current_read => ReadState1#{sequence_number => Seq + 1,
- compression_state => CompressionS1}},
- {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
- #alert{} = Alert ->
- Alert
- end;
-decode_cipher_text(#ssl_tls{type = Type, version = Version,
- fragment = CipherFragment} = CipherText,
- #{current_read :=
- #{compression_state := CompressionS0,
- sequence_number := Seq,
- security_parameters :=
- #security_parameters{compression_algorithm = CompAlg}
- } = ReadState0} = ConnnectionStates0, PaddingCheck) ->
- case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of
- {PlainFragment, Mac, ReadState1} ->
- MacHash = ssl_cipher:calc_mac_hash(Type, Version, PlainFragment, ReadState1),
- case ssl_record:is_correct_mac(Mac, MacHash) of
- true ->
- {Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
- PlainFragment, CompressionS0),
- ConnnectionStates = ConnnectionStates0#{
- current_read => ReadState1#{
- sequence_number => Seq + 1,
- compression_state => CompressionS1}},
- {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
- end;
- #alert{} = Alert ->
- Alert
- end.
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
new file mode 100644
index 0000000000..d713062284
--- /dev/null
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -0,0 +1,320 @@
+%%
+%% %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%
+
+-module(tls_record_1_3).
+
+-include("tls_record.hrl").
+-include("tls_record_1_3.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_cipher.hrl").
+
+%% Encoding
+-export([encode_handshake/2, encode_alert_record/2,
+ encode_data/2]).
+-export([encode_plain_text/3]).
+
+%% Decoding
+-export([decode_cipher_text/2]).
+
+%%====================================================================
+%% Encoding
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec encode_handshake(iolist(), ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%
+%% Description: Encodes a handshake message to send on the tls-1.3-socket.
+%%--------------------------------------------------------------------
+encode_handshake(Frag, ConnectionStates) ->
+ case iolist_size(Frag) of
+ N when N > ?MAX_PLAIN_TEXT_LENGTH ->
+ %% TODO: Consider padding here
+ Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH),
+ encode_iolist(?HANDSHAKE, Data, ConnectionStates);
+ _ ->
+ encode_plain_text(?HANDSHAKE, Frag, ConnectionStates)
+ end.
+
+%%--------------------------------------------------------------------
+-spec encode_alert_record(#alert{}, ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%%
+%% Description: Encodes an alert message to send on the ssl-socket.
+%%--------------------------------------------------------------------
+encode_alert_record(#alert{level = Level, description = Description},
+ ConnectionStates) ->
+ encode_plain_text(?ALERT, <<?BYTE(Level), ?BYTE(Description)>>,
+ ConnectionStates).
+%%--------------------------------------------------------------------
+-spec encode_data(binary(), ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%%
+%% Description: Encodes data to send on the ssl-socket.
+%%--------------------------------------------------------------------
+encode_data(Frag, ConnectionStates) ->
+ Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, {3,4}),
+ encode_iolist(?APPLICATION_DATA, Data, ConnectionStates).
+
+encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) ->
+ PadLen = 0, %% TODO where to specify PadLen?
+ Data = inner_plaintext(Type, Data0, PadLen),
+ CipherFragment = encode_plain_text(Data, Write0),
+ {CipherText, Write} = encode_tls_cipher_text(CipherFragment, Write0),
+ {CipherText, ConnectionStates#{current_write => Write}}.
+
+encode_iolist(Type, Data, ConnectionStates0) ->
+ {ConnectionStates, EncodedMsg} =
+ lists:foldl(fun(Text, {CS0, Encoded}) ->
+ {Enc, CS1} =
+ encode_plain_text(Type, Text, CS0),
+ {CS1, [Enc | Encoded]}
+ end, {ConnectionStates0, []}, Data),
+ {lists:reverse(EncodedMsg), ConnectionStates}.
+
+%%====================================================================
+%% Decoding
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states()) ->
+ {#ssl_tls{}, 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.
+%%--------------------------------------------------------------------
+decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
+ version = ?LEGACY_VERSION,
+ fragment = CipherFragment},
+ #{current_read :=
+ #{sequence_number := Seq,
+ cipher_state := #cipher_state{key = Key,
+ iv = IV,
+ tag_len = TagLen},
+ security_parameters :=
+ #security_parameters{
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm =
+ BulkCipherAlgo}
+ } = ReadState0} = ConnectionStates0) ->
+ case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of
+ #alert{} = Alert ->
+ Alert;
+ PlainFragment ->
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{sequence_number => Seq + 1}},
+ {decode_inner_plaintext(PlainFragment), ConnectionStates}
+ end;
+
+
+%% RFC8446 - TLS 1.3 (OpenSSL compatibility)
+%% Handle unencrypted Alerts from openssl s_client when server's
+%% connection states are already stepped into traffic encryption.
+%% (E.g. openssl s_client receives a CertificateRequest with
+%% a signature_algorithms_cert extension that does not contain
+%% the signature algorithm of the client's certificate.)
+decode_cipher_text(#ssl_tls{type = ?ALERT,
+ version = ?LEGACY_VERSION,
+ fragment = <<2,47>>},
+ ConnectionStates0) ->
+ {#ssl_tls{type = ?ALERT,
+ version = {3,4}, %% Internally use real version
+ fragment = <<2,47>>}, ConnectionStates0};
+%% TLS 1.3 server can receive a User Cancelled Alert when handshake is
+%% paused and then cancelled on the client side.
+decode_cipher_text(#ssl_tls{type = ?ALERT,
+ version = ?LEGACY_VERSION,
+ fragment = <<2,90>>},
+ ConnectionStates0) ->
+ {#ssl_tls{type = ?ALERT,
+ version = {3,4}, %% Internally use real version
+ fragment = <<2,90>>}, ConnectionStates0};
+%% RFC8446 - TLS 1.3
+%% 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.
+decode_cipher_text(#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
+ version = ?LEGACY_VERSION,
+ fragment = <<1>>},
+ ConnectionStates0) ->
+ {#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
+ version = {3,4}, %% Internally use real version
+ fragment = <<1>>}, ConnectionStates0};
+decode_cipher_text(#ssl_tls{type = Type,
+ version = ?LEGACY_VERSION,
+ fragment = CipherFragment},
+ #{current_read :=
+ #{security_parameters :=
+ #security_parameters{
+ cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
+ }} = ConnnectionStates0) ->
+ {#ssl_tls{type = Type,
+ version = {3,4}, %% Internally use real version
+ fragment = CipherFragment}, ConnnectionStates0};
+decode_cipher_text(#ssl_tls{type = Type}, _) ->
+ %% Version mismatch is already asserted
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_type_mismatch, Type}).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+split_bin(Bin, ChunkSize) ->
+ split_bin(Bin, ChunkSize, []).
+split_bin(Bin, ChunkSize, _) ->
+ do_split_bin(Bin, ChunkSize, []).
+
+do_split_bin(<<>>, _, Acc) ->
+ lists:reverse(Acc);
+do_split_bin(Bin, ChunkSize, Acc) ->
+ case Bin of
+ <<Chunk:ChunkSize/binary, Rest/binary>> ->
+ do_split_bin(Rest, ChunkSize, [Chunk | Acc]);
+ _ ->
+ lists:reverse(Acc, [Bin])
+ end.
+
+inner_plaintext(Type, Data, Length) ->
+ #inner_plaintext{
+ content = Data,
+ type = Type,
+ zeros = zero_padding(Length)
+ }.
+zero_padding(Length)->
+ binary:copy(<<?BYTE(0)>>, Length).
+
+encode_plain_text(#inner_plaintext{
+ content = Data,
+ type = Type,
+ zeros = Zeros
+ }, #{cipher_state := #cipher_state{key= Key,
+ iv = IV,
+ tag_len = TagLen},
+ sequence_number := Seq,
+ security_parameters :=
+ #security_parameters{
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo}
+ }) ->
+ PlainText = [Data, Type, Zeros],
+ Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen),
+ #tls_cipher_text{opaque_type = 23, %% 23 (application_data) for outward compatibility
+ legacy_version = {3,3},
+ encoded_record = Encoded};
+encode_plain_text(#inner_plaintext{
+ content = Data,
+ type = Type
+ }, #{security_parameters :=
+ #security_parameters{
+ cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
+ }) ->
+ %% RFC8446 - 5.1. Record Layer
+ %% When record protection has not yet been engaged, TLSPlaintext
+ %% structures are written directly onto the wire.
+ #tls_cipher_text{opaque_type = Type,
+ legacy_version = {3,3},
+ encoded_record = Data};
+
+encode_plain_text(_, CS) ->
+ exit({cs, CS}).
+
+additional_data(Length) ->
+ <<?BYTE(?OPAQUE_TYPE), ?BYTE(3), ?BYTE(3),?UINT16(Length)>>.
+
+%% The per-record nonce for the AEAD construction is formed as
+%% follows:
+%%
+%% 1. The 64-bit record sequence number is encoded in network byte
+%% order and padded to the left with zeros to iv_length.
+%%
+%% 2. The padded sequence number is XORed with either the static
+%% client_write_iv or server_write_iv (depending on the role).
+%%
+%% The resulting quantity (of length iv_length) is used as the
+%% per-record nonce.
+nonce(Seq, IV) ->
+ Padding = binary:copy(<<0>>, byte_size(IV) - 8),
+ crypto:exor(<<Padding/binary,?UINT64(Seq)>>, IV).
+
+cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
+ AAD = additional_data(erlang:iolist_size(Fragment) + TagLen),
+ Nonce = nonce(Seq, IV),
+ {Content, CipherTag} =
+ ssl_cipher:aead_encrypt(BulkCipherAlgo, Key, Nonce, Fragment, AAD, TagLen),
+ <<Content/binary, CipherTag/binary>>.
+
+encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type,
+ legacy_version = {MajVer, MinVer},
+ encoded_record = Encoded}, #{sequence_number := Seq} = Write) ->
+ Length = erlang:iolist_size(Encoded),
+ {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded],
+ Write#{sequence_number => Seq +1}}.
+
+decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
+ try
+ AAD = additional_data(erlang:iolist_size(CipherFragment)),
+ Nonce = nonce(Seq, IV),
+ {CipherText, CipherTag} = aead_ciphertext_split(CipherFragment, TagLen),
+ case ssl_cipher:aead_decrypt(BulkCipherAlgo, Key, Nonce, CipherText, CipherTag, AAD) of
+ Content when is_binary(Content) ->
+ Content;
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
+ end
+ catch
+ _:_ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
+ end.
+
+
+aead_ciphertext_split(CipherTextFragment, TagLen)
+ when is_binary(CipherTextFragment) ->
+ CipherLen = erlang:byte_size(CipherTextFragment) - TagLen,
+ <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> = CipherTextFragment,
+ {CipherText, CipherTag};
+aead_ciphertext_split(CipherTextFragment, TagLen)
+ when is_list(CipherTextFragment) ->
+ CipherLen = erlang:iolist_size(CipherTextFragment) - TagLen,
+ <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> =
+ erlang:iolist_to_binary(CipherTextFragment),
+ {CipherText, CipherTag}.
+
+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 ->
+ #ssl_tls{type = Type,
+ version = {3,4}, %% Internally use real version
+ fragment = init_binary(PlainText)};
+ _Else ->
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert)
+ end.
+
+init_binary(B) ->
+ {Init, _} =
+ split_binary(B, byte_size(B) - 1),
+ Init.
diff --git a/lib/ssl/src/tls_record_1_3.hrl b/lib/ssl/src/tls_record_1_3.hrl
new file mode 100644
index 0000000000..273427a34e
--- /dev/null
+++ b/lib/ssl/src/tls_record_1_3.hrl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-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: Record and constant defenitions for the TLS-1.3-record protocol
+%% see RFC 8446 not present in earlier versions
+%%----------------------------------------------------------------------
+
+-ifndef(tls_record_1_3).
+-define(tls_record_1_3, true).
+
+%% enum {
+%% invalid(0),
+%% %% defined in ssl_record.hrl
+%% change_cipher_spec(20),
+%% alert(21),
+%% handshake(22),
+%% application_data(23),
+%% heartbeat(24), /* RFC 6520 */
+%% (255)
+%% } ContentType;
+
+-define(INVALID, 0).
+-define(LEGACY_VERSION, {3,3}).
+-define(OPAQUE_TYPE, 23).
+
+-record(inner_plaintext, {
+ content, %% data
+ type, %% Contentype
+ zeros %% padding "uint8 zeros[length_of_padding]"
+ }).
+-record(tls_cipher_text, { %% Equivalent of encrypted version of #ssl_tls from previous versions
+ %% decrypted version will still use #ssl_tls for code reuse purposes
+ %% with real values for content type and version
+ opaque_type = ?OPAQUE_TYPE,
+ legacy_version = ?LEGACY_VERSION,
+ encoded_record
+ }).
+
+-endif. % -ifdef(tls_record_1_3).
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
new file mode 100644
index 0000000000..d0604565e3
--- /dev/null
+++ b/lib/ssl/src/tls_sender.erl
@@ -0,0 +1,524 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. 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_sender).
+
+-behaviour(gen_statem).
+
+-include("ssl_internal.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_api.hrl").
+
+%% API
+-export([start/0, start/1, initialize/2, send_data/2, send_alert/2,
+ send_and_ack_alert/2, setopts/2, renegotiate/1, peer_renegotiate/1, downgrade/2,
+ update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]).
+
+%% gen_statem callbacks
+-export([callback_mode/0, init/1, terminate/3, code_change/4]).
+-export([init/3, connection/3, handshake/3, death_row/3]).
+
+-define(SERVER, ?MODULE).
+
+-record(static,
+ {connection_pid,
+ role,
+ socket,
+ socket_options,
+ tracker,
+ transport_cb,
+ negotiated_version,
+ renegotiate_at,
+ connection_monitor,
+ dist_handle,
+ log_level
+ }).
+
+-record(data,
+ {static = #static{},
+ connection_states = #{}
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%--------------------------------------------------------------------
+-spec start() -> {ok, Pid :: pid()} |
+ ignore |
+ {error, Error :: term()}.
+-spec start(list()) -> {ok, Pid :: pid()} |
+ ignore |
+ {error, Error :: term()}.
+
+%% Description: Start sender process to avoid dead lock that
+%% may happen when a socket is busy (busy port) and the
+%% same process is sending and receiving
+%%--------------------------------------------------------------------
+start() ->
+ gen_statem:start(?MODULE, [], []).
+start(SpawnOpts) ->
+ gen_statem:start(?MODULE, [], SpawnOpts).
+
+%%--------------------------------------------------------------------
+-spec initialize(pid(), map()) -> ok.
+%% Description: So TLS connection process can initialize it sender
+%% process.
+%%--------------------------------------------------------------------
+initialize(Pid, InitMsg) ->
+ gen_statem:call(Pid, {self(), InitMsg}).
+
+%%--------------------------------------------------------------------
+-spec send_data(pid(), iodata()) -> ok | {error, term()}.
+%% Description: Send application data
+%%--------------------------------------------------------------------
+send_data(Pid, AppData) ->
+ %% Needs error handling for external API
+ call(Pid, {application_data, AppData}).
+
+%%--------------------------------------------------------------------
+-spec send_alert(pid(), #alert{}) -> _.
+%% Description: TLS connection process wants to send an Alert
+%% in the connection state.
+%%--------------------------------------------------------------------
+send_alert(Pid, Alert) ->
+ gen_statem:cast(Pid, Alert).
+
+%%--------------------------------------------------------------------
+-spec send_and_ack_alert(pid(), #alert{}) -> _.
+%% Description: TLS connection process wants to send an Alert
+%% in the connection state and recive an ack.
+%%--------------------------------------------------------------------
+send_and_ack_alert(Pid, Alert) ->
+ gen_statem:call(Pid, {ack_alert, Alert}, ?DEFAULT_TIMEOUT).
+%%--------------------------------------------------------------------
+-spec setopts(pid(), [{packet, integer() | atom()}]) -> ok | {error, term()}.
+%% Description: Send application data
+%%--------------------------------------------------------------------
+setopts(Pid, Opts) ->
+ call(Pid, {set_opts, Opts}).
+
+%%--------------------------------------------------------------------
+-spec renegotiate(pid()) -> {ok, WriteState::map()} | {error, closed}.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when handshaking.
+%%--------------------------------------------------------------------
+renegotiate(Pid) ->
+ %% Needs error handling for external API
+ call(Pid, renegotiate).
+
+%%--------------------------------------------------------------------
+-spec peer_renegotiate(pid()) -> {ok, WriteState::map()} | {error, term()}.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when handshaking.
+%%--------------------------------------------------------------------
+peer_renegotiate(Pid) ->
+ gen_statem:call(Pid, renegotiate, ?DEFAULT_TIMEOUT).
+
+%%--------------------------------------------------------------------
+-spec update_connection_state(pid(), WriteState::map(), tls_record:tls_version()) -> ok.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when sending application data.
+%%--------------------------------------------------------------------
+update_connection_state(Pid, NewState, Version) ->
+ gen_statem:cast(Pid, {new_write, NewState, Version}).
+
+%%--------------------------------------------------------------------
+-spec downgrade(pid(), integer()) -> {ok, ssl_record:connection_state()}
+ | {error, timeout}.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when sending application data.
+%%--------------------------------------------------------------------
+downgrade(Pid, Timeout) ->
+ try gen_statem:call(Pid, downgrade, Timeout) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ {error, timeout}
+ end.
+%%--------------------------------------------------------------------
+-spec dist_handshake_complete(pid(), node(), term()) -> ok.
+%% Description: Erlang distribution callback
+%%--------------------------------------------------------------------
+dist_handshake_complete(ConnectionPid, Node, DHandle) ->
+ gen_statem:call(ConnectionPid, {dist_handshake_complete, Node, DHandle}).
+%%--------------------------------------------------------------------
+-spec dist_tls_socket(pid()) -> {ok, #sslsocket{}}.
+%% Description: To enable distribution startup to get a proper "#sslsocket{}"
+%%--------------------------------------------------------------------
+dist_tls_socket(Pid) ->
+ gen_statem:call(Pid, dist_get_tls_socket).
+
+%%%===================================================================
+%%% gen_statem callbacks
+%%%===================================================================
+%%--------------------------------------------------------------------
+-spec callback_mode() -> gen_statem:callback_mode_result().
+%%--------------------------------------------------------------------
+callback_mode() ->
+ state_functions.
+
+
+-define(HANDLE_COMMON,
+ ?FUNCTION_NAME(Type, Msg, StateData) ->
+ handle_common(Type, Msg, StateData)).
+%%--------------------------------------------------------------------
+-spec init(Args :: term()) ->
+ gen_statem:init_result(atom()).
+%%--------------------------------------------------------------------
+init(_) ->
+ %% Note: Should not trap exits so that this process
+ %% will be terminated if tls_connection process is
+ %% killed brutally
+ {ok, init, #data{}}.
+
+%%--------------------------------------------------------------------
+-spec init(gen_statem:event_type(),
+ Msg :: term(),
+ StateData :: term()) ->
+ gen_statem:event_handler_result(atom()).
+%%--------------------------------------------------------------------
+init({call, From}, {Pid, #{current_write := WriteState,
+ role := Role,
+ socket := Socket,
+ socket_options := SockOpts,
+ tracker := Tracker,
+ transport_cb := Transport,
+ negotiated_version := Version,
+ renegotiate_at := RenegotiateAt,
+ log_level := LogLevel}},
+ #data{connection_states = ConnectionStates, static = Static0} = StateData0) ->
+ Monitor = erlang:monitor(process, Pid),
+ StateData =
+ StateData0#data{connection_states = ConnectionStates#{current_write => WriteState},
+ static = Static0#static{connection_pid = Pid,
+ connection_monitor = Monitor,
+ role = Role,
+ socket = Socket,
+ socket_options = SockOpts,
+ tracker = Tracker,
+ transport_cb = Transport,
+ negotiated_version = Version,
+ renegotiate_at = RenegotiateAt,
+ log_level = LogLevel}},
+ {next_state, handshake, StateData, [{reply, From, ok}]};
+init(_, _, _) ->
+ %% Just in case anything else sneeks through
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(),
+ Msg :: term(),
+ StateData :: term()) ->
+ gen_statem:event_handler_result(atom()).
+%%--------------------------------------------------------------------
+connection({call, From}, {application_data, AppData},
+ #data{static = #static{socket_options = #socket_options{packet = Packet}}} =
+ StateData) ->
+ case encode_packet(Packet, AppData) of
+ {error, _} = Error ->
+ {next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]};
+ Data ->
+ send_application_data(Data, From, ?FUNCTION_NAME, StateData)
+ end;
+connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) ->
+ StateData = send_tls_alert(Alert, StateData0),
+ {next_state, ?FUNCTION_NAME, StateData,
+ [{reply,From,ok}]};
+connection({call, From}, renegotiate,
+ #data{connection_states = #{current_write := Write}} = StateData) ->
+ {next_state, handshake, StateData, [{reply, From, {ok, Write}}]};
+connection({call, From}, downgrade, #data{connection_states =
+ #{current_write := Write}} = StateData) ->
+ {next_state, death_row, StateData, [{reply,From, {ok, Write}}]};
+connection({call, From}, {set_opts, Opts}, StateData) ->
+ handle_set_opts(From, Opts, StateData);
+connection({call, From}, dist_get_tls_socket,
+ #data{static = #static{transport_cb = Transport,
+ socket = Socket,
+ connection_pid = Pid,
+ tracker = Tracker}} = StateData) ->
+ TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Tracker),
+ {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) ->
+ ok = erlang:dist_ctrl_input_handler(DHandle, Pid),
+ ok = ssl_connection: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}},
+ [{reply,From,ok}|
+ case dist_data(DHandle) of
+ [] ->
+ [];
+ Data ->
+ [{next_event, internal,
+ {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}]
+ end]};
+connection(internal, {application_packets, From, Data}, StateData) ->
+ send_application_data(Data, From, ?FUNCTION_NAME, StateData);
+%%
+connection(cast, #alert{} = Alert, StateData0) ->
+ StateData = send_tls_alert(Alert, StateData0),
+ {next_state, ?FUNCTION_NAME, StateData};
+connection(cast, {new_write, WritesState, Version},
+ #data{connection_states = ConnectionStates, static = Static} = StateData) ->
+ {next_state, connection,
+ StateData#data{connection_states =
+ ConnectionStates#{current_write => WritesState},
+ static = Static#static{negotiated_version = Version}}};
+%%
+connection(info, dist_data, #data{static = #static{dist_handle = DHandle}}) ->
+ {keep_state_and_data,
+ case dist_data(DHandle) of
+ [] ->
+ [];
+ Data ->
+ [{next_event, internal,
+ {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}]
+ end};
+connection(info, tick, StateData) ->
+ consume_ticks(),
+ Data = [<<0:32>>], % encode_packet(4, <<>>)
+ From = {self(), undefined},
+ send_application_data(Data, From, ?FUNCTION_NAME, StateData);
+connection(info, {send, From, Ref, Data}, _StateData) ->
+ %% This is for testing only!
+ %%
+ %% Needed by some OTP distribution
+ %% test suites...
+ From ! {Ref, ok},
+ {keep_state_and_data,
+ [{next_event, {call, {self(), undefined}},
+ {application_data, erlang:iolist_to_iovec(Data)}}]};
+?HANDLE_COMMON.
+
+%%--------------------------------------------------------------------
+-spec handshake(gen_statem:event_type(),
+ Msg :: term(),
+ StateData :: term()) ->
+ gen_statem:event_handler_result(atom()).
+%%--------------------------------------------------------------------
+handshake({call, From}, {set_opts, Opts}, StateData) ->
+ handle_set_opts(From, Opts, StateData);
+handshake({call, _}, _, _) ->
+ %% Postpone all calls to the connection state
+ {keep_state_and_data, [postpone]};
+handshake(internal, {application_packets,_,_}, _) ->
+ {keep_state_and_data, [postpone]};
+handshake(cast, {new_write, WritesState, Version},
+ #data{connection_states = ConnectionStates, static = Static} = StateData) ->
+ {next_state, connection,
+ StateData#data{connection_states = ConnectionStates#{current_write => WritesState},
+ static = Static#static{negotiated_version = Version}}};
+handshake(info, dist_data, _) ->
+ {keep_state_and_data, [postpone]};
+handshake(info, tick, _) ->
+ %% Ignore - data is sent anyway during handshake
+ consume_ticks(),
+ keep_state_and_data;
+handshake(info, {send, _, _, _}, _) ->
+ %% Testing only, OTP distribution test suites...
+ {keep_state_and_data, [postpone]};
+?HANDLE_COMMON.
+
+%%--------------------------------------------------------------------
+-spec death_row(gen_statem:event_type(),
+ Msg :: term(),
+ StateData :: term()) ->
+ gen_statem:event_handler_result(atom()).
+%%--------------------------------------------------------------------
+death_row(state_timeout, Reason, _State) ->
+ {stop, {shutdown, Reason}};
+death_row(_Type, _Msg, _State) ->
+ %% Waste all other events
+ keep_state_and_data.
+
+%%--------------------------------------------------------------------
+-spec terminate(Reason :: term(), State :: term(), Data :: term()) ->
+ any().
+%%--------------------------------------------------------------------
+terminate(_Reason, _State, _Data) ->
+ void.
+
+%%--------------------------------------------------------------------
+-spec code_change(
+ OldVsn :: term() | {down,term()},
+ State :: term(), Data :: term(), Extra :: term()) ->
+ {ok, NewState :: term(), NewData :: term()} |
+ (Reason :: term()).
+%% Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, Data, _Extra) ->
+ {ok, State, Data}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+handle_set_opts(
+ From, Opts, #data{static = #static{socket_options = SockOpts} = Static} = StateData) ->
+ {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}},
+ [{reply, From, ok}]}.
+
+handle_common(
+ {call, From}, {set_opts, Opts},
+ #data{static = #static{socket_options = SockOpts} = Static} = StateData) ->
+ {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}},
+ [{reply, From, ok}]};
+handle_common(
+ info, {'DOWN', Monitor, _, _, Reason},
+ #data{static = #static{connection_monitor = Monitor,
+ dist_handle = Handle}} = StateData) when Handle =/= undefined ->
+ {next_state, death_row, StateData,
+ [{state_timeout, 5000, Reason}]};
+handle_common(
+ info, {'DOWN', Monitor, _, _, _},
+ #data{static = #static{connection_monitor = Monitor}} = StateData) ->
+ {stop, normal, StateData};
+handle_common(info, Msg, _) ->
+ Report =
+ io_lib:format("TLS sender: Got unexpected info: ~p ~n", [Msg]),
+ error_logger:info_report(Report),
+ keep_state_and_data;
+handle_common(Type, Msg, _) ->
+ Report =
+ io_lib:format(
+ "TLS sender: Got unexpected event: ~p ~n", [{Type,Msg}]),
+ error_logger:error_report(Report),
+ keep_state_and_data.
+
+send_tls_alert(#alert{} = Alert,
+ #data{static = #static{negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport,
+ log_level = LogLevel},
+ connection_states = ConnectionStates0} = StateData0) ->
+ {BinMsg, ConnectionStates} =
+ tls_record:encode_alert_record(Alert, Version, ConnectionStates0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+ StateData0#data{connection_states = ConnectionStates}.
+
+send_application_data(Data, From, StateName,
+ #data{static = #static{connection_pid = Pid,
+ socket = Socket,
+ dist_handle = DistHandle,
+ negotiated_version = Version,
+ transport_cb = Transport,
+ renegotiate_at = RenegotiateAt,
+ log_level = LogLevel},
+ connection_states = ConnectionStates0} = StateData0) ->
+ case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
+ true ->
+ ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
+ {next_state, handshake, StateData0,
+ [{next_event, internal, {application_packets, From, Data}}]};
+ false ->
+ {Msgs, ConnectionStates} = tls_record:encode_data(Data, Version, ConnectionStates0),
+ StateData = StateData0#data{connection_states = ConnectionStates},
+ case tls_socket:send(Transport, Socket, Msgs) of
+ ok when DistHandle =/= undefined ->
+ ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
+ {next_state, StateName, StateData, []};
+ Reason when DistHandle =/= undefined ->
+ {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]};
+ ok ->
+ ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
+ {next_state, StateName, StateData, [{reply, From, ok}]};
+ Result ->
+ {next_state, StateName, StateData, [{reply, From, Result}]}
+ end
+ end.
+
+-compile({inline, encode_packet/2}).
+encode_packet(Packet, Data) ->
+ Len = iolist_size(Data),
+ case Packet of
+ 1 when Len < (1 bsl 8) -> [<<Len:8>>|Data];
+ 2 when Len < (1 bsl 16) -> [<<Len:16>>|Data];
+ 4 when Len < (1 bsl 32) -> [<<Len:32>>|Data];
+ N when N =:= 1; N =:= 2; N =:= 4 ->
+ {error,
+ {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}};
+ _ ->
+ Data
+ end.
+
+set_opts(SocketOptions, [{packet, N}]) ->
+ SocketOptions#socket_options{packet = N}.
+
+time_to_renegotiate(_Data,
+ #{current_write := #{sequence_number := Num}},
+ RenegotiateAt) ->
+
+ %% We could do test:
+ %% is_time_to_renegotiate((erlang:byte_size(_Data) div
+ %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to
+ %% have a some what lower renegotiateAt and a much cheaper test
+ is_time_to_renegotiate(Num, RenegotiateAt).
+
+is_time_to_renegotiate(N, M) when N < M->
+ false;
+is_time_to_renegotiate(_,_) ->
+ true.
+
+call(FsmPid, Event) ->
+ try gen_statem:call(FsmPid, Event)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{{shutdown, _},_} ->
+ {error, closed}
+ end.
+
+%%-------------- Erlang distribution helpers ------------------------------
+
+dist_data(DHandle) ->
+ case erlang:dist_ctrl_get_data(DHandle) of
+ none ->
+ erlang:dist_ctrl_get_data_notification(DHandle),
+ [];
+ %% This is encode_packet(4, Data) without Len check
+ %% 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)]
+ end.
+
+
+%% Empty the inbox from distribution ticks - do not let them accumulate
+consume_ticks() ->
+ receive tick ->
+ consume_ticks()
+ after 0 ->
+ ok
+ end.
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index e76d9c100a..6c32e6fa04 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1998-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.
@@ -27,11 +27,12 @@
-export([send/3, listen/3, accept/3, socket/5, connect/4, upgrade/3,
setopts/3, getopts/3, getstat/3, peername/2, sockname/2, port/2]).
-export([split_options/1, get_socket_opts/3]).
--export([emulated_options/0, internal_inet_values/0, default_inet_values/0,
+-export([emulated_options/0, emulated_options/1, internal_inet_values/0, default_inet_values/0,
init/1, start_link/3, terminate/2, inherit_tracker/3,
emulated_socket_options/2, get_emulated_opts/1,
set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3]).
+-export([update_active_n/2]).
-record(state, {
emulated_opts,
@@ -45,18 +46,20 @@
send(Transport, Socket, Data) ->
Transport:send(Socket, Data).
-listen(Transport, Port, #config{transport_info = {Transport, _, _, _},
+listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _},
inet_user = Options,
ssl = SslOpts, emulated = EmOpts} = Config) ->
case Transport:listen(Port, Options ++ internal_inet_values()) of
{ok, ListenSocket} ->
{ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts),
- {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}};
+ Socket = #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}},
+ check_active_n(EmOpts, Socket),
+ {ok, Socket};
Err = {error, _} ->
Err
end.
-accept(ListenSocket, #config{transport_info = {Transport,_,_,_} = CbInfo,
+accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo,
connection_cb = ConnectionCb,
ssl = SslOpts,
emulated = Tracker}, Timeout) ->
@@ -64,11 +67,12 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_} = CbInfo,
{ok, Socket} ->
{ok, EmOpts} = get_emulated_opts(Tracker),
{ok, Port} = tls_socket:port(Transport, Socket),
- ConnArgs = [server, "localhost", Port, Socket,
+ {ok, Sender} = tls_sender:start(),
+ ConnArgs = [server, Sender, "localhost", Port, Socket,
{SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Tracker}, self(), CbInfo],
case tls_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
- ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport, Tracker);
+ ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Tracker);
{error, Reason} ->
{error, Reason}
end;
@@ -76,7 +80,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_} = CbInfo,
{error, Reason}
end.
-upgrade(Socket, #config{transport_info = {Transport,_,_,_}= CbInfo,
+upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo,
ssl = SslOptions,
emulated = EmOpts, connection_cb = ConnectionCb}, Timeout) ->
ok = setopts(Transport, Socket, tls_socket:internal_inet_values()),
@@ -94,7 +98,7 @@ connect(Address, Port,
#config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts,
emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb},
Timeout) ->
- {Transport, _, _, _} = CbInfo,
+ {Transport, _, _, _, _} = CbInfo,
try Transport:connect(Address, Port, SocketOpts, Timeout) of
{ok, Socket} ->
ssl_connection:connect(ConnetionCb, Address, Port, Socket,
@@ -112,18 +116,20 @@ connect(Address, Port,
{error, {options, {socket_options, UserOpts}}}
end.
-socket(Pid, Transport, Socket, ConnectionCb, Tracker) ->
- #sslsocket{pid = Pid,
+socket(Pids, Transport, Socket, ConnectionCb, Tracker) ->
+ #sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
fd = {Transport, Socket, ConnectionCb, Tracker}}.
-setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
+setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
+ check_active_n(EmulatedOpts, Socket),
inet:setopts(ListenSocket, SockOpts);
-setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_},
+setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_,_},
emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
+ check_active_n(EmulatedOpts, Socket),
Transport:setopts(ListenSocket, SockOpts);
%%% Following clauses will not be called for emulated options, they are handled in the connection process
setopts(gen_tcp, Socket, Options) ->
@@ -131,6 +137,31 @@ setopts(gen_tcp, Socket, Options) ->
setopts(Transport, Socket, Options) ->
Transport:setopts(Socket, Options).
+check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{emulated = Tracker}}}) ->
+ %% We check the resulting options to send an ssl_passive message if necessary.
+ case proplists:lookup(active, EmulatedOpts) of
+ %% The provided value is out of bound.
+ {_, N} when is_integer(N), N < -32768 ->
+ throw(einval);
+ {_, N} when is_integer(N), N > 32767 ->
+ throw(einval);
+ {_, N} when is_integer(N) ->
+ case get_emulated_opts(Tracker, [active]) of
+ [{_, false}] ->
+ self() ! {ssl_passive, Socket},
+ ok;
+ %% The result of the addition is out of bound.
+ [{_, A}] when is_integer(A), A < -32768 ->
+ throw(einval);
+ [{_, A}] when is_integer(A), A > 32767 ->
+ throw(einval);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
{SockOptNames, EmulatedOptNames} = split_options(Options),
EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames),
@@ -170,6 +201,9 @@ port(Transport, Socket) ->
emulated_options() ->
[mode, packet, active, header, packet_size].
+emulated_options(Opts) ->
+ emulated_options(Opts, internal_inet_values(), default_inet_values()).
+
internal_inet_values() ->
[{packet_size,0}, {packet, 0}, {header, 0}, {active, false}, {mode,binary}].
@@ -205,7 +239,7 @@ start_link(Port, SockOpts, SslOpts) ->
init([Port, Opts, SslOpts]) ->
process_flag(trap_exit, true),
true = link(Port),
- {ok, #state{emulated_opts = Opts, port = Port, ssl_opts = SslOpts}}.
+ {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}.
%%--------------------------------------------------------------------
-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
@@ -300,9 +334,24 @@ split_options([Name | Opts], Emu, SocketOptNames, EmuOptNames) ->
do_set_emulated_opts([], Opts) ->
Opts;
+do_set_emulated_opts([{active, N0} | Rest], Opts) when is_integer(N0) ->
+ N = update_active_n(N0, proplists:get_value(active, Opts, false)),
+ do_set_emulated_opts(Rest, [{active, N} | proplists:delete(active, Opts)]);
do_set_emulated_opts([{Name,_} = Opt | Rest], Opts) ->
do_set_emulated_opts(Rest, [Opt | proplists:delete(Name, Opts)]).
+update_active_n(New, Current) ->
+ if
+ is_integer(Current), New + Current =< 0 ->
+ false;
+ is_integer(Current) ->
+ New + Current;
+ New =< 0 ->
+ false;
+ true ->
+ New
+ end.
+
get_socket_opts(_, [], _) ->
[];
get_socket_opts(ListenSocket, SockOptNames, Cb) ->
@@ -328,3 +377,44 @@ emulated_socket_options(InetValues, #socket_options{
packet = proplists:get_value(packet, InetValues, Packet),
packet_size = proplists:get_value(packet_size, InetValues, Size)
}.
+
+emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) ->
+ validate_inet_option(mode, Value),
+ emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]);
+emulated_options([{header, Value} = Opt | Opts], Inet, Emulated) ->
+ validate_inet_option(header, Value),
+ emulated_options(Opts, Inet, [Opt | proplists:delete(header, Emulated)]);
+emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) ->
+ validate_inet_option(active, Value),
+ emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]);
+emulated_options([{packet, Value} = Opt |Opts], Inet, Emulated) ->
+ validate_inet_option(packet, Value),
+ emulated_options(Opts, Inet, [Opt | proplists:delete(packet, Emulated)]);
+emulated_options([{packet_size, Value} = Opt | Opts], Inet, Emulated) ->
+ validate_inet_option(packet_size, Value),
+ emulated_options(Opts, Inet, [Opt | proplists:delete(packet_size, Emulated)]);
+emulated_options([Opt|Opts], Inet, Emulated) ->
+ emulated_options(Opts, [Opt|Inet], Emulated);
+emulated_options([], Inet,Emulated) ->
+ {Inet, Emulated}.
+
+validate_inet_option(mode, Value)
+ when Value =/= list, Value =/= binary ->
+ throw({error, {options, {mode,Value}}});
+validate_inet_option(packet, Value)
+ when not (is_atom(Value) orelse is_integer(Value)) ->
+ throw({error, {options, {packet,Value}}});
+validate_inet_option(packet_size, Value)
+ when not is_integer(Value) ->
+ throw({error, {options, {packet_size,Value}}});
+validate_inet_option(header, Value)
+ when not is_integer(Value) ->
+ throw({error, {options, {header,Value}}});
+validate_inet_option(active, Value)
+ when Value >= -32768, Value =< 32767 ->
+ ok;
+validate_inet_option(active, Value)
+ when Value =/= true, Value =/= false, Value =/= once ->
+ throw({error, {options, {active,Value}}});
+validate_inet_option(_, _) ->
+ ok.
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 6797b290d4..f7c8c770ae 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% 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.
@@ -29,10 +29,22 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
--export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7,
+-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_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]).
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
@@ -41,12 +53,68 @@
sect193r1 | sect193r2 | secp192k1 | secp192r1 | sect163k1 |
sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2.
-type curves() :: [named_curve()].
--export_type([curves/0, named_curve/0]).
+-type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
+ ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192.
+-type supported_groups() :: [group()].
+-export_type([curves/0, named_curve/0, group/0, supported_groups/0]).
%%====================================================================
%% Internal application API
%%====================================================================
+%% TLS 1.3 ---------------------------------------------------
+-spec derive_secret(Secret::binary(), Label::binary(),
+ Messages::iodata(), Algo::ssl:hash()) -> Key::binary().
+derive_secret(Secret, Label, Messages, Algo) ->
+ Hash = crypto:hash(mac_algo(Algo), Messages),
+ hkdf_expand_label(Secret, Label,
+ Hash, ssl_cipher:hash_size(Algo), Algo).
+
+-spec hkdf_expand_label(Secret::binary(), Label0::binary(),
+ Context::binary(), Length::integer(),
+ Algo::ssl:hash()) -> KeyingMaterial::binary().
+hkdf_expand_label(Secret, Label0, Context, Length, Algo) ->
+ HkdfLabel = create_info(Label0, Context, Length),
+ hkdf_expand(Secret, HkdfLabel, Length, Algo).
+
+%% Create info parameter for HKDF-Expand:
+%% HKDF-Expand(PRK, info, L) -> OKM
+create_info(Label0, Context0, Length) ->
+ %% struct {
+ %% uint16 length = Length;
+ %% opaque label<7..255> = "tls13 " + Label;
+ %% opaque context<0..255> = Context;
+ %% } HkdfLabel;
+ Label1 = << <<"tls13 ">>/binary, Label0/binary>>,
+ LabelLen = size(Label1),
+ Label = <<?BYTE(LabelLen), Label1/binary>>,
+ ContextLen = size(Context0),
+ Context = <<?BYTE(ContextLen),Context0/binary>>,
+ Content = <<Label/binary, Context/binary>>,
+ <<?UINT16(Length), Content/binary>>.
+
+-spec hkdf_extract(MacAlg::ssl:hash(), Salt::binary(),
+ KeyingMaterial::binary()) -> PseudoRandKey::binary().
+
+hkdf_extract(MacAlg, Salt, KeyingMaterial) ->
+ hmac_hash(MacAlg, Salt, KeyingMaterial).
+
+
+-spec hkdf_expand(PseudoRandKey::binary(), ContextInfo::binary(),
+ Length::integer(), Algo::ssl:hash()) -> KeyingMaterial::binary().
+
+hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) ->
+ Iterations = erlang:ceil(Length / ssl_cipher:hash_size(Algo)),
+ hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, 1, Iterations, <<>>, <<>>).
+
+
+-spec transcript_hash(Messages::iodata(), Algo::ssl:hash()) -> Hash::binary().
+
+transcript_hash(Messages, Algo) ->
+ crypto:hash(mac_algo(Algo), Messages).
+%% TLS 1.3 ---------------------------------------------------
+
+%% TLS 1.0 -1.2 ---------------------------------------------------
-spec master_secret(integer(), binary(), binary(), binary()) -> binary().
master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) ->
@@ -56,9 +124,10 @@ master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) ->
prf(PrfAlgo, PreMasterSecret, <<"master secret">>,
[ClientRandom, ServerRandom], 48).
+%% TLS 1.0 -1.2 ---------------------------------------------------
-spec finished(client | server, integer(), integer(), binary(), [binary()]) -> binary().
-
+%% TLS 1.0 -1.1 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
when Version == 1; Version == 2; PrfAlgo == ?MD5SHA ->
%% RFC 2246 & 4346 - 7.4.9. Finished
@@ -72,7 +141,9 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
prf(?MD5SHA, MasterSecret, finished_label(Role), [MD5, SHA], 12);
+%% TLS 1.0 -1.1 ---------------------------------------------------
+%% TLS 1.2 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
when Version == 3 ->
%% RFC 5246 - 7.4.9. Finished
@@ -84,21 +155,28 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
%% PRF(master_secret, finished_label, Hash(handshake_messages)) [0..11];
Hash = crypto:hash(mac_algo(PrfAlgo), Handshake),
prf(PrfAlgo, MasterSecret, finished_label(Role), Hash, 12).
+%% TLS 1.2 ---------------------------------------------------
+
+%% TODO 1.3 finished
-spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary().
+%% TLS 1.0 -1.1 ---------------------------------------------------
certificate_verify(md5sha, _Version, Handshake) ->
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
<<MD5/binary, SHA/binary>>;
+%% TLS 1.0 -1.1 ---------------------------------------------------
+%% TLS 1.2 ---------------------------------------------------
certificate_verify(HashAlgo, _Version, Handshake) ->
crypto:hash(HashAlgo, Handshake).
+%% TLS 1.2 ---------------------------------------------------
-spec setup_keys(integer(), integer(), binary(), binary(), binary(), integer(),
integer(), integer()) -> {binary(), binary(), binary(),
binary(), binary(), binary()}.
-
+%% TLS v1.0 ---------------------------------------------------
setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
when Version == 1 ->
@@ -123,8 +201,9 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV};
+%% TLS v1.0 ---------------------------------------------------
-%% TLS v1.1
+%% TLS v1.1 ---------------------------------------------------
setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
when Version == 2 ->
@@ -150,11 +229,12 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV};
+%% TLS v1.1 ---------------------------------------------------
-%% TLS v1.2
+%% TLS v1.2 ---------------------------------------------------
setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
- when Version == 3 ->
+ when Version == 3; Version == 4 ->
%% RFC 5246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -176,8 +256,177 @@ setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV}.
+%% TLS v1.2 ---------------------------------------------------
--spec mac_hash(integer(), binary(), integer(), integer(), tls_record:tls_version(),
+%% TLS v1.3 ---------------------------------------------------
+%% RFC 8446 - 7.1. Key Schedule
+%%
+%% 0
+%% |
+%% v
+%% PSK -> HKDF-Extract = Early Secret
+%% |
+%% +-----> Derive-Secret(., "ext binder" | "res binder", "")
+%% | = binder_key
+%% |
+%% +-----> Derive-Secret(., "c e traffic", ClientHello)
+%% | = client_early_traffic_secret
+%% |
+%% +-----> Derive-Secret(., "e exp master", ClientHello)
+%% | = early_exporter_master_secret
+%% v
+%% Derive-Secret(., "derived", "")
+%% |
+%% v
+%% (EC)DHE -> HKDF-Extract = Handshake Secret
+%% |
+%% +-----> Derive-Secret(., "c hs traffic",
+%% | ClientHello...ServerHello)
+%% | = client_handshake_traffic_secret
+%% |
+%% +-----> Derive-Secret(., "s hs traffic",
+%% | ClientHello...ServerHello)
+%% | = server_handshake_traffic_secret
+%% v
+%% Derive-Secret(., "derived", "")
+%% |
+%% v
+%% 0 -> HKDF-Extract = Master Secret
+%% |
+%% +-----> Derive-Secret(., "c ap traffic",
+%% | ClientHello...server Finished)
+%% | = client_application_traffic_secret_0
+%% |
+%% +-----> Derive-Secret(., "s ap traffic",
+%% | ClientHello...server Finished)
+%% | = server_application_traffic_secret_0
+%% |
+%% +-----> Derive-Secret(., "exp master",
+%% | ClientHello...server Finished)
+%% | = exporter_master_secret
+%% |
+%% +-----> Derive-Secret(., "res master",
+%% ClientHello...client Finished)
+%% = resumption_master_secret
+-spec key_schedule(early_secret | handshake_secret | master_secret,
+ atom(), {psk | early_secret | handshake_secret, binary()}) ->
+ {early_secret | handshake_secret | master_secret, binary()}.
+
+key_schedule(early_secret, Algo, {psk, PSK}) ->
+ Len = ssl_cipher:hash_size(Algo),
+ Salt = binary:copy(<<?BYTE(0)>>, Len),
+ {early_secret, hkdf_extract(Algo, Salt, PSK)};
+key_schedule(master_secret, Algo, {handshake_secret, Secret}) ->
+ Len = ssl_cipher:hash_size(Algo),
+ IKM = binary:copy(<<?BYTE(0)>>, Len),
+ Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
+ {master_secret, hkdf_extract(Algo, Salt, IKM)}.
+%%
+key_schedule(handshake_secret, Algo, IKM, {early_secret, Secret}) ->
+ Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
+ {handshake_secret, hkdf_extract(Algo, Salt, IKM)}.
+
+-spec external_binder_key(atom(), {early_secret, binary()}) -> binary().
+external_binder_key(Algo, {early_secret, Secret}) ->
+ derive_secret(Secret, <<"ext binder">>, <<>>, Algo).
+
+-spec resumption_binder_key(atom(), {early_secret, binary()}) -> binary().
+resumption_binder_key(Algo, {early_secret, Secret}) ->
+ derive_secret(Secret, <<"res binder">>, <<>>, Algo).
+
+-spec client_early_traffic_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello
+client_early_traffic_secret(Algo, {early_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c e traffic">>, M, Algo).
+
+-spec early_exporter_master_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello
+early_exporter_master_secret(Algo, {early_secret, Secret}, M) ->
+ derive_secret(Secret, <<"e exp master">>, M, Algo).
+
+-spec client_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...ServerHello
+client_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c hs traffic">>, M, Algo).
+
+-spec server_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...ServerHello
+server_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
+ derive_secret(Secret, <<"s hs traffic">>, M, Algo).
+
+-spec client_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+client_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c ap traffic">>, M, Algo).
+
+-spec server_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+server_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"s ap traffic">>, M, Algo).
+
+-spec exporter_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+exporter_master_secret(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"exp master">>, M, Algo).
+
+-spec resumption_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...client Finished
+resumption_master_secret(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"res master">>, M, Algo).
+
+-spec finished_key(binary(), atom()) -> binary().
+finished_key(BaseKey, Algo) ->
+ %% finished_key =
+ %% HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
+ ssl_cipher:hash_size(Algo),
+ hkdf_expand_label(BaseKey, <<"finished">>, <<>>, ssl_cipher:hash_size(Algo), Algo).
+
+-spec finished_verify_data(binary(), atom(), iodata()) -> binary().
+finished_verify_data(FinishedKey, HKDFAlgo, Messages) ->
+ %% The verify_data value is computed as follows:
+ %%
+ %% verify_data =
+ %% HMAC(finished_key,
+ %% Transcript-Hash(Handshake Context,
+ %% Certificate*, CertificateVerify*))
+ Context = lists:reverse(Messages),
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+ tls_v1:hmac_hash(HKDFAlgo, FinishedKey, THash).
+
+%% The next-generation application_traffic_secret is computed as:
+%%
+%% application_traffic_secret_N+1 =
+%% HKDF-Expand-Label(application_traffic_secret_N,
+%% "traffic upd", "", Hash.length)
+-spec update_traffic_secret(atom(), binary()) -> binary().
+update_traffic_secret(Algo, Secret) ->
+ hkdf_expand_label(Secret, <<"traffic upd">>, <<>>, ssl_cipher:hash_size(Algo), Algo).
+
+%% The traffic keying material is generated from the following input
+%% values:
+%%
+%% - A secret value
+%%
+%% - A purpose value indicating the specific value being generated
+%%
+%% - The length of the key being generated
+%%
+%% The traffic keying material is generated from an input traffic secret
+%% value using:
+%%
+%% [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),
+ %% TODO: remove hard coded IV size
+ IV = hkdf_expand_label(Secret, <<"iv">>, <<>>, 12, HKDFAlgo),
+ {Key, IV}.
+
+%% TLS v1.3 ---------------------------------------------------
+
+%% TLS 1.0 -1.2 ---------------------------------------------------
+-spec mac_hash(integer() | atom(), binary(), integer(), integer(), tls_record:tls_version(),
integer(), binary()) -> binary().
mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
@@ -191,8 +440,11 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
Fragment]),
Mac.
+%% TLS 1.0 -1.2 ---------------------------------------------------
--spec suites(1|2|3) -> [ssl_cipher:cipher_suite()].
+%% TODO 1.3 same as above?
+
+-spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
[
@@ -202,30 +454,16 @@ suites(Minor) when Minor == 1; Minor == 2 ->
?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_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,
- ?TLS_RSA_WITH_AES_128_CBC_SHA,
-
- ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_WITH_3DES_EDE_CBC_SHA
+ ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
];
suites(3) ->
- [
- ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
-
- ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ [?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
@@ -234,13 +472,10 @@ suites(3) ->
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_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_RSA_WITH_AES_256_GCM_SHA384,
- ?TLS_RSA_WITH_AES_256_CBC_SHA256,
?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
@@ -254,19 +489,36 @@ suites(3) ->
?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
- ?TLS_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_RSA_WITH_AES_128_CBC_SHA256
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
%% not supported
%% ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
%% ?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(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
+ ].
+
+
+signature_algs({3, 4}, HashSigns) ->
+ signature_algs({3, 3}, HashSigns);
signature_algs({3, 3}, HashSigns) ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
@@ -294,6 +546,10 @@ signature_algs({3, 3}, HashSigns) ->
end, [], HashSigns),
lists:reverse(Supported).
+default_signature_algs({3, 4} = Version) ->
+ %% TLS 1.3 servers shall be prepared to process TLS 1.2 ClientHellos
+ %% containing legacy hash-sign tuples.
+ default_signature_schemes(Version) ++ default_signature_algs({3,3});
default_signature_algs({3, 3} = Version) ->
Default = [%% SHA2
{sha512, ecdsa},
@@ -312,15 +568,117 @@ default_signature_algs({3, 3} = Version) ->
default_signature_algs(_) ->
undefined.
+
+signature_schemes(Version, SignatureSchemes) when is_tuple(Version)
+ andalso Version >= {3, 3} ->
+ CryptoSupports = crypto:supports(),
+ Hashes = proplists:get_value(hashs, CryptoSupports),
+ PubKeys = proplists:get_value(public_keys, CryptoSupports),
+ Curves = proplists:get_value(curves, CryptoSupports),
+ RSAPSSSupported = lists:member(rsa_pkcs1_pss_padding,
+ proplists:get_value(rsa_opts, CryptoSupports)),
+ Fun = fun (Scheme, Acc) when is_atom(Scheme) ->
+ {Hash0, Sign0, Curve} =
+ ssl_cipher:scheme_to_components(Scheme),
+ Sign = case Sign0 of
+ rsa_pkcs1 ->
+ rsa;
+ rsa_pss_rsae when RSAPSSSupported ->
+ rsa;
+ rsa_pss_pss when RSAPSSSupported ->
+ rsa;
+ S -> S
+ end,
+ Hash = case Hash0 of
+ sha1 ->
+ sha;
+ H -> H
+ end,
+ case proplists:get_bool(Sign, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso (Curve =:= undefined orelse
+ proplists:get_bool(Curve, Curves))
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Scheme | Acc];
+ false ->
+ Acc
+ end;
+ %% Special clause for filtering out the legacy hash-sign tuples.
+ ({Hash, dsa = Sign} = Alg, Acc) ->
+ case proplists:get_bool(dss, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Alg | Acc];
+ false ->
+ Acc
+ end;
+ ({Hash, Sign} = Alg, Acc) ->
+ case proplists:get_bool(Sign, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Alg | Acc];
+ false ->
+ Acc
+ end
+ end,
+ Supported = lists:foldl(Fun, [], SignatureSchemes),
+ lists:reverse(Supported);
+signature_schemes(_, _) ->
+ [].
+
+default_signature_schemes(Version) ->
+ Default = [
+ ecdsa_secp521r1_sha512,
+ ecdsa_secp384r1_sha384,
+ ecdsa_secp256r1_sha256,
+ rsa_pss_pss_sha512,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha256,
+ rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256,
+ %% ed25519,
+ %% ed448,
+
+ %% These values refer solely to signatures
+ %% which appear in certificates (see Section 4.4.2.2) and are not
+ %% defined for use in signed TLS handshake messages, although they
+ %% MAY appear in "signature_algorithms" and
+ %% "signature_algorithms_cert" for backward compatibility with
+ %% TLS 1.2.
+ rsa_pkcs1_sha512,
+ rsa_pkcs1_sha384,
+ rsa_pkcs1_sha256,
+ ecdsa_sha1,
+ rsa_pkcs1_sha1
+ ],
+ signature_schemes(Version, Default).
+
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, N, N, Prev, Acc) ->
+ Keyingmaterial = hmac_hash(Algo, PseudoRandKey, <<Prev/binary, ContextInfo/binary, ?BYTE(N)>>),
+ binary:part(<<Acc/binary, Keyingmaterial/binary>>, {0, Length});
+hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M, N, Prev, Acc) ->
+ Keyingmaterial = hmac_hash(Algo, PseudoRandKey, <<Prev/binary, ContextInfo/binary, ?BYTE(M)>>),
+ hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M + 1, N, Keyingmaterial, <<Acc/binary, Keyingmaterial/binary>>).
+
%%%% HMAC and the Pseudorandom Functions RFC 2246 & 4346 - 5.%%%%
hmac_hash(?NULL, _, _) ->
<<>>;
hmac_hash(Alg, Key, Value) ->
crypto:hmac(mac_algo(Alg), Key, Value).
+mac_algo(Alg) when is_atom(Alg) ->
+ Alg;
mac_algo(?MD5) -> md5;
mac_algo(?SHA) -> sha;
mac_algo(?SHA256) -> sha256;
@@ -407,7 +765,7 @@ is_pair(Hash, rsa, Hashs) ->
AtLeastMd5 = Hashs -- [md2,md4],
lists:member(Hash, AtLeastMd5).
-%% list ECC curves in prefered order
+%% list ECC curves in preferred order
-spec ecc_curves(1..3 | all) -> [named_curve()].
ecc_curves(all) ->
[sect571r1,sect571k1,secp521r1,brainpoolP512r1,
@@ -416,6 +774,7 @@ ecc_curves(all) ->
sect239k1,sect233k1,sect233r1,secp224k1,secp224r1,
sect193r1,sect193r2,secp192k1,secp192r1,sect163k1,
sect163r1,sect163r2,secp160k1,secp160r1,secp160r2];
+
ecc_curves(Minor) ->
TLSCurves = ecc_curves(all),
ecc_curves(Minor, TLSCurves).
@@ -430,6 +789,63 @@ ecc_curves(_Minor, TLSCurves) ->
end
end, [], TLSCurves).
+-spec groups(4 | all | default) -> [group()].
+groups(all) ->
+ [x25519,
+ x448,
+ secp256r1,
+ secp384r1,
+ secp521r1,
+ ffdhe2048,
+ ffdhe3072,
+ ffdhe4096,
+ ffdhe6144,
+ ffdhe8192];
+groups(default) ->
+ [x25519,
+ x448,
+ secp256r1,
+ secp384r1];
+groups(Minor) ->
+ TLSGroups = groups(all),
+ groups(Minor, TLSGroups).
+%%
+-spec groups(4, [group()]) -> [group()].
+groups(_Minor, TLSGroups) ->
+ CryptoGroups = supported_groups(),
+ lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups).
+
+default_groups(Minor) ->
+ TLSGroups = groups(default),
+ groups(Minor, TLSGroups).
+
+supported_groups() ->
+ %% TODO: Add new function to crypto?
+ proplists:get_value(curves, crypto:supports()) ++
+ [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192].
+
+group_to_enum(secp256r1) -> 23;
+group_to_enum(secp384r1) -> 24;
+group_to_enum(secp521r1) -> 25;
+group_to_enum(x25519) -> 29;
+group_to_enum(x448) -> 30;
+group_to_enum(ffdhe2048) -> 256;
+group_to_enum(ffdhe3072) -> 257;
+group_to_enum(ffdhe4096) -> 258;
+group_to_enum(ffdhe6144) -> 259;
+group_to_enum(ffdhe8192) -> 260.
+
+enum_to_group(23) -> secp256r1;
+enum_to_group(24) -> secp384r1;
+enum_to_group(25) -> secp521r1;
+enum_to_group(29) -> x25519;
+enum_to_group(30) -> x448;
+enum_to_group(256) -> ffdhe2048;
+enum_to_group(257) -> ffdhe3072;
+enum_to_group(258) -> ffdhe4096;
+enum_to_group(259) -> ffdhe6144;
+enum_to_group(260) -> ffdhe8192;
+enum_to_group(_) -> undefined.
%% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005)
oid_to_enum(?sect163k1) -> 1;