aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/Makefile136
-rw-r--r--lib/ssl/src/dtls_connection.erl19
-rw-r--r--lib/ssl/src/dtls_handshake.erl19
-rw-r--r--lib/ssl/src/dtls_handshake.hrl7
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl5
-rw-r--r--lib/ssl/src/inet_tls_dist.erl23
-rw-r--r--lib/ssl/src/ssl.app.src6
-rw-r--r--lib/ssl/src/ssl.erl243
-rw-r--r--lib/ssl/src/ssl_alert.erl4
-rw-r--r--lib/ssl/src/ssl_alert.hrl5
-rw-r--r--lib/ssl/src/ssl_app.erl17
-rw-r--r--lib/ssl/src/ssl_certificate.erl2
-rw-r--r--lib/ssl/src/ssl_cipher.erl183
-rw-r--r--lib/ssl/src/ssl_cipher.hrl18
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl89
-rw-r--r--lib/ssl/src/ssl_connection.erl141
-rw-r--r--lib/ssl/src/ssl_connection.hrl63
-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_handshake.erl1014
-rw-r--r--lib/ssl/src/ssl_handshake.hrl86
-rw-r--r--lib/ssl/src/ssl_internal.hrl48
-rw-r--r--lib/ssl/src/ssl_logger.erl420
-rw-r--r--lib/ssl/src/ssl_manager.erl6
-rw-r--r--lib/ssl/src/ssl_pkix_db.erl3
-rw-r--r--lib/ssl/src/ssl_record.erl25
-rw-r--r--lib/ssl/src/ssl_record.hrl3
-rw-r--r--lib/ssl/src/tls_connection.erl380
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl189
-rw-r--r--lib/ssl/src/tls_handshake.erl184
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl886
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl238
-rw-r--r--lib/ssl/src/tls_record.erl133
-rw-r--r--lib/ssl/src/tls_record_1_3.erl298
-rw-r--r--lib/ssl/src/tls_record_1_3.hrl58
-rw-r--r--lib/ssl/src/tls_sender.erl18
-rw-r--r--lib/ssl/src/tls_v1.erl441
37 files changed, 5244 insertions, 638 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 8d1341f594..8dc76f2638 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -39,60 +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 \
- 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_packet_demux \
+ dtls_handshake \
dtls_listener_sup \
- ssl_dist_sup\
- ssl_dist_admin_sup\
- ssl_dist_connection_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_cipher_format \
- ssl_srp_primes \
- tls_connection \
- dtls_connection \
- tls_sender\
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_session \
+ ssl_session_cache \
+ ssl_srp_primes \
+ ssl_sup \
ssl_v3 \
- tls_v1 \
- dtls_v1
+ 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) \
@@ -111,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
# ----------------------------------------------------
@@ -118,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)
# ----------------------------------------------------
@@ -127,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);' $< > $@
@@ -141,7 +176,6 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
docs:
-
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -159,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_connection.erl b/lib/ssl/src/dtls_connection.erl
index 2c6b71c97a..6b5a311efc 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -32,6 +32,7 @@
-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
@@ -350,8 +351,8 @@ reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag},
dtls_handshake_later_fragments = []
}}.
-select_sni_extension(#client_hello{extensions = HelloExtensions}) ->
- HelloExtensions#hello_extensions.sni;
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
select_sni_extension(_) ->
undefined.
@@ -537,14 +538,14 @@ hello(internal, #client_hello{extensions = Extensions} = Hello,
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, ssl_connection:map_extensions(Extensions)}}]};
+ [{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, ssl_connection:map_extensions(Extensions)}}]};
+ [{reply, From, {ok, Extensions}}]};
hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server,
transport_cb = Transport,
@@ -963,7 +964,7 @@ handle_own_alert(Alert, Version, StateName, #state{static_env = #static_env{data
ssl_options = Options} = State0) ->
case ignore_alert(Alert, State0) of
{true, State} ->
- log_ignore_alert(Options#ssl_options.log_alert, StateName, Alert, Role),
+ 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)
@@ -1160,11 +1161,11 @@ is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) ->
is_ignore_alert(_) ->
false.
-log_ignore_alert(true, StateName, Alert, Role) ->
+log_ignore_alert(debug, StateName, Alert, Role) ->
Txt = ssl_alert:alert_txt(Alert),
- error_logger:format("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n",
- [Role, StateName, Txt]);
-log_ignore_alert(false, _, _,_) ->
+ ?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,
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 6e9bf99e52..8e749e65b8 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -79,7 +79,7 @@ client_hello(Host, Port, Cookie, ConnectionStates,
Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites,
SslOpts, ConnectionStates,
- Renegotiation),
+ Renegotiation, undefined),
Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
#client_hello{session_id = Id,
@@ -169,10 +169,7 @@ handle_client_hello(Version,
cipher_suites = CipherSuites,
compression_methods = Compressions,
random = Random,
- extensions =
- #hello_extensions{elliptic_curves = Curves,
- signature_algs = ClientHashSigns}
- = HelloExt},
+ extensions = HelloExt},
#ssl_options{versions = Versions,
signature_algs = SupportedHashSigns,
eccs = SupportedECCs,
@@ -181,6 +178,8 @@ handle_client_hello(Version,
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),
@@ -195,7 +194,7 @@ handle_client_hello(Version,
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_definition(CipherSuite),
- case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg,
+ case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, Cert, KeyExAlg,
SupportedHashSigns, TLSVersion) of
#alert{} = Alert ->
Alert;
@@ -332,7 +331,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,
@@ -340,8 +339,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},
diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl
index 41da8e5c8c..dab4038762 100644
--- a/lib/ssl/src/dtls_handshake.hrl
+++ b/lib/ssl/src/dtls_handshake.hrl
@@ -57,4 +57,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_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index dccc22a448..afcd4af000 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -24,6 +24,7 @@
-behaviour(gen_server).
-include("ssl_internal.hrl").
+-include_lib("kernel/include/logger.hrl").
%% API
-export([start_link/5, active_once/3, accept/2, sockname/1, close/1,
@@ -146,11 +147,11 @@ handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket
%% 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]),
- error_logger:info_report(Report),
+ ?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]),
- error_logger:info_report(Report),
+ ?LOG_NOTICE(Report),
{noreply, State#state{close=true}};
handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients,
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 5cab35fd4b..e7fab7ebc5 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -41,6 +41,7 @@
-include_lib("public_key/include/public_key.hrl").
-include("ssl_api.hrl").
+-include_lib("kernel/include/logger.hrl").
%% -------------------------------------------------------------------------
@@ -225,7 +226,7 @@ accept_loop(Driver, Listen, Kernel) ->
true ->
accept_loop(Driver, Listen, Kernel, Socket);
{false,IP} ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Connection attempt from "
"disallowed IP ~w ** ~n", [IP]),
?shutdown2(no_node, trace({disallowed, IP}))
@@ -260,7 +261,7 @@ accept_loop(Driver, Listen, Kernel, Socket) ->
{error, {options, _}} = Error ->
%% Bad options: that's probably our fault.
%% Let's log that.
- error_logger:error_msg(
+ ?LOG_ERROR(
"Cannot accept TLS distribution connection: ~s~n",
[ssl:format_error(Error)]),
gen_tcp:close(Socket),
@@ -436,7 +437,7 @@ allowed_nodes(SslSocket, Allowed) ->
PeerCert, allowed_hosts(Allowed), PeerIP)
of
[] ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Connection attempt from "
"disallowed node(s) ~p ** ~n", [PeerIP]),
?shutdown2(
@@ -695,12 +696,12 @@ split_node(Driver, Node, LongOrShortNames) ->
{node, Name, Host} ->
check_node(Driver, Node, Name, Host, LongOrShortNames);
{host, _} ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Nodename ~p illegal, no '@' character **~n",
[Node]),
?shutdown2(Node, trace({illegal_node_n@me, Node}));
_ ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Nodename ~p illegal **~n", [Node]),
?shutdown2(Node, trace({illegal_node_name, Node}))
end.
@@ -712,7 +713,7 @@ check_node(Driver, Node, Name, Host, LongOrShortNames) ->
{ok, _} ->
{Name, Host};
_ ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** System running to use "
"fully qualified hostnames **~n"
"** Hostname ~s is illegal **~n",
@@ -720,7 +721,7 @@ check_node(Driver, Node, Name, Host, LongOrShortNames) ->
?shutdown2(Node, trace({not_longnames, Host}))
end;
[_,_|_] when LongOrShortNames =:= shortnames ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** System NOT running to use "
"fully qualified hostnames **~n"
"** Hostname ~s is illegal **~n",
@@ -850,13 +851,13 @@ monitor_pid(Pid) ->
%% MRef = erlang:monitor(process, Pid),
%% receive
%% {'DOWN', MRef, _, _, normal} ->
- %% error_logger:error_report(
- %% [dist_proc_died,
+ %% ?LOG_ERROR(
+ %% [{slogan, dist_proc_died},
%% {reason, normal},
%% {pid, Pid}]);
%% {'DOWN', MRef, _, _, Reason} ->
- %% error_logger:info_report(
- %% [dist_proc_died,
+ %% ?LOG_NOTICE(
+ %% [{slogan, dist_proc_died},
%% {reason, Reason},
%% {pid, Pid}])
%% end
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 936df12e70..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,
tls_connection_sup,
tls_sender,
+ ssl_dh_groups,
%% DTLS
dtls_connection,
dtls_handshake,
@@ -51,6 +55,8 @@
ssl_crl_cache,
ssl_crl_cache_api,
ssl_crl_hash_dir,
+ %% Logging
+ ssl_logger,
%% App structure
ssl_app,
ssl_sup,
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index bdb2283ad6..a50960c1a3 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -51,11 +51,12 @@
%% SSL/TLS protocol handling
-export([cipher_suites/0, cipher_suites/1, cipher_suites/2, filter_cipher_suites/2,
prepend_cipher_suites/2, append_cipher_suites/2,
- eccs/0, eccs/1, versions/0,
+ 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]).
+-export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1,
+ set_log_level/1]).
-deprecated({ssl_accept, 1, eventually}).
-deprecated({ssl_accept, 2, eventually}).
@@ -86,6 +87,7 @@
private_key_type/0,
cipher/0,
hash/0,
+ key/0,
key_algo/0,
sign_algo/0
]).
@@ -134,6 +136,22 @@
-type legacy_hash() :: md5.
-type sign_algo() :: rsa | dsa | ecdsa.
+
+-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.
+
-type key_algo() :: rsa |
dhe_rsa | dhe_dss |
ecdhe_ecdsa | ecdh_ecdsa | ecdh_rsa |
@@ -227,6 +245,7 @@
{password, key_password()} |
{ciphers, cipher_suites()} |
{eccs, eccs()} |
+ {signature_algs_cert, signature_schemes()} |
{secure_renegotiate, secure_renegotiation()} |
{depth, allowed_cert_chain_length()} |
{verify_fun, custom_verify()} |
@@ -236,6 +255,7 @@
{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()} |
@@ -270,13 +290,14 @@
-type root_fun() :: fun().
-type protocol_versions() :: [protocol_version()].
-type signature_algs() :: [{hash(), sign_algo()}].
+-type signature_schemes() :: [sign_scheme()].
-type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: term()}.
-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()} |
@@ -395,12 +416,12 @@ stop() ->
%%
%% Description: Connect to an ssl server.
%%--------------------------------------------------------------------
--spec connect(host() | port(), [client_option()]) -> {ok, #sslsocket{}} |
- {error, reason()}.
+-spec connect(host() | port(), [tls_client_option()]) -> {ok, #sslsocket{}} |
+ {error, reason()}.
connect(Socket, SslOptions) when is_port(Socket) ->
connect(Socket, SslOptions, infinity).
--spec connect(host() | port(), [client_option()] | inet:port_number(),
+-spec connect(host() | port(), [tls_client_option()] | inet:port_number(),
timeout() | list()) ->
{ok, #sslsocket{}} | {error, reason()}.
connect(Socket, SslOptions0, Timeout) when is_port(Socket),
@@ -419,8 +440,9 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket),
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
--spec connect(host() | port(), inet:port_number(), [client_option()], timeout()) ->
+-spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
{ok, #sslsocket{}} | {error, reason()}.
+
connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
try
{ok, Config} = handle_options(Options, client, Host),
@@ -509,6 +531,7 @@ ssl_accept(Socket, SslOptions, Timeout) ->
%% Performs the SSL/TLS/DTLS server-side handshake.
-spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}.
+
handshake(ListenSocket) ->
handshake(ListenSocket, infinity).
@@ -518,6 +541,12 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim
(Timeout == infinity) ->
ssl_connection:handshake(Socket, Timeout);
+%% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to
+%% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns
+%% 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).
@@ -783,9 +812,10 @@ cipher_suites(Base, Version) ->
[ssl_cipher_format:suite_definition(Suite) || Suite <- supported_suites(Base, Version)].
%%--------------------------------------------------------------------
--spec filter_cipher_suites([erl_cipher_suite()],
+-spec filter_cipher_suites([erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()] ,
[{key_exchange | cipher | mac | prf, fun()}] | []) ->
- [erl_cipher_suite()].
+ [erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
+
%% 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.
@@ -873,6 +903,20 @@ eccs_filter_supported(Curves) ->
Curves).
%%--------------------------------------------------------------------
+-spec groups() -> tls_v1:supported_groups().
+%% Description: returns all supported groups (TLS 1.3 and later)
+%%--------------------------------------------------------------------
+groups() ->
+ tls_v1:groups(4).
+
+%%--------------------------------------------------------------------
+-spec groups(default) -> tls_v1:supported_groups().
+%% Description: returns the default groups (TLS 1.3 and later)
+%%--------------------------------------------------------------------
+groups(default) ->
+ tls_v1:default_groups(4).
+
+%%--------------------------------------------------------------------
-spec getopts(#sslsocket{}, [gen_tcp:option_name()]) ->
{ok, [gen_tcp:option()]} | {error, reason()}.
%%
@@ -1126,6 +1170,32 @@ suite_to_str(Cipher) ->
ssl_cipher_format:suite_to_str(Cipher).
+%%--------------------------------------------------------------------
+-spec set_log_level(atom()) -> ok | {error, term()}.
+%%
+%% Description: Set log level for the SSL application
+%%--------------------------------------------------------------------
+set_log_level(Level) ->
+ case application:get_all_key(ssl) of
+ {ok, PropList} ->
+ Modules = proplists:get_value(modules, PropList),
+ set_module_level(Modules, Level);
+ undefined ->
+ {error, ssl_not_started}
+ end.
+
+set_module_level(Modules, Level) ->
+ Fun = fun (Module) ->
+ ok = logger:set_module_level(Module, Level)
+ end,
+ try lists:map(Fun, Modules) of
+ _ ->
+ ok
+ catch
+ error:{badmatch, Error} ->
+ Error
+ end.
+
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
@@ -1185,9 +1255,10 @@ 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
@@ -1203,12 +1274,14 @@ handle_options(Opts0, Role, Host) ->
CertFile = handle_option(certfile, 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),
@@ -1219,7 +1292,7 @@ handle_options(Opts0, Role, Host) ->
ok
end,
- SSLOptions = #ssl_options{
+ SSLOptions0 = #ssl_options{
versions = Versions,
verify = validate_option(verify, Verify),
verify_fun = VerifyFun,
@@ -1240,13 +1313,29 @@ handle_options(Opts0, Role, Host) ->
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))),
+ 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),
@@ -1265,7 +1354,6 @@ handle_options(Opts0, Role, Host) ->
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,
default_option_role(client,
server_name_indication_default(Host), Role)),
@@ -1291,6 +1379,10 @@ handle_options(Opts0, Role, Host) ->
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)),
SslOptions = [protocol, versions, verify, verify_fun, partial_chain,
@@ -1302,10 +1394,12 @@ handle_options(Opts0, Role, Host) ->
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, max_handshake_size, handshake, customize_hostname_check],
+ 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)
end, Opts, SslOptions),
@@ -1486,7 +1580,20 @@ validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols
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),
@@ -1558,19 +1665,42 @@ validate_option(customize_hostname_check, Value) when is_list(Value) ->
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
+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} ->
+ 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(_, Version) when Version >= {3, 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),
@@ -1614,7 +1744,8 @@ 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 ->
@@ -1627,10 +1758,11 @@ validate_versions([Ver| _], 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}}}}).
@@ -1736,6 +1868,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])).
@@ -1881,8 +2023,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) ->
@@ -1895,12 +2039,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) ->
@@ -1962,11 +2120,20 @@ 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};
default_cb_info(dtls) ->
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 2a20d13cd5..e17476f33b 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -165,6 +165,8 @@ description_txt(?USER_CANCELED) ->
"User Canceled";
description_txt(?NO_RENEGOTIATION) ->
"No Renegotiation";
+description_txt(?MISSING_EXTENSION) ->
+ "Missing extension";
description_txt(?UNSUPPORTED_EXTENSION) ->
"Unsupported Extension";
description_txt(?CERTIFICATE_UNOBTAINABLE) ->
@@ -179,6 +181,8 @@ description_txt(?UNKNOWN_PSK_IDENTITY) ->
"Unknown Psk Identity";
description_txt(?INAPPROPRIATE_FALLBACK) ->
"Inappropriate Fallback";
+description_txt(?CERTIFICATE_REQUIRED) ->
+ "Certificate required";
description_txt(?NO_APPLICATION_PROTOCOL) ->
"No application protocol";
description_txt(Enum) ->
diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl
index b23123905e..9b2322da17 100644
--- a/lib/ssl/src/ssl_alert.hrl
+++ b/lib/ssl/src/ssl_alert.hrl
@@ -29,6 +29,9 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Alert protocol - RFC 2246 section 7.2
+%%% updated by RFC 8486 with
+%%% missing_extension(109),
+%%% certificate_required(116),
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% AlertLevel
@@ -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}}).
diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl
index 62e8765d4a..2a5047c75c 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).
+
+%%
+%% Description: Stop SSL logger
+stop_logger() ->
+ logger:remove_handler(ssl_handler).
+
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 549e557beb..9997f5e0c8 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -71,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};
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index fce48d1678..6e751f9ceb 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -31,9 +31,10 @@
-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,
+-export([security_parameters/2, security_parameters/3, security_parameters_1_3/2,
cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/5, 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,
@@ -42,7 +43,12 @@
filter/3, filter_suites/1, filter_suites/2,
hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1,
random_bytes/1, calc_mac_hash/4, calc_mac_hash/6,
- is_stream_ciphersuite/1]).
+ is_stream_ciphersuite/1, signature_scheme/1,
+ scheme_to_components/1, hash_size/1, effective_key_bits/1,
+ key_material/1]).
+
+%% RFC 8446 TLS 1.3
+-export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]).
-compile(inline).
@@ -83,6 +89,15 @@ security_parameters(Version, CipherSuite, SecParams) ->
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_definition(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{}.
%%
@@ -158,7 +173,7 @@ 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 ->
+ when N == 2; N == 3; N == 4 ->
IV_Size = byte_size(IV),
<<NextIV:IV_Size/binary, IV_Cache/binary>> =
case IV_Cache0 of
@@ -294,6 +309,8 @@ anonymous_suites({3, N}) ->
srp_suites_anon() ++ 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(N)
when N >= 3 ->
psk_suites_anon(N) ++
@@ -328,6 +345,8 @@ anonymous_suites(N) when N == 0;
%%--------------------------------------------------------------------
psk_suites({3, N}) ->
psk_suites(N);
+psk_suites(4) ->
+ []; %% TODO Add new PSK, PSK_(EC)DHE suites
psk_suites(N)
when N >= 3 ->
[
@@ -418,11 +437,12 @@ rc4_suites({3, Minor}) ->
rc4_suites(0) ->
[?TLS_RSA_WITH_RC4_128_SHA,
?TLS_RSA_WITH_RC4_128_MD5];
-rc4_suites(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_ECDH_ECDSA_WITH_RC4_128_SHA,
?TLS_ECDH_RSA_WITH_RC4_128_SHA].
+
%%--------------------------------------------------------------------
-spec des_suites(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
@@ -457,7 +477,7 @@ rsa_suites(0) ->
?TLS_RSA_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA
];
-rsa_suites(N) when N =< 3 ->
+rsa_suites(N) when N =< 4 ->
[
?TLS_RSA_WITH_AES_256_GCM_SHA384,
?TLS_RSA_WITH_AES_256_CBC_SHA256,
@@ -556,7 +576,8 @@ crypto_support_filters() ->
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 ->
@@ -651,6 +672,28 @@ 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
%%--------------------------------------------------------------------
@@ -660,7 +703,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).
@@ -804,26 +847,58 @@ 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;
-%% 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.
-%% 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;
+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;
+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}.
%% RFC 5246: 6.2.3.2. CBC Block Cipher
%%
@@ -861,7 +936,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),
@@ -1159,3 +1234,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 2371e8bd32..00822ad9de 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -47,6 +47,7 @@
-record(cipher_state, {
iv,
key,
+ finished_key,
state,
nonce,
tag_len
@@ -611,4 +612,21 @@
%% TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x15}
-define(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#15)>>).
+%%% 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_AES_128_CCM_SHA256 = {0x13,0x04}
+%% -define(TLS_AES_128_CCM_SHA256, <<?BYTE(16#13), ?BYTE(16#04)>>).
+
+%% %% 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
index f7af96583f..f75daaad22 100644
--- a/lib/ssl/src/ssl_cipher_format.erl
+++ b/lib/ssl/src/ssl_cipher_format.erl
@@ -61,6 +61,12 @@ suite_to_str(#{key_exchange := null,
mac := null,
prf := null}) ->
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV";
+suite_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_to_str(#{key_exchange := Kex,
cipher := Cipher,
mac := aead,
@@ -801,7 +807,34 @@ suite_definition(?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_definition(?TLS_AES_128_GCM_SHA256) ->
+ #{key_exchange => any,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_definition(?TLS_AES_256_GCM_SHA384) ->
+ #{key_exchange => any,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_definition(?TLS_CHACHA20_POLY1305_SHA256) ->
+ #{key_exchange => any,
+ cipher => chacha20_poly1305,
+ mac => aead,
prf => sha256}.
+%% suite_definition(?TLS_AES_128_CCM_SHA256) ->
+%% #{key_exchange => any,
+%% cipher => aes_128_ccm,
+%% mac => aead,
+%% prf => sha256};
+%% suite_definition(?TLS_AES_128_CCM_8_SHA256) ->
+%% #{key_exchange => any,
+%% cipher => aes_128_ccm_8,
+%% mac => aead,
+%% prf => sha256}.
+
%%--------------------------------------------------------------------
-spec erl_suite_definition(cipher_suite() | internal_erl_cipher_suite()) -> old_erl_cipher_suite().
@@ -1426,8 +1459,33 @@ suite(#{key_exchange := dhe_rsa,
cipher := chacha20_poly1305,
mac := aead,
prf := sha256}) ->
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256.
-
+ ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+%% TLS 1.3 Cipher Suites RFC8446
+suite(#{key_exchange := any,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_AES_128_GCM_SHA256;
+suite(#{key_exchange := any,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_AES_256_GCM_SHA384;
+suite(#{key_exchange := any,
+ cipher := chacha20_poly1305,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_CHACHA20_POLY1305_SHA256.
+%% suite(#{key_exchange := any,
+%% cipher := aes_128_ccm,
+%% mac := aead,
+%% prf := sha256}) ->
+%% ?TLS_AES_128_CCM_SHA256;
+%% suite(#{key_exchange := any,
+%% cipher := aes_128_ccm_8,
+%% mac := aead,
+%% prf := sha256}) ->
+%% ?TLS_AES_128_CCM_8_SHA256.
%%--------------------------------------------------------------------
-spec openssl_suite(openssl_cipher_suite()) -> cipher_suite().
%%
@@ -1581,7 +1639,20 @@ openssl_suite("ECDHE-RSA-AES256-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.
+ ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384;
+
+%% TLS 1.3 Cipher Suites RFC8446
+openssl_suite("TLS_AES_128_GCM_SHA256") ->
+ ?TLS_AES_128_GCM_SHA256;
+openssl_suite("TLS_AES_256_GCM_SHA384") ->
+ ?TLS_AES_256_GCM_SHA384;
+openssl_suite("TLS_CHACHA20_POLY1305_SHA256") ->
+ ?TLS_CHACHA20_POLY1305_SHA256.
+%% openssl_suite("TLS_AES_128_CCM_SHA256") ->
+%% ?TLS_AES_128_CCM_SHA256;
+%% openssl_suite("TLS_AES_128_CCM_8_SHA256") ->
+%% ?TLS_AES_128_CCM_8_SHA256.
+
%%--------------------------------------------------------------------
-spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | internal_erl_cipher_suite().
@@ -1758,6 +1829,18 @@ openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) ->
openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) ->
"ECDH-RSA-AES256-GCM-SHA384";
+%% TLS 1.3 Cipher Suites RFC8446
+openssl_suite_name(?TLS_AES_128_GCM_SHA256) ->
+ "TLS_AES_128_GCM_SHA256";
+openssl_suite_name(?TLS_AES_256_GCM_SHA384) ->
+ "TLS_AES_256_GCM_SHA384";
+openssl_suite_name(?TLS_CHACHA20_POLY1305_SHA256) ->
+ "TLS_CHACHA20_POLY1305_SHA256";
+%% openssl_suite(?TLS_AES_128_CCM_SHA256) ->
+%% "TLS_AES_128_CCM_SHA256";
+%% openssl_suite(?TLS_AES_128_CCM_8_SHA256) ->
+%% "TLS_AES_128_CCM_8_SHA256";
+
%% No oppenssl name
openssl_suite_name(Cipher) ->
suite_definition(Cipher).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 9e037313bb..f194610d72 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -35,10 +35,11 @@
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
%% Setup
--export([connect/8, handshake/7, handshake/2, handshake/3,
+-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]).
@@ -59,7 +60,7 @@
%% Help functions for tls|dtls_connection.erl
-export([handle_session/7, ssl_config/3,
- prepare_connection/2, hibernate_after/3, map_extensions/1]).
+ 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
@@ -337,7 +338,9 @@ handle_own_alert(Alert, _, StateName,
ignore
end,
try %% Try to tell the local user
- log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = Role}),
+ log_alert(SslOpts#ssl_options.log_level, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = Role}),
handle_normal_shutdown(Alert,StateName, State)
catch _:_ ->
ok
@@ -379,7 +382,7 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName,
session = Session,
socket_options = Opts} = State) ->
invalidate_session(Role, Host, Port, Session),
- log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(),
+ log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(),
StateName, Alert#alert{role = opposite_role(Role)}),
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection),
@@ -397,7 +400,7 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
protocol_cb = Connection},
handshake_env = #handshake_env{renegotiation = {true, internal}},
ssl_options = SslOpts} = State) ->
- log_alert(SslOpts#ssl_options.log_alert, Role,
+ log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
handle_normal_shutdown(Alert, StateName, State),
{stop,{shutdown, peer_close}, State};
@@ -408,7 +411,7 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
ssl_options = SslOpts
} = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, Role,
+ 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),
@@ -419,8 +422,8 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
protocol_cb = Connection},
handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
ssl_options = SslOpts
- } = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, Role,
+ } = 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!
@@ -432,8 +435,9 @@ 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_alert, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ 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).
%%====================================================================
@@ -442,8 +446,7 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName,
passive_receive(State0 = #state{user_data_buffer = {_,BufferSize,_}}, StateName, Connection, StartTimerAction) ->
case BufferSize of
0 ->
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(StateName, Record, State, StartTimerAction);
+ Connection:next_event(StateName, no_record, State0, StartTimerAction);
_ ->
case read_application_data(<<>>, State0) of
{stop, _, _} = ShutdownError ->
@@ -1022,7 +1025,8 @@ certify(internal, #certificate_request{} = CertRequest,
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
+ case ssl_handshake:select_hashsign(CertRequest, Cert,
+ SupportedHashSigns, ssl:tls_version(Version)) of
#alert {} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
NegotiatedHashSign ->
@@ -1188,10 +1192,8 @@ cipher(internal, #finished{verify_data = Data} = Finished,
cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{expecting_finished = true,
- expecting_next_protocol_negotiation = true} = HsEnv} = State0, Connection) ->
- {Record, State} =
- Connection:next_record(State0),
- Connection:next_event(?FUNCTION_NAME, Record,
+ 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} =
@@ -1313,10 +1315,10 @@ handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_rec
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 =
+handle_common_event(Type, Msg, StateName, #state{connection_env =
#connection_env{negotiated_version = Version}} = State,
_) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, Msg}),
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}),
handle_own_alert(Alert, Version, StateName, State).
handle_call({application_data, _Data}, _, _, _, _) ->
@@ -1436,7 +1438,7 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName,
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:error_report(Report),
+ ?LOG_ERROR(Report),
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
{stop, {shutdown,normal}, State};
@@ -1466,7 +1468,7 @@ handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = 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}.
%%====================================================================
@@ -1581,18 +1583,23 @@ security_info(#state{connection_states = ConnectionStates}) ->
ssl_record:current_connection_state(ConnectionStates, read),
[{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}].
-do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} =
+do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
ServerHelloExt,
#state{connection_env = #connection_env{negotiated_version = Version},
handshake_env = HsEnv,
session = #session{session_id = SessId},
- connection_states = ConnectionStates0}
+ 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{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
+ State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
NextProtocols =/= undefined}}, Connection),
case Type of
new ->
@@ -1601,6 +1608,60 @@ 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},
@@ -2469,22 +2530,6 @@ hibernate_after(connection = StateName,
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
-map_extensions(#hello_extensions{renegotiation_info = RenegotiationInfo,
- signature_algs = SigAlg,
- alpn = Alpn,
- next_protocol_negotiation = Next,
- srp = SRP,
- ec_point_formats = ECPointFmt,
- elliptic_curves = ECCCurves,
- sni = SNI}) ->
- #{renegotiation_info => ssl_handshake:extension_value(RenegotiationInfo),
- signature_algs => ssl_handshake:extension_value(SigAlg),
- alpn => ssl_handshake:extension_value(Alpn),
- srp => ssl_handshake:extension_value(SRP),
- next_protocol => ssl_handshake:extension_value(Next),
- ec_point_formats => ssl_handshake:extension_value(ECPointFmt),
- elliptic_curves => ssl_handshake:extension_value(ECCCurves),
- sni => ssl_handshake:extension_value(SNI)}.
terminate_alert(normal) ->
?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
@@ -2833,14 +2878,14 @@ alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Con
{ssl_error, Connection:socket(Pids, Transport, Socket, Tracker), ReasonCode})
end.
-log_alert(true, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
+log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
Txt = ssl_alert:own_alert_txt(Alert),
- error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]));
-log_alert(true, Role, ProtocolName, StateName, Alert) ->
+ Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]),
+ ssl_logger:notice(Level, Report);
+log_alert(Level, Role, ProtocolName, StateName, Alert) ->
Txt = ssl_alert:alert_txt(Alert),
- error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]));
-log_alert(false, _, _, _, _) ->
- ok.
+ Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]),
+ ssl_logger:notice(Level, Report).
invalidate_session(client, Host, Port, Session) ->
ssl_manager:invalidate_session(Host, Port, Session);
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index b6b23701bb..0ac138b444 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -106,20 +106,77 @@
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()
+ 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_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_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 27c071d6dd..16b5b34a3e 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -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,
@@ -53,14 +54,14 @@
-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/2, verify_server_key/5,
- select_version/3, extension_value/1
+ select_version/3, select_supported_version/2, extension_value/1
]).
%% Encode
--export([encode_handshake/2, encode_hello_extensions/1,
+-export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2,
encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]).
%% Decode
--export([decode_handshake/3, decode_hello_extensions/1,
+-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
]).
@@ -71,13 +72,15 @@
premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%% Extensions handling
--export([client_hello_extensions/5,
+-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,
select_hashsign/4, select_hashsign/5,
- select_hashsign_algs/3
+ select_hashsign_algs/3, empty_extensions/2, add_server_share/2
]).
+-export([get_cert_params/1]).
+
%%====================================================================
%% Create handshake messages
%%====================================================================
@@ -93,7 +96,7 @@ hello_request() ->
%%--------------------------------------------------------------------
-spec server_hello(#session{}, ssl_record:ssl_version(), ssl_record:connection_states(),
- #hello_extensions{}) -> #server_hello{}.
+ Extension::map()) -> #server_hello{}.
%%
%% Description: Creates a server hello message.
%%--------------------------------------------------------------------
@@ -504,6 +507,21 @@ verify_server_key(#server_key_params{params_bin = EncParams,
select_version(RecordCB, ClientVersion, Versions) ->
do_select_version(RecordCB, ClientVersion, Versions).
+
+%% 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.
+
+
%%====================================================================
%% Encode handshake
%%====================================================================
@@ -517,7 +535,7 @@ encode_handshake(#server_hello{server_version = {Major, Minor},
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),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
@@ -567,71 +585,126 @@ 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([], 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 + 1,
- encode_hello_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
+ 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>>).
+ 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_client_protocol_negotiation(undefined, _) ->
undefined;
@@ -657,7 +730,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{
@@ -666,14 +739,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},
@@ -716,17 +788,49 @@ 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(binary(), ssl_record:ssl_version(),
+ ssl_record:ssl_version(), atom()) -> map().
+%%
+%% Description: Decodes TLS 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)).
+
%%--------------------------------------------------------------------
--spec decode_hello_extensions({client, binary()} | binary()) -> #hello_extensions{}.
+-spec decode_extensions(binary(),tuple(), 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_extensions(Extensions, Version, MessageType) ->
+ decode_extensions(Extensions, Version, MessageType, empty_extensions()).
%%--------------------------------------------------------------------
-spec decode_server_key(binary(), ssl:key_algo(), ssl_record:ssl_version()) ->
@@ -936,88 +1040,210 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) ->
%%====================================================================
%% Extensions handling
%%====================================================================
-client_hello_extensions(Version, CipherSuites,
- #ssl_options{signature_algs = SupportedHashSigns,
- eccs = SupportedECCs} = SslOpts, ConnectionStates, Renegotiation) ->
- {EcPointFormats, EllipticCurves} =
- case advertises_ec_ciphers(lists:map(fun ssl_cipher_format:suite_definition/1, CipherSuites)) of
- true ->
- client_ecc_extensions(SupportedECCs);
- false ->
- {undefined, undefined}
- end,
+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).
+
+
+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)
+ }.
+
- #hello_extensions{
- renegotiation_info = renegotiation_info(tls_record, client,
- ConnectionStates, Renegotiation),
- srp = SRP,
- signature_algs = available_signature_algs(SupportedHashSigns, Version),
- 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(SslOpts#ssl_options.server_name_indication)}.
+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_definition/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(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}}.
+
+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.
+ %% Filter out all hash-sign tuples when creating the signature_algs extension.
+ %% (TLS 1.3 specific record type)
+ SignatureSchemes = lists:filter(fun is_atom/1, SignatureSchemes0),
+ #signature_algorithms{signature_scheme_list = SignatureSchemes}.
+
+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,
- Random, NegotiatedCipherSuite,
+ 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,
+ ConnectionStates0, Renegotiation, SecureRenegotation),
+
+ Empty = empty_extensions(Version, server_hello),
+ ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
ConnectionStates, Renegotiation),
- ec_point_formats = server_ecc_extension(Version, ECCFormat)
- },
-
+ 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 ->
Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)),
{Session, ConnectionStates, Protocol,
- ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}};
+ ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}};
true ->
- ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
+ 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};
undefined ->
+ NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
{ConnectionStates, npn, Protocol};
{error, Reason} ->
@@ -1062,26 +1288,50 @@ select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon;
{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),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} =
- TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
-
- SubSign = sign_algo(SubjAlgo),
-
- case lists:filter(fun({_, S} = Algos) when S == SubSign ->
- is_acceptable_hash_sign(Algos, KeyExAlgo, SupportedHashSigns);
- (_) ->
- false
- end, HashSigns) of
- [] ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
- [HashSign | _] ->
- HashSign
+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;
select_hashsign(_, Cert, _, _, Version) ->
#'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
@@ -1095,21 +1345,23 @@ select_hashsign(_, Cert, _, _, Version) ->
%%
%% 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,
+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
+ {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 ->
- case lists:filter(fun({_, S} = Algos) when S == SubSign ->
+ case lists:filter(fun({_, S} = Algos) when S == PublicKeyAlgo ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
(_) ->
false
@@ -1122,8 +1374,38 @@ select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash
false ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
-select_hashsign(#certificate_request{}, Cert, _, Version) ->
- select_hashsign(undefined, Cert, undefined, [], Version).
+select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) ->
+ {_, _, PublicKeyAlgo0} = get_cert_params(Cert),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+
+ %% 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.
+
+
+%% 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}.
+
+
+get_signature_scheme(undefined) ->
+ undefined;
+get_signature_scheme(#signature_algorithms_cert{
+ signature_scheme_list = ClientSignatureSchemes}) ->
+ ClientSignatureSchemes.
+
%%--------------------------------------------------------------------
-spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) ->
@@ -1172,6 +1454,8 @@ 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}) ->
@@ -1192,33 +1476,30 @@ int_to_bin(I) ->
L = (length(integer_to_list(I, 16)) + 1) div 2,
<<I:(L*8)>>.
-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)>>
- end;
-
-certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == rsa;
- KeyExchange == dh_rsa;
- KeyExchange == dhe_rsa;
- KeyExchange == ecdhe_rsa ->
- <<?BYTE(?RSA_SIGN)>>;
-
-certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_dss;
- KeyExchange == dhe_dss;
- KeyExchange == srp_dss ->
- <<?BYTE(?DSS_SIGN)>>;
-
-certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_ecdsa;
- KeyExchange == dhe_ecdsa;
- KeyExchange == ecdh_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
- <<?BYTE(?ECDSA_SIGN)>>;
+%% 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(_, _) ->
- <<?BYTE(?RSA_SIGN)>>.
+ 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.
certificate_authorities(CertDbHandle, CertDbRef) ->
Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef),
@@ -1761,16 +2042,32 @@ encode_alpn(undefined, _) ->
encode_alpn(Protocols, _) ->
#alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
-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].
+
+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>>.
+
+hello_extensions_list(HelloExtensions) ->
+ [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined].
%%-------------Decode handshakes---------------------------------
dec_server_key(<<?UINT16(PLen), P:PLen/binary,
@@ -1910,16 +2207,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
@@ -1928,25 +2269,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}});
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{renegotiation_info =>
+ #renegotiation_info{renegotiated_connection =
+ RenegotiateInfo}});
-dec_hello_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), SRP:SRPLen/binary, Rest/binary>>, Acc)
+decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
+ SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc)
when Len == SRPLen + 1 ->
- dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}});
+ decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}});
-dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+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) ->
@@ -1958,31 +2328,103 @@ 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}});
+ 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,
+ 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),Rest/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{key_share =>
+ #key_share_hello_retry_request{
+ selected_group = tls_v1:enum_to_group(Group)}});
-dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), Rest/binary>>, Acc) when Len == 0 ->
- dec_hello_extensions(Rest, Acc#hello_extensions{sni = #sni{hostname = ""}}); %% Server may send an empy SNI
-dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
- <<?UINT16(_), NameList/binary>> = ExtData,
- dec_hello_extensions(Rest, Acc#hello_extensions{sni = dec_sni(NameList)});
%% 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)>>) ->
@@ -2000,6 +2442,26 @@ decode_alpn(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, []).
@@ -2253,17 +2715,6 @@ handle_srp_extension(undefined, Session) ->
handle_srp_extension(#srp{username = Username}, Session) ->
Session#session{srp_username = Username}.
-
-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( _, KeyExAlgo, _) when
KeyExAlgo == psk;
KeyExAlgo == dhe_psk;
@@ -2279,15 +2730,80 @@ is_acceptable_hash_sign(Algos,_, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns) ->
lists:member(Algos, SupportedHashSigns).
-is_acceptable_cert_type(Sign, _HashSigns, Types) ->
+is_acceptable_cert_type(Sign, Types) ->
lists:member(sign_type(Sign), binary_to_list(Types)).
-is_supported_sign(Sign, HashSigns) ->
- [] =/= lists:dropwhile(fun({_, S}) when S =/= Sign ->
- true;
- (_)->
- false
- end, HashSigns).
+%% 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).
+
sign_type(rsa) ->
?RSA_SIGN;
sign_type(dsa) ->
@@ -2306,6 +2822,11 @@ 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};
@@ -2473,4 +2994,51 @@ cert_curve(Cert, ECCCurve0, 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_exhange_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(_, 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 a191fcf766..d4233bea9b 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -52,9 +52,8 @@
-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 +104,11 @@
srp,
ec_point_formats,
elliptic_curves,
- sni
+ sni,
+ client_hello_versions,
+ server_hello_selected_version,
+ signature_algs_cert,
+ key_share
}).
-record(server_hello, {
@@ -313,12 +316,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).
@@ -338,9 +341,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).
@@ -348,11 +350,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).
@@ -365,10 +374,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).
@@ -377,4 +387,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 57c72aa122..3d117a655f 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -25,6 +25,7 @@
-include_lib("public_key/include/public_key.hrl").
+-define(VSN, "8.2.6").
-define(SECRET_PRINTOUT, "***").
-type reason() :: term().
@@ -70,14 +71,39 @@
-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]).
+%% Keep as interop with legacy software but do not support as default
+%% 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', 'tlsv1.1', tlsv1]).
-define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1]).
+
+%% 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', dtlsv1]).
-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).
@@ -96,7 +122,8 @@
cert :: public_key:der_encoded() | secret_printout() | 'undefined',
keyfile :: binary(),
key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo',
- public_key:der_encoded()} | key_map() | secret_printout() | '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(),
@@ -126,7 +153,7 @@
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_fun :: function() | undefined,
@@ -141,7 +168,9 @@
crl_check :: boolean() | peer | best_effort,
crl_cache,
signature_algs,
+ signature_algs_cert,
eccs,
+ supported_groups, %% RFC 8422, RFC 8446
honor_ecc_order :: boolean(),
max_handshake_size :: integer(),
handshake,
@@ -168,19 +197,12 @@
connection_cb
}).
--type key_map() :: #{algorithm := rsa | dss | ecdsa,
- %% engine and key_id ought to
- %% be :=, but putting it in
- %% the spec gives dialyzer warning
- %% of correct code!
- engine => crypto:engine_ref(),
- key_id => crypto:key_id(),
- password => crypto:password()
- }.
-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 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..d59a0dfda2
--- /dev/null
+++ b/lib/ssl/src/ssl_logger.erl
@@ -0,0 +1,420 @@
+%%
+%% %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("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
+ 'tls_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 =:= 'tls_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, #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_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_cipher_suites([_|_] = Ciphers) ->
+ [format_cipher(C) || C <- Ciphers].
+
+format_cipher(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) ->
+ 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV';
+format_cipher(C0) ->
+ list_to_atom(ssl_cipher_format:openssl_suite_name(C0)).
+
+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({M,N}) ->
+ io_lib:format("TLS [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(M),?BYTE(N),_/binary>>|_]) ->
+ io_lib:format("TLS [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(tls_record, N) ->
+ S = string:pad(string:to_lower(erlang:integer_to_list(N, 16)),4,leading,$0),
+ lists:reverse(lists:flatten(S ++ " - ")).
+
+
+end_row(tls_record, 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.
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index c56675b691..456a560bf6 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -516,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"
@@ -530,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,
diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl
index f7ddbd060e..dec48fa914 100644
--- a/lib/ssl/src/ssl_pkix_db.erl
+++ b/lib/ssl/src/ssl_pkix_db.erl
@@ -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,
@@ -311,7 +312,7 @@ 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.
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 1a36b2dba8..91f1876980 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -25,6 +25,7 @@
-module(ssl_record).
-include("ssl_record.hrl").
+-include("ssl_connection.hrl").
-include("ssl_internal.hrl").
-include("ssl_cipher.hrl").
-include("ssl_alert.hrl").
@@ -39,7 +40,8 @@
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]).
@@ -120,6 +122,22 @@ activate_pending_connection_state(#{current_write := Current,
}.
%%--------------------------------------------------------------------
+-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().
%%
@@ -280,13 +298,12 @@ 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
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index a927fba0de..eb718fd20c 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -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
@@ -140,6 +140,7 @@
-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).
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 3229004c9d..39b0b3e53a 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -37,7 +37,8 @@
-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
@@ -49,7 +50,8 @@
handle_protocol_record/3]).
%% Handshake handling
--export([renegotiation/2, renegotiate/2, send_handshake/2,
+-export([renegotiation/2, renegotiate/2, send_handshake/2,
+ send_handshake_flight/1,
queue_handshake/2, queue_change_cipher/2,
reinit/1, reinit_handshake_data/1, select_sni_extension/1,
empty_connection_state/2]).
@@ -66,9 +68,26 @@
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
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}]).
@@ -173,25 +192,32 @@ next_record(State) ->
next_record(State, CipherTexts, ConnectionStates, Check) ->
next_record(State, CipherTexts, ConnectionStates, Check, []).
%%
-next_record(State, [#ssl_tls{type = ?APPLICATION_DATA} = CT|CipherTexts], ConnectionStates0, Check, Acc) ->
- case tls_record:decode_cipher_text(CT, ConnectionStates0, Check) of
- {#ssl_tls{fragment = Fragment}, ConnectionStates} ->
- next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]);
- #alert{} = Alert ->
- Alert
- end;
-next_record(State, [CT|CipherTexts], ConnectionStates0, Check, []) ->
- case tls_record:decode_cipher_text(CT, ConnectionStates0, Check) of
- {Record, ConnectionStates} ->
+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(State, CipherTexts, ConnectionStates, _Check, Acc) ->
- %% Not ?APPLICATION_DATA but we have a nonempty Acc
- %% -> build an ?APPLICATION_DATA record with the accumulated fragments
- next_record_done(State, CipherTexts, ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))}).
+ end.
next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) ->
{Record,
@@ -209,7 +235,8 @@ next_event(StateName, no_record, State0, Actions) ->
{#ssl_tls{} = Record, State} ->
{next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
#alert{} = Alert ->
- {next_state, StateName, State0, [{next_event, internal, Alert} | Actions]}
+ Version = State0#state.connection_env#connection_env.negotiated_version,
+ ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
end;
next_event(StateName, Record, State, Actions) ->
case Record of
@@ -218,7 +245,8 @@ next_event(StateName, Record, 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]}
+ Version = State#state.connection_env#connection_env.negotiated_version,
+ ssl_connection:handle_own_alert(Alert, Version, StateName, State)
end.
@@ -229,8 +257,14 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat
{stop, _, _} = Stop->
Stop;
{Record, State1} ->
- {next_state, StateName, State, Actions} = next_event(StateName, Record, State1),
- ssl_connection:hibernate_after(StateName, State, Actions)
+ case next_event(StateName, Record, State1) of
+ {next_state, StateName, State} ->
+ ssl_connection:hibernate_after(StateName, State, []);
+ {next_state, StateName, State, Actions} ->
+ ssl_connection:hibernate_after(StateName, State, Actions);
+ {stop, _, _} = Stop ->
+ Stop
+ end
end;
%%% TLS record protocol level handshake messages
handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
@@ -239,7 +273,8 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = Options} = State0) ->
try
- {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options),
+ 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}},
@@ -320,12 +355,17 @@ renegotiate(#state{static_env = #static_env{role = server,
send_handshake(Handshake, State) ->
send_handshake_flight(queue_handshake(Handshake, State)).
+
queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
flight_buffer = Flight0,
+ ssl_options = SslOpts,
connection_states = ConnectionStates0} = State0) ->
{BinHandshake, ConnectionStates, Hist} =
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, 'tls_record', BinHandshake),
+
State0#state{connection_states = ConnectionStates,
handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
flight_buffer = Flight0 ++ [BinHandshake]}.
@@ -336,11 +376,14 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket,
tls_socket:send(Transport, Socket, Flight),
{State0#state{flight_buffer = []}, []}.
+
queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version},
flight_buffer = Flight0,
+ ssl_options = SslOpts,
connection_states = ConnectionStates0} = State0) ->
{BinChangeCipher, ConnectionStates} =
encode_change_cipher(Msg, Version, ConnectionStates0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'tls_record', BinChangeCipher),
State0#state{connection_states = ConnectionStates,
flight_buffer = Flight0 ++ [BinChangeCipher]}.
@@ -360,8 +403,8 @@ reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
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.
@@ -371,6 +414,7 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
%%====================================================================
%% Alert and close handling
%%====================================================================
+
%%--------------------------------------------------------------------
-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
@@ -383,10 +427,12 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
send_alert(Alert, #state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = 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, 'tls_record', BinMsg),
StateData0#state{connection_states = ConnectionStates}.
%% If an ALERT sent in the connection state, should cause the TLS
@@ -473,22 +519,27 @@ init({call, From}, {start, Timeout},
session = #session{own_certificate = Cert} = Session0,
connection_states = ConnectionStates0
} = State0) ->
+ 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:hello_version(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),
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, 'tls_record', BinMsg),
+
State = State0#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = Version}, %% Requested version
+ 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},
+ start_or_recv_from = From,
+ key_share = KeyShare},
next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]);
+
init(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
@@ -519,14 +570,15 @@ hello(internal, #client_hello{extensions = Extensions} = Hello,
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, ssl_connection:map_extensions(Extensions)}}]};
+ [{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, ssl_connection:map_extensions(Extensions)}}]};
+ [{reply, From, {ok, Extensions}}]};
+
hello(internal, #client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
static_env = #static_env{
@@ -539,27 +591,40 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
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#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
- })
+
+ 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,
#state{connection_states = ConnectionStates0,
@@ -664,7 +729,7 @@ connection(internal, #hello_request{},
try tls_sender:peer_renegotiate(Pid) of
{ok, Write} ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
+ 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)
@@ -683,7 +748,8 @@ connection(internal, #hello_request{},
ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
+ 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);
@@ -738,7 +804,119 @@ downgrade(info, {CloseTag, Socket},
downgrade(info, Info, State) ->
handle_info(Info, ?FUNCTION_NAME, State);
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+
+%%--------------------------------------------------------------------
+%% TLS 1.3 state functions
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-spec start(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+start(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+start(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec negotiated(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+negotiated(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+negotiated(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec recvd_ch(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+recvd_ch(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+recvd_ch(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_cert(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cv(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cv(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_cv(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_eoed(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_eoed(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_eoed(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_finished(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_finished(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_finished(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_flight2(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_flight2(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_flight2(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec connected(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connected(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+connected(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert_cr(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert_cr(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_cert_cr(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ee(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ee(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_ee(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_sh(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_sh(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_sh(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
%--------------------------------------------------------------------
%% gen_statem callbacks
%%--------------------------------------------------------------------
@@ -769,7 +947,6 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
#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;
@@ -825,8 +1002,9 @@ initialize_tls_sender(#state{static_env = #static_env{
tracker = Tracker
},
connection_env = #connection_env{negotiated_version = Version},
- socket_options = SockOpts,
- ssl_options = #ssl_options{renegotiate_at = RenegotiateAt},
+ 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,
@@ -836,21 +1014,27 @@ initialize_tls_sender(#state{static_env = #static_env{
tracker => Tracker,
transport_cb => Transport,
negotiated_version => Version,
- renegotiate_at => RenegotiateAt},
+ 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} = State0) ->
+ 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) of
+ case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of
{Records, Buf1} ->
CT1 = CT0 ++ Records,
next_record(State0#state{protocol_buffers =
@@ -968,12 +1152,26 @@ gen_handshake(StateName, Type, Event,
Version, StateName, State)
end.
+
+gen_handshake_1_3(StateName, Type, Event,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
+ 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)
@@ -989,6 +1187,29 @@ gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_ve
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
@@ -1033,3 +1254,34 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) ->
end
end,
spawn(Kill).
+
+maybe_generate_client_shares(#ssl_options{
+ versions = [Version|_],
+ supported_groups =
+ #supported_groups{
+ supported_groups = Groups}})
+ when Version =:= {3,4} ->
+ ssl_cipher:generate_client_shares(Groups);
+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_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
new file mode 100644
index 0000000000..de786d0875
--- /dev/null
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -0,0 +1,189 @@
+%%
+%% %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_finished/4
+ ]).
+
+start(internal,
+ #client_hello{} = Hello,
+ #state{connection_states = _ConnectionStates0,
+ ssl_options = #ssl_options{ciphers = _ServerCiphers,
+ signature_algs = _ServerSignAlgs,
+ signature_algs_cert = _SignatureSchemes, %% TODO: Check??
+ supported_groups = _ServerGroups0,
+ versions = _Versions} = SslOpts,
+ session = #session{own_certificate = Cert}} = State0,
+ _Module) ->
+
+ Env = #{cert => Cert},
+ case tls_handshake_1_3:handle_client_hello(Hello, SslOpts, Env) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ M ->
+ %% update connection_states with cipher
+ State = update_state(State0, M),
+ {next_state, negotiated, State, [{next_event, internal, M}]}
+
+ end.
+
+
+negotiated(internal, Map, State0, _Module) ->
+ case tls_handshake_1_3:do_negotiated(Map, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
+ State ->
+ {next_state, wait_finished, State, []}
+
+ end.
+
+
+wait_finished(internal,
+ #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_finished(ChangeCipherSpec, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_finished, State0);
+ State1 ->
+ {Record, State} = tls_connection:next_record(State1),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State)
+ end;
+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).
+
+
+update_state(#state{connection_states = ConnectionStates0,
+ connection_env = CEnv,
+ session = Session} = State,
+ #{cipher := Cipher,
+ key_share := KeyShare,
+ session_id := SessionId}) ->
+ #{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,
+ key_share = KeyShare,
+ session = Session#session{session_id = SessionId},
+ connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index fbb81f56fe..a1397047f2 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -26,15 +26,17 @@
-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").
%% Handshake handling
--export([client_hello/8, hello/4]).
+-export([client_hello/9, hello/4]).
%% Handshake encoding
-export([encode_handshake/2]).
@@ -49,7 +51,8 @@
%%====================================================================
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
- #ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
+ #ssl_options{}, integer(), atom(), boolean(), der_cert(),
+ #key_share_client_hello{} | undefined) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
@@ -59,19 +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),
+
+ %% 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(Version,
AvailableCipherSuites,
SslOpts, ConnectionStates,
- Renegotiation),
+ 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,
@@ -88,11 +104,69 @@ client_hello(Host, Port, ConnectionStates,
ssl_record:connection_states(), alpn | npn, binary() | undefined}|
{tls_record:tls_version(), {resumed | new, #session{}},
ssl_record:connection_states(), binary() | undefined,
- #hello_extensions{}, {ssl:hash(), ssl:sign_algo()} |
+ HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
undefined} | #alert{}.
%%
%% 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.
+%%--------------------------------------------------------------------
+%% TLS 1.2 Client
+%%
+%% - If "supported_version" is present (ServerHello):
+%% - Abort handshake with an "illegal_parameter" alert
+hello(#server_hello{server_version = Version,
+ extensions = #{server_hello_selected_version :=
+ #server_hello_selected_version{selected_version = Version}}
+ },
+ #ssl_options{versions = SupportedVersions},
+ _ConnectionStates0, _Renegotiation) ->
+ case tls_record:is_higher({3,4}, Version) of
+ true ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ false ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ %% Implement TLS 1.3 statem ???
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION);
+ false ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
+ end
+ end;
+
hello(#server_hello{server_version = Version, random = Random,
cipher_suite = CipherSuite,
compression_method = Compression,
@@ -107,6 +181,36 @@ hello(#server_hello{server_version = Version, random = Random,
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,
@@ -114,18 +218,7 @@ 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
@@ -140,7 +233,8 @@ hello(#client_hello{client_version = ClientVersion,
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--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
%%--------------------------------------------------------------------
@@ -176,10 +270,7 @@ handle_client_hello(Version,
cipher_suites = CipherSuites,
compression_methods = Compressions,
random = Random,
- extensions =
- #hello_extensions{elliptic_curves = Curves,
- signature_algs = ClientHashSigns}
- = HelloExt},
+ extensions = HelloExt},
#ssl_options{versions = Versions,
signature_algs = SupportedHashSigns,
eccs = SupportedECCs,
@@ -188,6 +279,9 @@ handle_client_hello(Version,
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),
@@ -201,8 +295,10 @@ handle_client_hello(Version,
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_definition(CipherSuite),
- case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg,
- SupportedHashSigns, Version) of
+ case ssl_handshake:select_hashsign({ClientHashSigns, ClientSignatureSchemes},
+ Cert, KeyExAlg,
+ SupportedHashSigns,
+ Version) of
#alert{} = Alert ->
Alert;
HashSign ->
@@ -243,8 +339,26 @@ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
catch throw:Alert ->
Alert
end.
+
+
+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},
random = Random,
@@ -263,7 +377,8 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
?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).
@@ -274,6 +389,7 @@ get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
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
_:_ ->
@@ -282,24 +398,26 @@ get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
get_tls_handshake_aux(_Version, Data, _, Acc) ->
{lists:reverse(Acc), Data}.
-decode_handshake(_, ?HELLO_REQUEST, <<>>) ->
+decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 ->
#hello_request{};
-decode_handshake(_Version, ?CLIENT_HELLO,
+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}),
+ 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 = Comp_methods,
+ 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_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
new file mode 100644
index 0000000000..c099e3f276
--- /dev/null
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -0,0 +1,886 @@
+%%
+%% %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]).
+
+%% Handshake
+-export([handle_client_hello/3]).
+
+%% Create handshake messages
+-export([certificate/5,
+ certificate_verify/4,
+ encrypted_extensions/0,
+ server_hello/4]).
+
+-export([do_negotiated/2,
+ do_wait_finished/2]).
+
+%%====================================================================
+%% Create handshake messages
+%%====================================================================
+
+server_hello(SessionId, KeyShare, ConnectionStates, _Map) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ Extensions = server_hello_extensions(KeyShare),
+ #server_hello{server_version = {3,3}, %% legacy_version
+ cipher_suite = SecParams#security_parameters.cipher_suite,
+ compression_method = 0, %% legacy attribute
+ random = SecParams#security_parameters.server_random,
+ session_id = SessionId,
+ extensions = Extensions
+ }.
+
+server_hello_extensions(KeyShare) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions = #{server_hello_selected_version => SupportedVersions},
+ ssl_handshake:add_server_share(Extensions, KeyShare).
+
+%% TODO: implement support for encrypted_extensions
+encrypted_extensions() ->
+ #encrypted_extensions{
+ extensions = #{}
+ }.
+
+%% TODO: use maybe monad for error handling!
+%% 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, server) ->
+ 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.
+ #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList};
+ {error, Error} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
+ end.
+
+
+certificate_verify(PrivateKey, SignatureScheme,
+ #state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages, _}}}, server) ->
+ #{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),
+
+ %% Digital signatures use the hash function defined by the selected signature
+ %% scheme.
+ case digitally_sign(THash, <<"TLS 1.3, server CertificateVerify">>,
+ 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(?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
+digitally_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.
+
+
+build_content(Context, THash) ->
+ Prefix = binary:copy(<<32>>, 64),
+ <<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
+
+%%====================================================================
+%% Handle handshake messages
+%%====================================================================
+
+handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
+ session_id = SessionId,
+ extensions = Extensions} = _Hello,
+ #ssl_options{ciphers = ServerCiphers,
+ signature_algs = ServerSignAlgs,
+ signature_algs_cert = _SignatureSchemes, %% TODO: Check??
+ supported_groups = ServerGroups0} = _SslOpts,
+ Env) ->
+
+ Cert = maps:get(cert, Env, undefined),
+
+ ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
+ ClientGroups = get_supported_groups(ClientGroups0),
+ ServerGroups = get_supported_groups(ServerGroups0),
+
+ ClientShares0 = maps:get(key_share, Extensions, undefined),
+ ClientShares = get_key_shares(ClientShares0),
+
+ ClientSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ClientSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+
+ %% TODO: use library function if it exists
+ %% Init the maybe "monad"
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% 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(ClientCiphers, ServerCiphers)),
+ Group = Maybe(select_server_group(ServerGroups, ClientGroups)),
+ Maybe(validate_key_share(ClientGroups, ClientShares)),
+
+ ClientPubKey = Maybe(get_client_public_key(Group, 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)),
+
+ %% Generate server_share
+ KeyShare = ssl_cipher:generate_server_share(Group),
+ _Ret = #{cipher => Cipher,
+ group => Group,
+ sign_alg => SelectedSignAlg,
+ client_share => ClientPubKey,
+ key_share => KeyShare,
+ session_id => SessionId}
+
+ %% TODO:
+ %% - session handling
+ %% - handle extensions: ALPN
+ %% (do not handle: NPN, srp, renegotiation_info, ec_point_formats)
+
+ catch
+ {Ref, {insufficient_security, no_suitable_groups}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
+ {Ref, illegal_parameter} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ {Ref, {hello_retry_request, _Group0}} ->
+ %% TODO
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, "hello_retry_request not implemented");
+ {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_negotiated(#{client_share := ClientKey,
+ group := SelectedGroup,
+ sign_alg := SignatureScheme
+ } = Map,
+ #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},
+ connection_env = #connection_env{private_key = CertPrivateKey},
+ static_env = #static_env{
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% Create server_hello
+ %% Extensions: supported_versions, key_share, (pre_shared_key)
+ ServerHello = server_hello(SessionId, KeyShare, ConnectionStates0, Map),
+
+ {State1, _} = tls_connection:send_handshake(ServerHello, State0),
+
+ State2 =
+ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, State1),
+
+ State3 = ssl_record:step_encryption_state(State2),
+
+ %% Create EncryptedExtensions
+ EncryptedExtensions = encrypted_extensions(),
+
+ %% Encode EncryptedExtensions
+ State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
+
+ %% Create Certificate
+ Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
+
+ %% Encode Certificate
+ State5 = tls_connection:queue_handshake(Certificate, State4),
+
+ %% Create CertificateVerify
+ CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme,
+ State5, server)),
+ %% Encode CertificateVerify
+ State6 = tls_connection:queue_handshake(CertificateVerify, State5),
+
+ %% Create Finished
+ Finished = finished(State6),
+
+ %% Encode Finished
+ State7 = tls_connection:queue_handshake(Finished, State6),
+
+ %% Send first flight
+ {State8, _} = tls_connection:send_handshake_flight(State7),
+
+ State8
+
+ catch
+ {Ref, {state_not_implemented, State}} ->
+ %% TODO
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end.
+
+
+do_wait_finished(#change_cipher_spec{},
+ #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{
+ cert_db = _CertDbHandle,
+ cert_db_ref = _CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ %% {Ref,Maybe} = maybe(),
+
+ try
+
+ State0
+
+ catch
+ {_Ref, {state_not_implemented, State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end;
+do_wait_finished(#finished{},
+ #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{
+ cert_db = _CertDbHandle,
+ cert_db_ref = _CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+
+ %% {Ref,Maybe} = maybe(),
+
+ try
+ %% TODO: validate client Finished
+
+ State1 = calculate_traffic_secrets(State0),
+
+ %% Configure traffic keys
+ ssl_record:step_encryption_state(State1)
+
+
+ catch
+ {_Ref, {state_not_implemented, State}} ->
+ %% TODO
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end.
+
+
+%% TODO: Remove this function!
+%% not_implemented(State) ->
+%% {error, {state_not_implemented, State}}.
+
+
+calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
+ #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}),
+ PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{}
+
+ IKM = calculate_shared_secret(ClientKey, 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_definition(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{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),
+
+ {Messages0, _} = HHistory,
+
+ %% Drop Client Finish
+ [_|Messages] = Messages0,
+
+ %% 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_definition(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{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_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)}.
+
+
+
+cipher_init(Key, IV, FinishedKey) ->
+ #cipher_state{key = Key,
+ iv = IV,
+ finished_key = FinishedKey,
+ tag_len = 16}.
+
+
+%% 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_server_group(_, []) ->
+ {error, {insufficient_security, no_suitable_groups}};
+select_server_group(ServerGroups, [C|ClientGroups]) ->
+ case lists:member(C, ServerGroups) of
+ true ->
+ {ok, C};
+ false ->
+ select_server_group(ServerGroups, ClientGroups)
+ 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_key_share(_ ,[]) ->
+ ok;
+validate_key_share([], _) ->
+ {error, illegal_parameter};
+validate_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
+ validate_key_share(ClientGroups, ClientShares);
+validate_key_share([_|ClientGroups], [_|_] = ClientShares) ->
+ validate_key_share(ClientGroups, ClientShares).
+
+
+get_client_public_key(Group, ClientShares) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, {_, _, ClientPublicKey}} ->
+ {ok, ClientPublicKey};
+ false ->
+ %% 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.
+ {error, {hello_retry_request, Group}}
+ end.
+
+select_cipher_suite([], _) ->
+ {error, no_suitable_cipher};
+select_cipher_suite([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(ClientCiphers, ServerCiphers)
+ 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}};
+%% TODO: Implement support for ECDSA keys!
+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.
+ case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae)
+ orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_rsae))
+ 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}) ->
+ ClientSignatureSchemes.
+
+get_supported_groups(#supported_groups{supported_groups = Groups}) ->
+ Groups.
+
+get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
+ ClientShares.
+
+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..7ae1b93e1c
--- /dev/null
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -0,0 +1,238 @@
+%%
+%% %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];
+ }).
+
+-define(PSK_KE, 0).
+-define(PSK_DHE_KE, 1).
+
+-record(psk_keyexchange_modes, {
+ ke_modes % ke_modes<1..255>
+ }).
+-record(empty, {
+ }).
+-record(early_data_indication, {
+ indication % uint32 max_early_data_size (new_session_ticket) |
+ %% #empty{} (client_hello, encrypted_extensions)
+ }).
+-record(psk_identity, {
+ identity, % opaque identity<1..2^16-1>
+ obfuscated_ticket_age % uint32
+ }).
+-record(offered_psks, {
+ psk_identity, %identities<7..2^16-1>;
+ psk_binder_entry %binders<33..2^16-1>, opaque PskBinderEntry<32..255>
+ }).
+-record(pre_shared_keyextension,{
+ extension %OfferedPsks (client_hello) | uint16 selected_identity (server_hello)
+ }).
+
+%% 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 b456197398..94506b8edc 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -30,9 +30,10 @@
-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/3, init_connection_states/2]).
+-export([get_tls_records/4, init_connection_states/2]).
%% Encoding TLS records
-export([encode_handshake/3, encode_alert_record/3,
@@ -40,13 +41,16 @@
-export([encode_plain_text/4]).
%% Decoding
--export([decode_cipher_text/3]).
+-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, hello_version/2]).
+ is_acceptable_version/1, is_acceptable_version/2, hello_version/1]).
-export_type([tls_version/0, tls_atom_version/0]).
@@ -76,8 +80,10 @@ init_connection_states(Role, BeastMitigation) ->
%%--------------------------------------------------------------------
-spec get_tls_records(
- binary(), [tls_version()] | tls_version(),
- Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}) ->
+ 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{}.
@@ -86,11 +92,10 @@ init_connection_states(Role, BeastMitigation) ->
%% Description: Given old buffer and new data from TCP, packs up a records
%% data
%%--------------------------------------------------------------------
-
-get_tls_records(Data, Versions, Buffer) when is_binary(Buffer) ->
- parse_tls_records(Versions, {[Data],byte_size(Data),[]}, undefined);
-get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}) ->
- parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, Hdr).
+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
@@ -102,6 +107,8 @@ get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}) ->
%
%% 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,
@@ -122,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)>>,
@@ -142,6 +151,8 @@ encode_change_cipher_spec(Version, ConnectionStates) ->
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
+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 :=
@@ -155,12 +166,14 @@ encode_data(Data, Version,
%%====================================================================
%%--------------------------------------------------------------------
--spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states(), boolean()) ->
+-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(CipherText,
+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 :=
@@ -170,7 +183,7 @@ decode_cipher_text(CipherText,
}
} = ConnectionStates0, _) ->
SeqBin = <<?UINT64(Seq)>>,
- #ssl_tls{type = Type, version = {MajVer,MinVer} = Version, fragment = Fragment} = CipherText,
+ #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(
@@ -187,17 +200,17 @@ decode_cipher_text(CipherText,
cipher_state => CipherS,
sequence_number => Seq + 1,
compression_state => CompressionS}},
- {CipherText#ssl_tls{fragment = Plain}, ConnectionStates};
+ {CipherTextRecord#ssl_tls{fragment = Plain}, ConnectionStates};
#alert{} = Alert ->
Alert
end;
-decode_cipher_text(#ssl_tls{version = Version,
- fragment = CipherFragment} = CipherText,
+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(CipherText#ssl_tls.type, Version, PlainFragment, 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,
@@ -210,7 +223,7 @@ decode_cipher_text(#ssl_tls{version = Version,
ConnnectionStates0#{current_read =>
ReadState1#{sequence_number => Seq + 1,
compression_state => CompressionS1}},
- {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
+ {CipherTextRecord#ssl_tls{fragment = Plain}, ConnnectionStates};
false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end;
@@ -229,6 +242,8 @@ decode_cipher_text(#ssl_tls{version = 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') ->
@@ -239,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}) ->
@@ -372,10 +389,10 @@ is_acceptable_version({N,_} = Version, Versions)
is_acceptable_version(_,_) ->
false.
--spec hello_version(tls_version(), [tls_version()]) -> tls_version().
-hello_version(Version, _) when Version >= {3, 3} ->
- Version;
-hello_version(_, Versions) ->
+-spec hello_version([tls_version()]) -> tls_version().
+hello_version([Highest|_]) when Highest >= {3,3} ->
+ Highest;
+hello_version(Versions) ->
lowest_protocol_version(Versions).
%%--------------------------------------------------------------------
@@ -394,84 +411,91 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
server_verify_data => undefined
}.
+%% 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, undefined) ->
- decode_tls_records(Versions, Q, [], undefined, undefined, undefined);
-parse_tls_records(Versions, Q, #ssl_tls{type = Type, version = Version, fragment = Length}) ->
- decode_tls_records(Versions, Q, [], Type, Version, Length).
+
+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, Acc, undefined, _Version, _Length) ->
+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, Acc, Type, {MajVer,MinVer}, Length);
+ 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, Acc, Type, {MajVer,MinVer}, undefined);
+ 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, Acc, Type, undefined, undefined);
+ validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, undefined, undefined);
true ->
- validate_tls_records_type(Versions, Q0, Acc, undefined, undefined, undefined)
+ validate_tls_records_type(Versions, Q0, SslOpts, Acc, undefined, undefined, undefined)
end;
-decode_tls_records(Versions, {_,Size,_} = Q0, Acc, Type, undefined, _Length) ->
+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, Acc, Type, {MajVer,MinVer}, Length);
+ 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, Acc, Type, {MajVer,MinVer}, undefined);
+ validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
true ->
- validate_tls_record_version(Versions, Q0, Acc, Type, undefined, undefined)
+ validate_tls_record_version(Versions, Q0, SslOpts, Acc, Type, undefined, undefined)
end;
-decode_tls_records(Versions, {_,Size,_} = Q0, Acc, Type, Version, undefined) ->
+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, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
true ->
- validate_tls_record_length(Versions, Q0, Acc, Type, Version, undefined)
+ validate_tls_record_length(Versions, Q0, SslOpts, Acc, Type, Version, undefined)
end;
-decode_tls_records(Versions, Q, Acc, Type, Version, Length) ->
- validate_tls_record_length(Versions, Q, Acc, Type, Version, Length).
+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, Acc, undefined, _Version, _Length) ->
+validate_tls_records_type(_Versions, Q, _SslOpts, Acc, undefined, _Version, _Length) ->
{lists:reverse(Acc),
{undefined, Q}};
-validate_tls_records_type(Versions, Q, Acc, Type, Version, Length) ->
+validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
if
?KNOWN_RECORD_TYPE(Type) ->
- validate_tls_record_version(Versions, Q, Acc, Type, Version, Length);
+ validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length);
true ->
%% Not ?KNOWN_RECORD_TYPE(Type)
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
end.
-validate_tls_record_version(_Versions, Q, Acc, Type, undefined, _Length) ->
+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, Acc, Type, Version, Length) ->
- if
- is_list(Versions) ->
+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, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end;
- Version =:= Versions ->
+ {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, Acc, Type, Version, Length);
- true ->
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ _ ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end.
-validate_tls_record_length(_Versions, Q, Acc, Type, Version, undefined) ->
+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, Acc, Type, Version, Length) ->
+validate_tls_record_length(Versions, {_,Size0,_} = Q0, SslOpts, Acc, Type, Version, Length) ->
if
Length =< ?MAX_CIPHER_TEXT_LENGTH ->
if
@@ -479,7 +503,8 @@ validate_tls_record_length(Versions, {_,Size0,_} = Q0, Acc, Type, Version, Lengt
%% Complete record
{Fragment, Q} = binary_from_front(Length, Q0),
Record = #ssl_tls{type = Type, version = Version, fragment = Fragment},
- decode_tls_records(Versions, Q, [Record|Acc], undefined, undefined, undefined);
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'tls_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}}
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..05acc08392
--- /dev/null
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -0,0 +1,298 @@
+%%
+%% %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
+%% 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),
+ <<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
index c07b7f49cd..ba73eddf0b 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -48,7 +48,8 @@
negotiated_version,
renegotiate_at,
connection_monitor,
- dist_handle
+ dist_handle,
+ log_level
}).
-record(data,
@@ -202,7 +203,8 @@ init({call, From}, {Pid, #{current_write := WriteState,
tracker := Tracker,
transport_cb := Transport,
negotiated_version := Version,
- renegotiate_at := RenegotiateAt}},
+ renegotiate_at := RenegotiateAt,
+ log_level := LogLevel}},
#data{connection_states = ConnectionStates, static = Static0} = StateData0) ->
Monitor = erlang:monitor(process, Pid),
StateData =
@@ -215,7 +217,8 @@ init({call, From}, {Pid, #{current_write := WriteState,
tracker = Tracker,
transport_cb = Transport,
negotiated_version = Version,
- renegotiate_at = RenegotiateAt}},
+ renegotiate_at = RenegotiateAt,
+ log_level = LogLevel}},
{next_state, handshake, StateData, [{reply, From, ok}]};
init(_, _, _) ->
%% Just in case anything else sneeks through
@@ -406,11 +409,13 @@ handle_common(Type, Msg, _) ->
send_tls_alert(#alert{} = Alert,
#data{static = #static{negotiated_version = Version,
socket = Socket,
- transport_cb = Transport},
+ 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, 'tls_record', BinMsg),
StateData0#data{connection_states = ConnectionStates}.
send_application_data(Data, From, StateName,
@@ -419,7 +424,8 @@ send_application_data(Data, From, StateName,
dist_handle = DistHandle,
negotiated_version = Version,
transport_cb = Transport,
- renegotiate_at = RenegotiateAt},
+ renegotiate_at = RenegotiateAt,
+ log_level = LogLevel},
connection_states = ConnectionStates0} = StateData0) ->
case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
@@ -431,10 +437,12 @@ send_application_data(Data, From, StateName,
StateData = StateData0#data{connection_states = ConnectionStates},
case tls_socket:send(Transport, Socket, Msgs) of
ok when DistHandle =/= undefined ->
+ ssl_logger:debug(LogLevel, outbound, 'tls_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, 'tls_record', Msgs),
{next_state, StateName, StateData, [{reply, From, ok}]};
Result ->
{next_state, StateName, StateData, [{reply, From, Result}]}
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 1bfd9a8b6d..f103f3218b 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -32,7 +32,19 @@
-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 ---------------------------------------------------
+
+%% TODO 1.3 same as above?
--spec suites(1|2|3) -> [ssl_cipher_format:cipher_suite()].
+-spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
[
@@ -244,8 +496,29 @@ suites(3) ->
%% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
%% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
%% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256
- ] ++ suites(2).
-
+ ] ++ suites(2);
+
+suites(4) ->
+ [?TLS_AES_256_GCM_SHA384,
+ ?TLS_AES_128_GCM_SHA256,
+ ?TLS_CHACHA20_POLY1305_SHA256
+ %% Not supported
+ %% ?TLS_AES_128_CCM_SHA256,
+ %% ?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
+ %% Not supported
+ %% ?TLS_AES_128_CCM_SHA256,
+ %% ?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),
@@ -273,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},
@@ -291,15 +568,99 @@ 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.
+ (_ , Acc) ->
+ Acc
+ 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;
@@ -395,6 +756,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).
@@ -409,6 +771,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;