aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/Makefile142
-rw-r--r--lib/ssl/src/dtls_connection.erl128
-rw-r--r--lib/ssl/src/dtls_handshake.erl2
-rw-r--r--lib/ssl/src/ssl.erl39
-rw-r--r--lib/ssl/src/ssl_cipher.erl56
-rw-r--r--lib/ssl/src/ssl_connection.erl251
-rw-r--r--lib/ssl/src/ssl_connection.hrl3
-rw-r--r--lib/ssl/src/ssl_dh_groups.erl26
-rw-r--r--lib/ssl/src/ssl_handshake.erl327
-rw-r--r--lib/ssl/src/ssl_handshake.hrl3
-rw-r--r--lib/ssl/src/ssl_internal.hrl1
-rw-r--r--lib/ssl/src/tls_connection.erl348
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl131
-rw-r--r--lib/ssl/src/tls_handshake.erl15
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl344
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl2
-rw-r--r--lib/ssl/src/tls_record.erl37
-rw-r--r--lib/ssl/src/tls_record_1_3.erl39
-rw-r--r--lib/ssl/src/tls_sender.erl94
-rw-r--r--lib/ssl/src/tls_v1.erl17
20 files changed, 1334 insertions, 671 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index c7dee81c71..8dc76f2638 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -39,66 +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 \
- tls_connection_1_3 \
- dtls_connection \
- tls_sender\
ssl_config \
ssl_connection \
- tls_handshake \
- tls_handshake_1_3\
- 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 \
- tls_record_1_3\
- 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 \
- ssl_logger \
- ssl_dh_groups
+ 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 tls_handshake_1_3.hrl dtls_handshake.hrl \
- ssl_api.hrl ssl_internal.hrl \
- ssl_record.hrl tls_record.hrl tls_record_1_3.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) \
@@ -117,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
# ----------------------------------------------------
@@ -133,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);' $< > $@
@@ -147,7 +176,6 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
docs:
-
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -165,24 +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)/tls_connection_1_3.$(EMULATOR): ssl_internal.hrl tls_connection.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)/tls_handshake_1_3.$(EMULATOR): tls_handshake_1_3.hrl tls_handshake.hrl ssl_internal.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 8ed4505256..73885de6c1 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -40,7 +40,7 @@
-export([start_fsm/8, start_link/7, init/1, pids/1]).
%% State transition handling
--export([next_record/1, next_event/3, next_event/4, handle_common_event/4]).
+-export([next_event/3, next_event/4, handle_common_event/4]).
%% Handshake handling
-export([renegotiate/2, send_handshake/2,
@@ -51,7 +51,7 @@
-export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
%% Data handling
--export([encode_data/3, passive_receive/2, next_record_if_active/1,
+-export([encode_data/3, next_record/1,
send/3, socket/5, setopts/3, getopts/3]).
%% gen_statem state functions
@@ -163,9 +163,9 @@ next_record(State) ->
next_event(StateName, Record, State) ->
next_event(StateName, Record, State, []).
-next_event(connection = StateName, no_record,
+next_event(StateName, no_record,
#state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case next_record_if_active(State0) of
+ case next_record(State0) of
{no_record, State} ->
ssl_connection:hibernate_after(StateName, State, Actions);
{#ssl_tls{epoch = CurrentEpoch,
@@ -179,21 +179,18 @@ next_event(connection = StateName, no_record,
{#ssl_tls{epoch = Epoch,
type = ?HANDSHAKE,
version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- {NextRecord, State} = next_record(State2),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
%% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
{#ssl_tls{epoch = Epoch,
type = ?CHANGE_CIPHER_SPEC,
version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- {NextRecord, State} = next_record(State2),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
{#ssl_tls{epoch = _Epoch,
- version = _Version}, State1} ->
+ version = _Version}, State} ->
%% TODO maybe buffer later epoch
- {Record, State} = next_record(State1),
- next_event(StateName, Record, State, Actions);
+ next_event(StateName, no_record, State, Actions);
{#alert{} = Alert, State} ->
{next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end;
@@ -211,24 +208,20 @@ next_event(connection = StateName, Record,
#ssl_tls{epoch = Epoch,
type = ?HANDSHAKE,
version = _Version} when Epoch == CurrentEpoch-1 ->
- {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- {NextRecord, State} = next_record(State1),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
%% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
#ssl_tls{epoch = Epoch,
type = ?CHANGE_CIPHER_SPEC,
version = _Version} when Epoch == CurrentEpoch-1 ->
- {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- {NextRecord, State} = next_record(State1),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
_ ->
next_event(StateName, no_record, State0, Actions)
end;
next_event(StateName, Record,
#state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
case Record of
- no_record ->
- {next_state, StateName, State0, Actions};
#ssl_tls{epoch = CurrentEpoch,
version = Version} = Record ->
State = dtls_version(StateName, Version, State0),
@@ -237,8 +230,7 @@ next_event(StateName, Record,
#ssl_tls{epoch = _Epoch,
version = _Version} = _Record ->
%% TODO maybe buffer later epoch
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State, Actions);
+ next_event(StateName, no_record, State0, Actions);
#alert{} = Alert ->
{next_state, StateName, State0, [{next_event, internal, Alert} | Actions]}
end.
@@ -255,8 +247,7 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE,
try
case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of
{[], Buffers} ->
- {Record, State} = next_record(State0#state{protocol_buffers = Buffers}),
- next_event(StateName, Record, State);
+ next_event(StateName, no_record, State0#state{protocol_buffers = Buffers});
{Packets, Buffers} ->
State = State0#state{protocol_buffers = Buffers},
Events = dtls_handshake_events(Packets),
@@ -292,15 +283,12 @@ handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
renegotiate(#state{role = client} = State, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
- {next_state, connection, State,
- [{next_event, internal, #hello_request{}} | Actions]};
-
+ next_event(connection, no_record, State, [{next_event, internal, #hello_request{}} | Actions]);
renegotiate(#state{role = server} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
State1 = prepare_flight(State0),
- {State2, MoreActions} = send_handshake(HelloRequest, State1),
- {Record, State} = next_record(State2),
- next_event(hello, Record, State, Actions ++ MoreActions).
+ {State, MoreActions} = send_handshake(HelloRequest, State1),
+ next_event(hello, no_record, State, Actions ++ MoreActions).
send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) ->
#{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
@@ -394,23 +382,6 @@ protocol_name() ->
encode_data(Data, Version, ConnectionStates0)->
dtls_record:encode_data(Data, Version, ConnectionStates0).
-passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
- case Buffer of
- <<>> ->
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State);
- _ ->
- {Record, State} = ssl_connection:read_application_data(<<>>, State0),
- next_event(StateName, Record, State)
- end.
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
- {no_record ,State};
-
-next_record_if_active(State) ->
- next_record(State).
-
send(Transport, {_, {{_,_}, _} = Socket}, Data) ->
send(Transport, Socket, Data);
send(Transport, Socket, Data) ->
@@ -452,15 +423,14 @@ init({call, From}, {start, Timeout},
HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),
State1 = prepare_flight(State0#state{negotiated_version = Version}),
{State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
- State3 = State2#state{negotiated_version = Version, %% Requested version
- session =
- Session0#session{session_id = Hello#client_hello.session_id},
- start_or_recv_from = From,
- timer = Timer,
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
- },
- {Record, State} = next_record(State3),
- next_event(hello, Record, State, Actions);
+ State = State2#state{negotiated_version = Version, %% Requested version
+ session =
+ Session0#session{session_id = Hello#client_hello.session_id},
+ start_or_recv_from = From,
+ timer = Timer,
+ flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
+ },
+ next_event(hello, no_record, State, Actions);
init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->
Result = gen_handshake(?FUNCTION_NAME, Type, Event,
State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
@@ -470,7 +440,6 @@ init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->
max_ignored_alerts => 10}}),
erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
Result;
-
init({call, _} = Type, Event, #state{role = server} = State) ->
%% I.E. DTLS over sctp
gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable});
@@ -520,9 +489,9 @@ hello(internal, #client_hello{cookie = <<>>,
%% negotiated.
VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
State1 = prepare_flight(State0#state{negotiated_version = Version}),
- {State2, Actions} = send_handshake(VerifyRequest, State1),
- {Record, State} = next_record(State2),
- next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);
+ {State, Actions} = send_handshake(VerifyRequest, State1),
+ next_event(?FUNCTION_NAME, no_record,
+ State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);
hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,
host = Host, port = Port,
ssl_options = SslOpts,
@@ -541,14 +510,14 @@ hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,
State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}),
{State2, Actions} = send_handshake(Hello, State1),
- State3 = State2#state{negotiated_version = Version, %% Requested version
- session =
- Session0#session{session_id =
- Hello#client_hello.session_id}},
- {Record, State} = next_record(State3),
- next_event(?FUNCTION_NAME, Record, State, Actions);
-hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello},
- start_or_recv_from = From} = State) ->
+ State = State2#state{negotiated_version = Version, %% Requested version
+ session =
+ Session0#session{session_id =
+ Hello#client_hello.session_id}},
+ next_event(?FUNCTION_NAME, no_record, State, Actions);
+hello(internal, #client_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #ssl_options{handshake = hello},
+ start_or_recv_from = From} = State) ->
{next_state, user_hello, State#state{start_or_recv_from = undefined,
hello = Hello},
[{reply, From, {ok, Extensions}}]};
@@ -596,8 +565,7 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) ->
{next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]};
hello(internal, #change_cipher_spec{type = <<1>>}, State0) ->
{State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {Record, State2} = next_record(State1),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0),
+ {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
hello(info, Event, State) ->
@@ -648,8 +616,7 @@ certify(internal = Type, #server_hello_done{} = Event, State) ->
ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE);
certify(internal, #change_cipher_spec{type = <<1>>}, State0) ->
{State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {Record, State2} = next_record(State1),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0),
+ {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
certify(state_timeout, Event, State) ->
@@ -702,13 +669,11 @@ connection(internal, #hello_request{}, #state{host = Host, port = Port,
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),
State1 = prepare_flight(State0),
- {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
- {Record, State} =
- next_record(
- State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
- session = Session0#session{session_id
- = Hello#client_hello.session_id}}),
- next_event(hello, Record, State, Actions);
+ {State, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
+ next_event(hello, no_record, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
+ session = Session0#session{session_id
+ = Hello#client_hello.session_id}},
+ Actions);
connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
@@ -928,8 +893,7 @@ handle_state_timeout(flight_retransmission_timeout, StateName,
#state{flight_state = {retransmit, NextTimeout}} = State0) ->
{State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}},
retransmit_epoch(StateName, State0)),
- {Record, State2} = next_record(State1),
- {next_state, StateName, State, Actions} = next_event(StateName, Record, State2, Actions0),
+ {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions}.
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 55aa8174a3..3dbda2c91b 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,
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 524f06d52e..2c3f8bc20f 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -51,7 +51,7 @@
%% 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, groups/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
@@ -585,6 +585,13 @@ 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()}.
%%
@@ -988,16 +995,17 @@ handle_options(Opts0, Role, Host) ->
eccs = handle_eccs_option(proplists:get_value(eccs, Opts, eccs()),
HighestVersion),
supported_groups = handle_supported_groups_option(
- proplists:get_value(supported_groups, Opts, groups()),
+ proplists:get_value(supported_groups, Opts, groups(default)),
HighestVersion),
signature_algs =
handle_hashsigns_option(
proplists:get_value(
signature_algs,
Opts,
- default_option_role(server,
+ default_option_role_sign_algs(server,
tls_v1:default_signature_algs(HighestVersion),
- Role)),
+ Role,
+ HighestVersion)),
tls_version(HighestVersion)),
signature_algs_cert =
handle_signature_algorithms_option(
@@ -1330,15 +1338,25 @@ 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.
@@ -1755,11 +1773,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_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index ff3e0d9c90..c4b8e2172a 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -34,7 +34,7 @@
-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/3,
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,
@@ -46,6 +46,9 @@
is_stream_ciphersuite/1, signature_scheme/1,
scheme_to_components/1, hash_size/1]).
+%% RFC 8446 TLS 1.3
+-export([generate_client_shares/1, generate_server_share/1]).
+
-compile(inline).
-type cipher_enum() :: integer().
@@ -85,6 +88,24 @@ security_parameters(Version, CipherSuite, SecParams) ->
prf_algorithm = prf_algorithm(PrfHashAlg, Version),
hash_size = hash_size(Hash)}.
+security_parameters_1_3(SecParams, ClientRandom, CipherSuite) ->
+ #{cipher := Cipher,
+ mac := Hash,
+ prf := PrfHashAlg} = ssl_cipher_format:suite_definition(CipherSuite),
+ SecParams#security_parameters{
+ client_random = ClientRandom,
+ cipher_suite = CipherSuite,
+ bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
+ cipher_type = type(Cipher),
+ key_size = effective_key_bits(Cipher),
+ expanded_key_material_length = expanded_key_material(Cipher),
+ key_material_length = key_material(Cipher),
+ iv_size = iv_size(Cipher),
+ mac_algorithm = mac_algorithm(Hash),
+ prf_algorithm =prf_algorithm(PrfHashAlg, {3,4}),
+ hash_size = hash_size(Hash),
+ compression_algorithm = 0}.
+
%%--------------------------------------------------------------------
-spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}.
%%
@@ -1188,3 +1209,36 @@ 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(FFDHE) ->
+ public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 2abc678ed9..5d5fd6e450 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -52,8 +52,8 @@
%% Alert and close handling
-export([handle_own_alert/4, handle_alert/3,
- handle_normal_shutdown/3, stop/2, stop_and_reply/3
- ]).
+ handle_normal_shutdown/3, stop/2, stop_and_reply/3,
+ handle_trusted_certs_db/1]).
%% Data handling
-export([read_application_data/2, internal_renegotiation/2]).
@@ -407,9 +407,8 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
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}),
- State1 = Connection:reinit_handshake_data(State0),
- {Record, State} = Connection:next_record(State1#state{renegotiation = undefined}),
- Connection:next_event(connection, Record, State);
+ State = Connection:reinit_handshake_data(State0),
+ Connection:next_event(connection, no_record, State#state{renegotiation = undefined});
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
#state{role = Role,
@@ -419,23 +418,36 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
Connection:protocol_name(), StateName,
Alert#alert{role = opposite_role(Role)}),
gen_statem:reply(From, {error, renegotiation_rejected}),
- {Record, State1} = Connection:next_record(State0),
%% Go back to connection!
- State = Connection:reinit(State1#state{renegotiation = undefined}),
- Connection:next_event(connection, Record, State);
+ State = Connection:reinit(State0#state{renegotiation = undefined}),
+ Connection:next_event(connection, no_record, State);
%% Gracefully log and ignore all other warning alerts
handle_alert(#alert{level = ?WARNING} = Alert, StateName,
- #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State0) ->
+ #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State) ->
log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName,
Alert#alert{role = opposite_role(Role)}),
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(StateName, Record, State).
+ Connection:next_event(StateName, no_record, State).
%%====================================================================
%% Data handling
%%====================================================================
+
+passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) ->
+ case Buffer of
+ <<>> ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_event(StateName, Record, State);
+ _ ->
+ case read_application_data(<<>>, State0) of
+ {stop, _, _} = ShutdownError ->
+ ShutdownError;
+ {Record, State} ->
+ Connection:next_event(StateName, Record, State)
+ end
+ end.
+
read_application_data(Data, #state{user_application = {_Mon, Pid},
socket = Socket,
protocol_cb = Connection,
@@ -478,28 +490,26 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
Buffer =:= <<>> ->
%% Passive mode, wait for active once or recv
%% Active and empty, get more data
- Connection:next_record_if_active(State);
+ {no_record, State};
true -> %% We have more data
read_application_data(<<>>, State)
end
end;
{more, Buffer} -> % no reply, we need more data
- Connection:next_record(State0#state{user_data_buffer = Buffer});
+ {no_record, State0#state{user_data_buffer = Buffer}};
{passive, Buffer} ->
- Connection:next_record_if_active(State0#state{user_data_buffer = Buffer});
+ {no_record, State0#state{user_data_buffer = Buffer}};
{error,_Reason} -> %% Invalid packet in packet mode
deliver_packet_error(Connection:pids(State0),
Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection),
stop(normal, State0)
end.
-dist_app_data(ClientData, #state{protocol_cb = Connection,
- erl_dist_data = #{dist_handle := undefined,
+dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := undefined,
dist_buffer := DistBuff} = DistData} = State) ->
- Connection:next_record_if_active(State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}});
+ {no_record, State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}};
dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle,
dist_buffer := DistBuff} = ErlDistData,
- protocol_cb = Connection,
user_data_buffer = Buffer,
socket_options = SOpts} = State) ->
Data = merge_dist_data(DistBuff, ClientData),
@@ -508,7 +518,7 @@ dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle,
Buffer =:= <<>> ->
%% Passive mode, wait for active once or recv
%% Active and empty, get more data
- Connection:next_record_if_active(State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}});
+ {no_record, State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}};
_ -> %% We have more data
read_application_data(<<>>, State)
catch error:_ ->
@@ -612,9 +622,7 @@ ssl_config(Opts, Role, State0, Type) ->
init({call, From}, {start, Timeout}, State0, Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, From),
- {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From,
- timer = Timer}),
- Connection:next_event(hello, Record, State);
+ Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer});
init({call, From}, {start, {Opts, EmOpts}, Timeout},
#state{role = Role, ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0, Connection) ->
@@ -727,20 +735,19 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{role = server, expecting_next_protocol_negotiation = true} = State0,
+ #state{role = server, expecting_next_protocol_negotiation = true} = State,
Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
- Connection:next_event(?FUNCTION_NAME, Record,
- State#state{expecting_next_protocol_negotiation = false});
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false});
abbreviated(internal,
#change_cipher_spec{type = <<1>>},
- #state{connection_states = ConnectionStates0} = State0, Connection) ->
+ #state{connection_states = ConnectionStates0} = State, Connection) ->
ConnectionStates1 =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- {Record, State} = Connection:next_record(State0#state{connection_states =
- ConnectionStates1}),
- Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true});
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
+ ConnectionStates1,
+ expecting_finished = true});
abbreviated(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
abbreviated(Type, Msg, State, Connection) ->
@@ -769,9 +776,7 @@ certify(internal, #certificate{asn1_certificates = []},
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = false}} =
State0, Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{client_certificate_requested = false}),
- Connection:next_event(?FUNCTION_NAME, Record, State);
+ Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
certify(internal, #certificate{},
#state{role = server,
negotiated_version = Version,
@@ -839,26 +844,25 @@ certify(internal, #certificate_request{},
Version, ?FUNCTION_NAME, State);
certify(internal, #certificate_request{},
#state{session = #session{own_certificate = undefined},
- role = client} = State0, Connection) ->
+ role = client} = State, Connection) ->
%% The client does not have a certificate and will send an empty reply, the server may fail
%% or accept the connection by its own preference. No signature algorihms needed as there is
%% no certificate to verify.
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(?FUNCTION_NAME, Record, State#state{client_certificate_requested = true});
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
certify(internal, #certificate_request{} = CertRequest,
#state{session = #session{own_certificate = Cert},
role = client,
ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
- negotiated_version = Version} = State0, Connection) ->
+ negotiated_version = Version} = State, Connection) ->
case ssl_handshake:select_hashsign(CertRequest, Cert,
SupportedHashSigns,
ssl:tls_version(Version)) of
#alert {} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
- NegotiatedHashSign ->
- {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
- Connection:next_event(?FUNCTION_NAME, Record,
- State#state{cert_hashsign_algorithm = NegotiatedHashSign})
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+ NegotiatedHashSign ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{client_certificate_requested = true,
+ cert_hashsign_algorithm = NegotiatedHashSign})
end;
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(internal, #server_hello_done{},
@@ -967,7 +971,7 @@ cipher(internal, #certificate_verify{signature = Signature,
negotiated_version = Version,
session = #session{master_secret = MasterSecret},
tls_handshake_history = Handshake
- } = State0, Connection) ->
+ } = State, Connection) ->
TLSVersion = ssl:tls_version(Version),
%% Use negotiated value if TLS-1.2 otherwhise return default
@@ -975,11 +979,10 @@ cipher(internal, #certificate_verify{signature = Signature,
case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
TLSVersion, HashSign, MasterSecret, Handshake) of
valid ->
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(?FUNCTION_NAME, Record,
+ Connection:next_event(?FUNCTION_NAME, no_record,
State#state{cert_hashsign_algorithm = HashSign});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
%% client must send a next protocol message if we are expecting it
cipher(internal, #finished{},
@@ -1013,18 +1016,18 @@ cipher(internal, #finished{verify_data = Data} = Finished,
%% & before finished message and it is not allowed during renegotiation
cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
#state{role = server, expecting_next_protocol_negotiation = true,
- expecting_finished = true} = State0, Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
- Connection:next_event(?FUNCTION_NAME, Record,
- State#state{expecting_next_protocol_negotiation = false});
+ expecting_finished = true} = State, Connection) ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{expecting_next_protocol_negotiation = false,
+ negotiated_protocol = SelectedProtocol
+ });
cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} =
- State0, Connection) ->
- ConnectionStates1 =
+ State, Connection) ->
+ ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- {Record, State} = Connection:next_record(State0#state{connection_states =
- ConnectionStates1}),
- Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true});
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
+ ConnectionStates,
+ expecting_finished = true});
cipher(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
@@ -1037,9 +1040,9 @@ connection({call, RecvFrom}, {recv, N, Timeout},
#state{protocol_cb = Connection, socket_options =
#socket_options{active = false}} = State0, Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
- Connection:passive_receive(State0#state{bytes_to_read = N,
- start_or_recv_from = RecvFrom,
- timer = Timer}, ?FUNCTION_NAME);
+ passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom,
+ timer = Timer}, ?FUNCTION_NAME, Connection);
connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State,
Connection) ->
Connection:renegotiate(State#state{renegotiation = {true, From}}, []);
@@ -1081,7 +1084,7 @@ connection(cast, {dist_handshake_complete, DHandle},
connection(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
connection(internal, {recv, _}, State, Connection) ->
- Connection:passive_receive(State, ?FUNCTION_NAME);
+ passive_receive(State, ?FUNCTION_NAME, Connection);
connection(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
@@ -1100,6 +1103,12 @@ downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) ->
gen_statem:reply(From, {error, timeout}),
stop(normal, State);
+downgrade(
+ info, {CloseTag, Socket},
+ #state{socket = Socket, close_tag = CloseTag, downgrade = {_, From}} =
+ State, _) ->
+ gen_statem:reply(From, {error, CloseTag}),
+ stop(normal, State);
downgrade(Type, Event, State, Connection) ->
handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection).
@@ -1134,15 +1143,15 @@ handle_common_event(internal, {application_data, Data}, StateName, State0, Conne
case read_application_data(Data, State0) of
{stop, _, _} = Stop->
Stop;
- {Record, State} ->
- case Connection:next_event(StateName, Record, State) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, []);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, Actions);
- {stop, _, _} = Stop ->
- Stop
- end
+ {Record, State1} ->
+ case Connection:next_event(StateName, Record, State1) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, []);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, Actions);
+ {stop, _, _} = Stop ->
+ Stop
+ end
end;
handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
#state{negotiated_version = Version} = State, _) ->
@@ -1172,23 +1181,31 @@ handle_call({close, _} = Close, From, StateName, State, _Connection) ->
stop_and_reply(
{shutdown, normal},
{reply, From, Result}, State#state{terminated = true});
-handle_call({shutdown, How0}, From, StateName,
+handle_call({shutdown, read_write = How}, From, StateName,
#state{transport_cb = Transport,
socket = Socket} = State, _) ->
- case How0 of
- How when How == write; How == both ->
- send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- StateName, State);
- _ ->
- ok
- end,
+ try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ StateName, State) of
+ _ ->
+ case Transport:shutdown(Socket, How) of
+ ok ->
+ {next_state, StateName, State#state{terminated = true}, [{reply, From, ok}]};
+ Error ->
+ {stop, StateName, State#state{terminated = true}, [{reply, From, Error}]}
+ end
+ catch
+ throw:Return ->
+ Return
+ end;
+handle_call({shutdown, How0}, From, StateName,
+ #state{transport_cb = Transport,
+ socket = Socket} = State, _) ->
case Transport:shutdown(Socket, How0) of
ok ->
- {keep_state_and_data, [{reply, From, ok}]};
+ {next_state, StateName, State, [{reply, From, ok}]};
Error ->
- gen_statem:reply(From, {error, Error}),
- stop(normal, State)
+ {stop, StateName, State, [{reply, From, Error}]}
end;
handle_call({recv, _N, _Timeout}, From, _,
#state{socket_options =
@@ -1350,15 +1367,15 @@ terminate(downgrade = Reason, connection, #state{protocol_cb = Connection,
handle_trusted_certs_db(State),
Connection:close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, connection, #state{protocol_cb = Connection,
- connection_states = ConnectionStates,
- ssl_options = #ssl_options{padding_check = Check},
- transport_cb = Transport, socket = Socket
- } = State) ->
+ connection_states = ConnectionStates,
+ ssl_options = #ssl_options{padding_check = Check},
+ transport_cb = Transport, socket = Socket
+ } = State) ->
handle_trusted_certs_db(State),
Alert = terminate_alert(Reason),
%% Send the termination ALERT if possible
- catch (ok = Connection:send_alert_in_connection(Alert, State)),
- Connection:close(Reason, Socket, Transport, ConnectionStates, Check);
+ catch (Connection:send_alert_in_connection(Alert, State)),
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection,
socket = Socket
} = State) ->
@@ -1514,13 +1531,12 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite,
negotiated_version = Version} = State0, Connection) ->
try server_certify_and_key_exchange(State0, Connection) of
#state{} = State1 ->
- {State2, Actions} = server_hello_done(State1, Connection),
+ {State, Actions} = server_hello_done(State1, Connection),
Session =
Session0#session{session_id = SessionId,
cipher_suite = CipherSuite,
compression_method = Compression},
- {Record, State} = Connection:next_record(State2#state{session = Session}),
- Connection:next_event(certify, Record, State, Actions)
+ Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
catch
#alert{} = Alert ->
handle_own_alert(Alert, Version, hello, State0)
@@ -1535,10 +1551,9 @@ resumed_server_hello(#state{session = Session,
{_, ConnectionStates1} ->
State1 = State0#state{connection_states = ConnectionStates1,
session = Session},
- {State2, Actions} =
+ {State, Actions} =
finalize_handshake(State1, abbreviated, Connection),
- {Record, State} = Connection:next_record(State2),
- Connection:next_event(abbreviated, Record, State, Actions);
+ Connection:next_event(abbreviated, no_record, State, Actions);
#alert{} = Alert ->
handle_own_alert(Alert, Version, hello, State0)
end.
@@ -1560,10 +1575,8 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
Session#session{peer_certificate = PeerCert},
public_key_info = PublicKeyInfo},
#{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite),
- State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
-
- {Record, State} = Connection:next_record(State2),
- Connection:next_event(certify, Record, State).
+ State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
+ Connection:next_event(certify, no_record, State).
handle_peer_cert_key(client, _,
{?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
@@ -1621,11 +1634,10 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} =
try do_client_certify_and_key_exchange(State0, Connection) of
State1 = #state{} ->
{State2, Actions} = finalize_handshake(State1, certify, Connection),
- State3 = State2#state{
- %% Reinitialize
- client_certificate_requested = false},
- {Record, State} = Connection:next_record(State3),
- Connection:next_event(cipher, Record, State, Actions)
+ State = State2#state{
+ %% Reinitialize
+ client_certificate_requested = false},
+ Connection:next_event(cipher, no_record, State, Actions)
catch
throw:#alert{} = Alert ->
handle_own_alert(Alert, Version, certify, State0)
@@ -2034,10 +2046,9 @@ calculate_master_secret(PremasterSecret,
ConnectionStates0, server) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
- State1 = State0#state{connection_states = ConnectionStates,
+ State = State0#state{connection_states = ConnectionStates,
session = Session},
- {Record, State} = Connection:next_record(State1),
- Connection:next_event(Next, Record, State);
+ Connection:next_event(Next, no_record, State);
#alert{} = Alert ->
handle_own_alert(Alert, Version, certify, State0)
end.
@@ -2110,10 +2121,9 @@ calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
calculate_secret(#server_psk_params{
hint = IdentityHint},
- State0, Connection) ->
+ State, Connection) ->
%% store for later use
- {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}),
- Connection:next_event(certify, Record, State);
+ Connection:next_event(certify, no_record, State#state{psk_identity = IdentityHint});
calculate_secret(#server_dhe_psk_params{
dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
@@ -2390,9 +2400,8 @@ prepare_connection(#state{renegotiation = Renegotiate,
start_or_recv_from = RecvFrom} = State0, Connection)
when Renegotiate =/= {false, first},
RecvFrom =/= undefined ->
- State1 = Connection:reinit(State0),
- {Record, State} = Connection:next_record(State1),
- {Record, ack_connection(State)};
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)};
prepare_connection(State0, Connection) ->
State = Connection:reinit(State0),
{no_record, ack_connection(State)}.
@@ -2446,26 +2455,23 @@ handle_new_session(NewId, CipherSuite, Compression,
Session = Session0#session{session_id = NewId,
cipher_suite = CipherSuite,
compression_method = Compression},
- {Record, State} = Connection:next_record(State0#state{session = Session}),
- Connection:next_event(certify, Record, State).
+ Connection:next_event(certify, no_record, State0#state{session = Session}).
handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
negotiated_version = Version,
host = Host, port = Port,
protocol_cb = Connection,
session_cache = Cache,
- session_cache_cb = CacheCb} = State0) ->
+ session_cache_cb = CacheCb} = State) ->
Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{_, ConnectionStates} ->
- {Record, State} =
- Connection:next_record(State0#state{
- connection_states = ConnectionStates,
- session = Session}),
- Connection:next_event(abbreviated, Record, State);
+ Connection:next_event(abbreviated, no_record, State#state{
+ connection_states = ConnectionStates,
+ session = Session});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
+ handle_own_alert(Alert, Version, hello, State)
end.
make_premaster_secret({MajVer, MinVer}, rsa) ->
@@ -2515,10 +2521,7 @@ handle_active_option(false, connection = StateName, To, Reply, State) ->
handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection,
user_data_buffer = <<>>} = State0) ->
- %% Need data, set active once
- {Record, State1} = Connection:next_record_if_active(State0),
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State1) of
+ case Connection:next_event(StateName0, no_record, State0) of
{next_state, StateName, State} ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
{next_state, StateName, State, Actions} ->
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 66e3182313..91467e9b26 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -96,7 +96,8 @@
%% The mecahnism is also usefull in TLS although we do not
%% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
- protocol_specific = #{} :: map()
+ protocol_specific = #{} :: map(),
+ key_share
}).
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
#'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME,
diff --git a/lib/ssl/src/ssl_dh_groups.erl b/lib/ssl/src/ssl_dh_groups.erl
index 36c97ed13e..20d53de430 100644
--- a/lib/ssl/src/ssl_dh_groups.erl
+++ b/lib/ssl/src/ssl_dh_groups.erl
@@ -20,12 +20,15 @@
-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]).
+ ffdhe8192_generator/0, ffdhe8192_prime/0,
+ dh_params/1]).
%% RFC3526 - 2048-bit MODP Group
%% This group is assigned id 14.
@@ -441,3 +444,24 @@ ffdhe8192_prime() ->
"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 39a627c0a2..417e5d9eb6 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,
@@ -60,7 +61,7 @@
-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_vector/1, decode_hello_extensions/3, decode_extensions/2,
+-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/3, 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, empty_extensions/2
+ select_hashsign_algs/3, empty_extensions/2, add_server_share/2
]).
+-export([get_cert_params/1]).
+
%%====================================================================
%% Create handshake messages
%%====================================================================
@@ -679,12 +682,29 @@ encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) ->
VerLen = byte_size(Versions),
Len = VerLen + 2,
encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT),
- ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>);
+ ?UINT16(Len), ?UINT16(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>>).
+ ?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;
@@ -726,7 +746,7 @@ decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32
Cipher_suite:2/binary, ?BYTE(Comp_method),
?UINT16(ExtLen), Extensions:ExtLen/binary>>) ->
- HelloExtensions = decode_hello_extensions(Extensions, Version, server),
+ HelloExtensions = decode_hello_extensions(Extensions, Version, server_hello),
#server_hello{
server_version = {Major,Minor},
@@ -783,25 +803,27 @@ decode_vector(<<?UINT16(Len), Vector:Len/binary>>) ->
Vector.
%%--------------------------------------------------------------------
--spec decode_hello_extensions(binary(), ssl_record:ssl_version(), client | server) -> map().
+-spec decode_hello_extensions(binary(), ssl_record:ssl_version(), atom()) -> map().
%%
%% Description: Decodes TLS hello extensions
%%--------------------------------------------------------------------
-decode_hello_extensions(Extensions, Version, Role) ->
+decode_hello_extensions(Extensions, Version, MessageType0) ->
+ %% Convert legacy atoms
MessageType =
- case Role of
+ case MessageType0 of
client -> client_hello;
- server -> server_hello
+ server -> server_hello;
+ T -> T
end,
- decode_extensions(Extensions, Version, empty_extensions(Version, MessageType)).
+ decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)).
%%--------------------------------------------------------------------
--spec decode_extensions(binary(),tuple()) -> map().
+-spec decode_extensions(binary(),tuple(), atom()) -> map().
%%
%% Description: Decodes TLS hello extensions
%%--------------------------------------------------------------------
-decode_extensions(Extensions, Version) ->
- decode_extensions(Extensions, Version, empty_extensions()).
+decode_extensions(Extensions, Version, MessageType) ->
+ decode_extensions(Extensions, Version, MessageType, empty_extensions()).
%%--------------------------------------------------------------------
-spec decode_server_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) ->
@@ -1011,21 +1033,20 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) ->
%%====================================================================
%% Extensions handling
%%====================================================================
-client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) ->
+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).
+ maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare).
-add_tls12_extensions(Version,
- #ssl_options{signature_algs = SupportedHashSigns} = SslOpts,
+add_tls12_extensions(_Version,
+ SslOpts,
ConnectionStates,
Renegotiation) ->
SRP = srp_user(SslOpts),
#{renegotiation_info => renegotiation_info(tls_record, client,
ConnectionStates, Renegotiation),
srp => SRP,
- signature_algs => available_signature_algs(SupportedHashSigns, Version),
alpn => encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation),
next_protocol_negotiation =>
encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
@@ -1038,16 +1059,19 @@ add_common_extensions({3,4},
HelloExtensions,
_CipherSuites,
#ssl_options{eccs = SupportedECCs,
- supported_groups = Groups}) ->
+ supported_groups = Groups,
+ signature_algs = SignatureSchemes}) ->
{EcPointFormats, _} =
client_ecc_extensions(SupportedECCs),
HelloExtensions#{ec_point_formats => EcPointFormats,
- elliptic_curves => Groups};
+ elliptic_curves => Groups,
+ signature_algs => signature_algs_ext(SignatureSchemes)};
-add_common_extensions(_Version,
+add_common_extensions(Version,
HelloExtensions,
CipherSuites,
- #ssl_options{eccs = SupportedECCs}) ->
+ #ssl_options{eccs = SupportedECCs,
+ signature_algs = SupportedHashSigns}) ->
{EcPointFormats, EllipticCurves} =
case advertises_ec_ciphers(
@@ -1059,20 +1083,93 @@ add_common_extensions(_Version,
{undefined, undefined}
end,
HelloExtensions#{ec_point_formats => EcPointFormats,
- elliptic_curves => EllipticCurves}.
+ elliptic_curves => EllipticCurves,
+ signature_algs => available_signature_algs(SupportedHashSigns, Version)}.
maybe_add_tls13_extensions({3,4},
- HelloExtensions,
+ HelloExtensions0,
#ssl_options{signature_algs_cert = SignatureSchemes,
- versions = SupportedVersions}) ->
- HelloExtensions#{client_hello_versions =>
- #client_hello_versions{versions = SupportedVersions},
- signature_algs_cert =>
- signature_algs_cert(SignatureSchemes)};
-maybe_add_tls13_extensions(_, HelloExtensions, _) ->
+ 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(SignatureSchemes) ->
+ #signature_algorithms{signature_scheme_list = SignatureSchemes}.
+
signature_algs_cert(undefined) ->
undefined;
signature_algs_cert(SignatureSchemes) ->
@@ -1948,6 +2045,20 @@ encode_versions([], 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].
@@ -2090,19 +2201,19 @@ 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)).
-decode_extensions(<<>>, _Version, Acc) ->
+decode_extensions(<<>>, _Version, _MessageType, Acc) ->
Acc;
decode_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
- ExtensionData:Len/binary, Rest/binary>>, Version, Acc)
+ ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
when Len + 2 =:= ExtLen ->
ALPN = #alpn{extension_data = ExtensionData},
- decode_extensions(Rest, Version, Acc#{alpn => ALPN});
+ decode_extensions(Rest, Version, MessageType, Acc#{alpn => ALPN});
decode_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len),
- ExtensionData:Len/binary, Rest/binary>>, Version, Acc) ->
+ ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
NextP = #next_protocol_negotiation{extension_data = ExtensionData},
- decode_extensions(Rest, Version, Acc#{next_protocol_negotiation => NextP});
+ decode_extensions(Rest, Version, MessageType, Acc#{next_protocol_negotiation => NextP});
decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len),
- Info:Len/binary, Rest/binary>>, Version, Acc) ->
+ 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
@@ -2111,49 +2222,53 @@ decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len),
<<?BYTE(VerifyLen), VerifyInfo/binary>> = Info,
VerifyInfo
end,
- decode_extensions(Rest, Version, Acc#{renegotiation_info =>
- #renegotiation_info{renegotiated_connection =
- RenegotiateInfo}});
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{renegotiation_info =>
+ #renegotiation_info{renegotiated_connection =
+ RenegotiateInfo}});
decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
- SRP:SRPLen/binary, Rest/binary>>, Version, Acc)
+ SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc)
when Len == SRPLen + 2 ->
- decode_extensions(Rest, Version, Acc#{srp => #srp{username = SRP}});
+ decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, Acc)
+ 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],
- decode_extensions(Rest, Version, Acc#{signature_algs =>
- #hash_sign_algos{hash_sign_algos =
- HashSignAlgos}});
+ 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, Acc)
+ 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, Acc#{signature_algs =>
- #signature_algorithms{
- signature_scheme_list = SignSchemes}});
+ 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, Acc) ->
+ 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, Acc#{signature_algs_cert =>
- #signature_algorithms_cert{
- signature_scheme_list = SignSchemes}});
+ 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, Acc)
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
when Version < {3,4} ->
<<?UINT16(_), EllipticCurveList/binary>> = ExtData,
%% Ignore unknown curves
@@ -2166,13 +2281,13 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
end
end,
EllipticCurves = lists:filtermap(Pick, [ECC || <<ECC:16>> <= EllipticCurveList]),
- decode_extensions(Rest, Version, Acc#{elliptic_curves =>
- #elliptic_curves{elliptic_curve_list =
- EllipticCurves}});
-
+ 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, Acc)
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
when Version =:= {3,4} ->
<<?UINT16(_), GroupList/binary>> = ExtData,
%% Ignore unknown curves
@@ -2185,47 +2300,84 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
end
end,
SupportedGroups = lists:filtermap(Pick, [Group || <<Group:16>> <= GroupList]),
- decode_extensions(Rest, Version, Acc#{elliptic_curves =>
- #supported_groups{supported_groups =
- SupportedGroups}});
+ 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, Acc) ->
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
<<?BYTE(_), ECPointFormatList/binary>> = ExtData,
ECPointFormats = binary_to_list(ECPointFormatList),
- decode_extensions(Rest, Version, Acc#{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, Acc) when Len == 0 ->
- decode_extensions(Rest, Version, Acc#{sni => #sni{hostname = ""}}); %% Server may send an empy SNI
+ 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, Acc) ->
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
<<?UINT16(_), NameList/binary>> = ExtData,
- decode_extensions(Rest, Version, Acc#{sni => dec_sni(NameList)});
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{sni => dec_sni(NameList)});
decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, Acc) when Len > 2 ->
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 ->
<<?UINT16(_),Versions/binary>> = ExtData,
- decode_extensions(Rest, Version, Acc#{client_hello_versions =>
- #client_hello_versions{
- versions = decode_versions(Versions)}});
+ 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, Acc)
+ ?UINT16(SelectedVersion), Rest/binary>>, Version, MessageType, Acc)
when Len =:= 2, SelectedVersion =:= 16#0304 ->
- decode_extensions(Rest, Version, Acc#{server_hello_selected_version =>
- #server_hello_selected_version{selected_version =
- {3,4}}});
+ 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)}});
+
%% Ignore data following the ClientHello (i.e.,
%% extensions) if not understood.
-decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, Acc) ->
- decode_extensions(Rest, Version, 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.
-decode_extensions(_, _, Acc) ->
+decode_extensions(_, _, _, Acc) ->
Acc.
dec_hashsign(<<?BYTE(HashAlgo), ?BYTE(SignAlgo)>>) ->
@@ -2252,6 +2404,17 @@ 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, []).
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 1fd143a641..d4233bea9b 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -107,7 +107,8 @@
sni,
client_hello_versions,
server_hello_selected_version,
- signature_algs_cert
+ signature_algs_cert,
+ key_share
}).
-record(server_hello, {
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 48798799f7..a079c6a796 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -61,6 +61,7 @@
-define(CDR_MAGIC, "GIOP").
-define(CDR_HDR_SIZE, 12).
+-define(INTERNAL_ACTIVE_N, 100).
-define(DEFAULT_TIMEOUT, 5000).
-define(NO_DIST_POINT, "http://dummy/no_distribution_point").
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 29988edf76..ae784380c8 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -47,7 +47,7 @@
-export([start_fsm/8, start_link/8, init/1, pids/1]).
%% State transition handling
--export([next_record/1, next_event/3, next_event/4,
+-export([next_event/3, next_event/4,
handle_common_event/4]).
%% Handshake handling
@@ -62,7 +62,7 @@
encode_alert/3, close/5, protocol_name/0]).
%% Data handling
--export([encode_data/3, passive_receive/2, next_record_if_active/1,
+-export([encode_data/3, next_record/1,
send/3, socket/5, setopts/3, getopts/3]).
%% gen_statem state functions
@@ -88,6 +88,7 @@
%% 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}]).
@@ -180,30 +181,30 @@ next_record(#state{protocol_buffers =
{Alert, State}
end;
next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []},
- socket = Socket,
+ protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
+ socket = Socket,
close_tag = CloseTag,
transport_cb = Transport} = State) ->
- case tls_socket:setopts(Transport, Socket, [{active,once}]) of
- ok ->
- {no_record, State};
- _ ->
- self() ! {CloseTag, Socket},
- {no_record, State}
- end;
+ case tls_socket:setopts(Transport, Socket, [{active, N}]) of
+ ok ->
+ {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
+ _ ->
+ self() ! {CloseTag, Socket},
+ {no_record, State}
+ end;
next_record(State) ->
{no_record, State}.
next_event(StateName, Record, State) ->
next_event(StateName, Record, State, []).
-
-next_event(connection = StateName, no_record, State0, Actions) ->
- case next_record_if_active(State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {#ssl_tls{} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#alert{} = Alert, State} ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+next_event(StateName, no_record, State0, Actions) ->
+ case next_record(State0) of
+ {no_record, State} ->
+ {next_state, StateName, State, Actions};
+ {#ssl_tls{} = Record, State} ->
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
+ {#alert{} = Alert, State} ->
+ {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end;
next_event(StateName, Record, State, Actions) ->
case Record of
@@ -225,23 +226,23 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
negotiated_version = Version,
ssl_options = Options} = State0) ->
try
- {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options),
- State1 =
+ EffectiveVersion = effective_version(Version, Options),
+ {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
+ State =
State0#state{protocol_buffers =
Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
case Packets of
[] ->
assert_buffer_sanity(Buf, Options),
- {Record, State} = next_record(State1),
- next_event(StateName, Record, State);
+ next_event(StateName, no_record, State);
_ ->
Events = tls_handshake_events(Packets),
case StateName of
connection ->
- ssl_connection:hibernate_after(StateName, State1, Events);
+ ssl_connection:hibernate_after(StateName, State, Events);
_ ->
{next_state, StateName,
- State1#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
end
end
catch throw:#alert{} = Alert ->
@@ -296,11 +297,10 @@ renegotiate(#state{role = server,
{BinMsg, ConnectionStates} =
tls_record:encode_handshake(Frag, Version, ConnectionStates0),
send(Transport, Socket, BinMsg),
- State1 = State0#state{connection_states =
+ State = State0#state{connection_states =
ConnectionStates,
tls_handshake_history = Hs0},
- {Record, State} = next_record(State1),
- next_event(hello, Record, State, Actions).
+ next_event(hello, no_record, State, Actions).
send_handshake(Handshake, State) ->
send_handshake_flight(queue_handshake(Handshake, State)).
@@ -406,13 +406,11 @@ send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) ->
send_alert_in_connection(Alert,
#state{protocol_specific = #{sender := Sender}}) ->
tls_sender:send_alert(Sender, Alert).
-send_sync_alert(Alert, #state{protocol_specific = #{sender := Sender}}= State) ->
- tls_sender:send_and_ack_alert(Sender, Alert),
- receive
- {Sender, ack_alert} ->
- ok
- after ?DEFAULT_TIMEOUT ->
- %% Sender is blocked terminate anyway
+send_sync_alert(
+ Alert, #state{protocol_specific = #{sender := Sender}} = State) ->
+ try tls_sender:send_and_ack_alert(Sender, Alert)
+ catch
+ _:_ ->
throw({stop, {shutdown, own_alert}, State})
end.
@@ -450,23 +448,6 @@ protocol_name() ->
encode_data(Data, Version, ConnectionStates0)->
tls_record:encode_data(Data, Version, ConnectionStates0).
-passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
- case Buffer of
- <<>> ->
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State);
- _ ->
- {Record, State} = ssl_connection:read_application_data(<<>>, State0),
- next_event(StateName, Record, State)
- end.
-
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
- {no_record ,State};
-next_record_if_active(State) ->
- next_record(State).
-
send(Transport, Socket, Data) ->
tls_socket:send(Transport, Socket, Data).
@@ -498,12 +479,12 @@ init({call, From}, {start, Timeout},
session_cache = Cache,
session_cache_cb = CacheCb
} = State0) ->
+ KeyShare = maybe_generate_client_shares(SslOpts),
Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
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),
@@ -516,15 +497,16 @@ init({call, From}, {start, Timeout},
message => Hello},
ssl_logger:debug(SslOpts#ssl_options.log_level, HelloMsg, #{domain => [otp,ssl,handshake]}),
ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
- State1 = State0#state{connection_states = ConnectionStates,
- negotiated_version = Version, %% Requested version
- session =
- Session0#session{session_id = Hello#client_hello.session_id},
- tls_handshake_history = Handshake,
- start_or_recv_from = From,
- timer = Timer},
- {Record, State} = next_record(State1),
- next_event(hello, Record, State);
+ State = State0#state{connection_states = ConnectionStates,
+ negotiated_version = HelloVersion, %% Requested version
+ session =
+ Session0#session{session_id = Hello#client_hello.session_id},
+ tls_handshake_history = Handshake,
+ start_or_recv_from = From,
+ timer = Timer,
+ key_share = KeyShare},
+ next_event(hello, no_record, State);
+
init(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
@@ -569,41 +551,36 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
negotiated_protocol = CurrentProtocol,
key_algorithm = KeyExAlg,
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{negotiated_version
- = ClientVersion});
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} when Version < {3,4} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
- gen_handshake(?FUNCTION_NAME, internal, {common_client_hello, Type, ServerHelloExt},
- State#state{connection_states = ConnectionStates,
- negotiated_version = Version,
- hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
- session = Session,
- negotiated_protocol = Protocol});
- %% TLS 1.3
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
- tls_connection_1_3:gen_handshake(?FUNCTION_NAME,
- internal,
- {common_client_hello, Type, ServerHelloExt},
- State#state{connection_states = ConnectionStates,
- negotiated_version = Version,
- hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
- session = Session,
- negotiated_protocol = Protocol})
+ case choose_tls_version(SslOpts, Hello) of
+ 'tls_v1.3' ->
+ %% Continue in TLS 1.3 'start' state
+ {next_state, start, State, [{next_event, internal, Hello}]};
+ 'tls_v1.2' ->
+ case tls_handshake:hello(Hello,
+ SslOpts,
+ {Port, Session0, Cache, CacheCb,
+ ConnectionStates0, Cert, KeyExAlg},
+ Renegotiation) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, ClientVersion, hello,
+ State#state{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,
+ negotiated_version = Version,
+ hashsign_algorithm = HashSign,
+ client_hello_version = ClientVersion,
+ session = Session,
+ negotiated_protocol = Protocol})
+ end
end;
hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
@@ -674,37 +651,34 @@ connection(internal, #hello_request{},
ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
- {State1, Actions} = send_handshake(Hello, State0),
- {Record, State} =
- next_record(
- State1#state{session = Session0#session{session_id
- = Hello#client_hello.session_id}}),
- next_event(hello, Record, State, Actions);
+ Cache, CacheCb, Renegotiation, Cert, undefined),
+ {State, Actions} = send_handshake(Hello, State0),
+ next_event(hello, no_record, State#state{session = Session0#session{session_id
+ = Hello#client_hello.session_id}}, Actions);
connection(internal, #client_hello{} = Hello,
#state{role = server, allow_renegotiate = true, connection_states = CS,
%%protocol_cb = Connection,
protocol_specific = #{sender := Sender}
- } = State0) ->
+ } = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {Record, State} = next_record(State0#state{allow_renegotiate = false,
- renegotiation = {true, peer}}),
{ok, Write} = tls_sender:renegotiate(Sender),
- next_event(hello, Record, State#state{connection_states = CS#{current_write => Write}},
+ next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write},
+ allow_renegotiate = false,
+ renegotiation = {true, peer}
+ },
[{next_event, internal, Hello}]);
connection(internal, #client_hello{},
#state{role = server, allow_renegotiate = false,
protocol_cb = Connection} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
send_alert_in_connection(Alert, State0),
- State1 = Connection:reinit_handshake_data(State0),
- {Record, State} = next_record(State1),
- next_event(?FUNCTION_NAME, Record, State);
+ State = Connection:reinit_handshake_data(State0),
+ next_event(?FUNCTION_NAME, no_record, State);
connection(Type, Event, State) ->
ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
@@ -723,108 +697,108 @@ downgrade(Type, Event, State) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
start(info, Event, State) ->
- gen_info(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
start(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
negotiated(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
recvd_ch(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_cert(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_cv(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_eoed(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_finished(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_flight2(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
connected(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_cert_cr(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_ee(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, 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(Event, ?FUNCTION_NAME, State);
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
wait_sh(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
%--------------------------------------------------------------------
%% gen_statem callbacks
@@ -832,6 +806,12 @@ wait_sh(Type, Event, State) ->
callback_mode() ->
state_functions.
+
+terminate(
+ {shutdown, sender_died, Reason}, _StateName,
+ #state{socket = Socket, transport_cb = Transport} = State) ->
+ ssl_connection:handle_trusted_certs_db(State),
+ close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, StateName, State) ->
catch ssl_connection:terminate(Reason, StateName, State),
ensure_sender_terminate(Reason, State).
@@ -850,7 +830,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),
-
ErlDistData = erl_dist_data(IsErlDist),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
@@ -858,6 +837,13 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
_ ->
ssl_session_cache
end,
+
+ InternalActiveN = case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) andalso (not IsErlDist) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end,
UserMonitor = erlang:monitor(process, User),
@@ -884,7 +870,10 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
protocol_cb = ?MODULE,
tracker = Tracker,
flight_buffer = [],
- protocol_specific = #{sender => Sender}
+ protocol_specific = #{sender => Sender,
+ active_n => InternalActiveN,
+ active_n_toggle => true
+ }
}.
erl_dist_data(true) ->
@@ -955,7 +944,8 @@ tls_handshake_events(Packets) ->
%% raw data from socket, upack records
handle_info({Protocol, _, Data}, StateName,
- #state{data_tag = Protocol} = State0) ->
+ #state{data_tag = Protocol
+ } = State0) ->
case next_tls_record(Data, StateName, State0) of
{Record, State} ->
next_event(StateName, Record, State);
@@ -963,11 +953,16 @@ handle_info({Protocol, _, Data}, StateName,
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
ssl_connection:stop({shutdown, own_alert}, State0)
end;
+handle_info({tcp_passive, Socket}, StateName, #state{socket = Socket,
+ protocol_specific = PS
+ } = State) ->
+ next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}});
handle_info({CloseTag, Socket}, StateName,
#state{socket = Socket, close_tag = CloseTag,
socket_options = #socket_options{active = Active},
protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
user_data_buffer = Buffer,
+ protocol_specific = PS,
negotiated_version = Version} = State) ->
%% Note that as of TLS 1.1,
@@ -993,8 +988,9 @@ handle_info({CloseTag, Socket}, StateName,
true ->
%% Fixes non-delivery of final TLS record in {active, once}.
%% Basically allows the application the opportunity to set {active, once} again
- %% and then receive the final message.
- next_event(StateName, no_record, State)
+ %% and then receive the final message. Set internal active_n to zero
+ %% to ensure socket close message is sent if there is not enough data to deliver.
+ next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}})
end;
handle_info({'EXIT', Sender, Reason}, _,
#state{protocol_specific = #{sender := Sender}} = State) ->
@@ -1036,6 +1032,18 @@ gen_handshake(StateName, Type, Event,
Version, StateName, State)
end.
+gen_handshake_1_3(StateName, Type, Event,
+ #state{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{negotiated_version = Version} = State) ->
try handle_info(Event, StateName, State) of
Result ->
@@ -1057,6 +1065,29 @@ gen_info(Event, StateName, #state{negotiated_version = Version} = State) ->
malformed_handshake_data),
Version, StateName, State)
end.
+
+gen_info_1_3(Event, connected = StateName, #state{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{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
@@ -1101,3 +1132,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
index c8732e7847..04bcea1e1b 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -104,56 +104,97 @@
-include("ssl_alert.hrl").
-include("ssl_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
--export([hello/4]).
--export([gen_handshake/4]).
+%% gen_statem helper functions
+-export([start/4,
+ negotiated/4
+ ]).
-hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) ->
- do_server_hello(Type, ServerHelloExt, State, Connection).
+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.
+
+%% TODO: move these functions
+update_state(#state{connection_states = ConnectionStates0,
+ session = Session} = State,
+ #{client_random := ClientRandom,
+ 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, ClientRandom, Cipher),
+ SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, ClientRandom, 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}}.
+
+
+negotiated(internal,
+ Map,
+ #state{connection_states = ConnectionStates0,
+ session = #session{session_id = SessionId},
+ ssl_options = #ssl_options{} = SslOpts,
+ key_share = KeyShare,
+ tls_handshake_history = HHistory0,
+ transport_cb = Transport,
+ socket = Socket}, _Module) ->
+
+ %% Create server_hello
+ %% Extensions: supported_versions, key_share, (pre_shared_key)
+ ServerHello = tls_handshake_1_3:server_hello(SessionId, KeyShare,
+ ConnectionStates0, Map),
+
+ %% Update handshake_history (done in encode!)
+ %% Encode handshake
+ {BinMsg, _ConnectionStates, _HHistory} =
+ tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0),
+ %% Send server_hello
+ tls_connection:send(Transport, Socket, BinMsg),
+ Report = #{direction => outbound,
+ protocol => 'tls_record',
+ message => BinMsg},
+ Msg = #{direction => outbound,
+ protocol => 'handshake',
+ message => ServerHello},
+ ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
+ ok.
+
+ %% K_send = handshake ???
+ %% (Send EncryptedExtensions)
+ %% ([Send CertificateRequest])
+ %% [Send Certificate + CertificateVerify]
+ %% Send Finished
+ %% K_send = application ???
-do_server_hello(Type, #{next_protocol_negotiation := _NextProtocols} =
- _ServerHelloExt,
- #state{negotiated_version = _Version,
- session = #session{session_id = _SessId},
- connection_states = _ConnectionStates0,
- ssl_options = #ssl_options{versions = [_HighestVersion|_]}}
- = State0, _Connection) when is_atom(Type) ->
-%% 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 <--------+
%% Will be called implicitly
%% {Record, State} = Connection:next_record(State2#state{session = Session}),
%% Connection:next_event(wait_flight2, Record, State, Actions),
%% OR
%% Connection:next_event(WAIT_EOED, Record, State, Actions)
- {next_state, wait_flight2, State0, []}.
- %% TODO: Add new states to tls_connection!
- %% State0.
-
-
-gen_handshake(StateName, Type, Event,
- #state{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.
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 37f13fcbac..5aca4bf8c8 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -26,6 +26,7 @@
-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").
@@ -34,7 +35,7 @@
-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 +50,8 @@
%%====================================================================
%%--------------------------------------------------------------------
-spec client_hello(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,7 +61,7 @@ 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
@@ -79,7 +81,8 @@ client_hello(Host, Port, ConnectionStates,
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,
@@ -260,8 +263,6 @@ get_tls_handshake(Version, Data, Buffer, Options) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-handle_client_hello(Version = {3,4}, ClientHello, SslOpts, Info, Renegotiation) ->
- tls_handshake_1_3:handle_client_hello(Version, ClientHello, SslOpts, Info, Renegotiation);
handle_client_hello(Version,
#client_hello{session_id = SugesstedId,
cipher_suites = CipherSuites,
@@ -409,7 +410,7 @@ decode_handshake(Version, ?CLIENT_HELLO,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
Extensions/binary>>) ->
Exts = ssl_handshake:decode_vector(Extensions),
- DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client),
+ DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client_hello),
#client_hello{
client_version = {Major,Minor},
random = Random,
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 104017b67c..f381e038cf 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -28,13 +28,45 @@
-include("tls_handshake_1_3.hrl").
-include("ssl_alert.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/5]).
+-export([handle_client_hello/3]).
+
+%% Create handshake messages
+-export([server_hello/4]).
+
+%%====================================================================
+%% 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 =
+ SecParams#security_parameters.compression_algorithm,
+ 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).
+
+
+
+%%====================================================================
+%% Encode handshake
+%%====================================================================
encode_handshake(#certificate_request_1_3{
certificate_request_context = Context,
@@ -68,14 +100,19 @@ encode_handshake(#key_update{request_update = 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),
+ 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),
+ Exts = decode_extensions(EncExts, certificate_request),
#certificate_request_1_3{
certificate_request_context = Context,
extensions = Exts};
@@ -94,12 +131,12 @@ decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary,
};
decode_handshake(?ENCRYPTED_EXTENSIONS, <<?UINT16(Size), EncExts:Size/binary>>) ->
#encrypted_extensions{
- extensions = decode_extensions(EncExts)
+ 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),
+ Exts = decode_extensions(BinExts, encrypted_extensions),
#new_session_ticket{ticket_lifetime = LifeTime,
ticket_age_add = Age,
ticket_nonce = Nonce,
@@ -143,58 +180,271 @@ 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),
+ 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) ->
- ssl_handshake:decode_extensions(Exts, {3,4}).
+decode_extensions(Exts, MessageType) ->
+ ssl_handshake:decode_extensions(Exts, {3,4}, MessageType).
extensions_list(HelloExtensions) ->
[Ext || {_, Ext} <- maps:to_list(HelloExtensions)].
-handle_client_hello(Version,
- #client_hello{session_id = SugesstedId,
- cipher_suites = CipherSuites,
- compression_methods = Compressions,
+%%====================================================================
+%% Handle handshake messages
+%%====================================================================
+
+handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
random = Random,
- extensions = HelloExt},
- #ssl_options{versions = Versions,
- signature_algs = SupportedHashSigns,
- eccs = SupportedECCs,
- honor_ecc_order = ECCOrder} = SslOpts,
- {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _},
- Renegotiation) ->
- case tls_record:is_acceptable_version(Version, Versions) of
- true ->
- %% Get supported_groups
- %% SupportedGroups = maps:get(elliptic_curves, HelloExt, undefined),
- %% Get KeyShareClientHello
-
- %% Validate supported_groups + KeyShareClientHello
- %% IF valid THEN
- %% IF supported_groups IS empty send HelloRetryRequest
- %% ELSE continue
- %% ELSE
- %% send Alert
- %% ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
- %% ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined),
-
- %% Implement session handling.
-
- %% Select curve
-
- %% Sessions cannot be resumed by ClientHello
-
- %% Select cipher_suite
- %% Select hash_sign
-
- %% Handle extensions
- ok;
- false ->
- ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
+ 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)),
+
+ %% Handle certificate
+ {PublicKeyAlgo, SignAlgo} = get_certificate_params(Cert),
+
+ %% Check if client supports signature algorithm of server certificate
+ Maybe(check_cert_sign_algo(SignAlgo, ClientSignAlgs, ClientSignAlgsCert)),
+
+ %% Check if server supports
+ 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,
+ client_random => Random,
+ 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, {client_hello_retry_request, _Group0}} ->
+ %% TODO
+ exit({client_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.
+
+
+%% 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 ->
+ %% ClientHelloRetryRequest
+ {error, {client_hello_retry_request, Group}}
+ end.
+
+select_cipher_suite([], _) ->
+ {error, no_suitable_cipher};
+select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) ->
+ case 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_cert_sign_algo(SignAlgo, ClientSignAlgs, undefined) ->
+ maybe_lists_member(SignAlgo, ClientSignAlgs,
+ {insufficient_security, no_suitable_signature_algorithm});
+check_cert_sign_algo(SignAlgo, _, ClientSignAlgsCert) ->
+ maybe_lists_member(SignAlgo, ClientSignAlgsCert,
+ {insufficient_security, no_suitable_signature_algorithm}).
+
+
+%% DSA keys are not supported by TLS 1.3
+select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) ->
+ {error, {insufficient_security, no_suitable_public_key}};
+%% TODO: Implement check for ellipctic curves!
+select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
+ {_, S, _} = ssl_cipher:scheme_to_components(C),
+ case PublicKeyAlgo =:= rsa andalso
+ ((S =:= rsa_pkcs1) orelse (S =:= rsa_pss_rsae) orelse (S =:= rsa_pss_pss)) andalso
+ lists:member(C, ServerSignAlgs) of
+ true ->
+ {ok, C};
+ false ->
+ select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)
+ end.
+
+
+maybe_lists_member(Elem, List, Error) ->
+ case lists:member(Elem, List) of
+ true ->
+ ok;
+ false ->
+ {error, Error}
+ end.
+
+%% TODO: test with ecdsa, rsa_pss_rsae, rsa_pss_pss
+get_certificate_params(Cert) ->
+ {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert),
+ SignAlgo = public_key:pkix_sign_types(SignAlgo0),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+ Scheme = sign_algo_to_scheme(SignAlgo),
+ {PublicKeyAlgo, Scheme}.
+
+sign_algo_to_scheme({Hash0, Sign0}) ->
+ SupportedSchemes = tls_v1:default_signature_schemes({3,4}),
+ Hash = case Hash0 of
+ sha ->
+ sha1;
+ H ->
+ H
+ end,
+ Sign = case Sign0 of
+ rsa ->
+ rsa_pkcs1;
+ S ->
+ S
+ end,
+ sign_algo_to_scheme(Hash, Sign, SupportedSchemes).
+%%
+sign_algo_to_scheme(_, _, []) ->
+ not_found;
+sign_algo_to_scheme(H, S, [Scheme|T]) ->
+ {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme),
+ case H =:= Hash andalso S =:= Sign of
+ true ->
+ Scheme;
+ false ->
+ sign_algo_to_scheme(H, S, T)
+ end.
+
+
+%% Note: copied from ssl_handshake
+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
index 9ee0e0f845..6ef5364399 100644
--- a/lib/ssl/src/tls_handshake_1_3.hrl
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -59,7 +59,7 @@
key_exchange %key_exchange<1..2^16-1>;
}).
-record(key_share_client_hello, {
- entries %% KeyShareEntry client_shares<0..2^16-1>;
+ client_shares %% KeyShareEntry client_shares<0..2^16-1>;
}).
-record(key_share_hello_retry_request, {
selected_group %% NamedGroup
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 7debac7d37..ed7b475619 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -47,7 +47,7 @@
-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]).
@@ -116,7 +116,7 @@ encode_handshake(Frag, Version,
ConnectionStates) ->
case iolist_size(Frag) of
N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
+ Data = split_bin(iolist_to_binary(Frag), Version, BCA, BeastMitigation),
encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
@@ -157,7 +157,7 @@ encode_data(Frag, Version,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
- Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
+ Data = split_bin(Frag, Version, BCA, BeastMitigation),
encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates).
%%====================================================================
@@ -386,10 +386,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).
%%--------------------------------------------------------------------
@@ -526,27 +526,26 @@ start_additional_data(Type, {MajVer, MinVer},
%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
%% not vulnerable to this attack.
-split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when
+split_bin(<<FirstByte:8, Rest/binary>>, Version, BCA, one_n_minus_one) when
BCA =/= ?RC4 andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
- do_split_bin(Rest, ChunkSize, [[FirstByte]]);
+ [[FirstByte]|do_split_bin(Rest)];
%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
%% splitting.
-split_bin(Bin, ChunkSize, Version, BCA, zero_n) when
+split_bin(Bin, Version, BCA, zero_n) when
BCA =/= ?RC4 andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
- do_split_bin(Bin, ChunkSize, [[<<>>]]);
-split_bin(Bin, ChunkSize, _, _, _) ->
- do_split_bin(Bin, ChunkSize, []).
+ [<<>>|do_split_bin(Bin)];
+split_bin(Bin, _, _, _) ->
+ do_split_bin(Bin).
-do_split_bin(<<>>, _, Acc) ->
- lists:reverse(Acc);
-do_split_bin(Bin, ChunkSize, Acc) ->
+do_split_bin(<<>>) -> [];
+do_split_bin(Bin) ->
case Bin of
- <<Chunk:ChunkSize/binary, Rest/binary>> ->
- do_split_bin(Rest, ChunkSize, [Chunk | Acc]);
+ <<Chunk:?MAX_PLAIN_TEXT_LENGTH/binary, Rest/binary>> ->
+ [Chunk|do_split_bin(Rest)];
_ ->
- lists:reverse(Acc, [Bin])
+ [Bin]
end.
%%--------------------------------------------------------------------
lowest_list_protocol_version(Ver, []) ->
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index ff198a09bf..d424336187 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -111,21 +111,32 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
cipher_type = ?AEAD,
bulk_cipher_algorithm =
BulkCipherAlgo}
- } = ReadState0} = ConnnectionStates0) ->
+ } = ReadState0} = ConnectionStates0) ->
AAD = start_additional_data(),
CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0),
case decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment) of
{PlainFragment, CipherS1} ->
- ConnnectionStates =
- ConnnectionStates0#{current_read =>
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
ReadState0#{cipher_state => CipherS1,
sequence_number => Seq + 1}},
- decode_inner_plaintext(PlainFragment, ConnnectionStates);
+ decode_inner_plaintext(PlainFragment, ConnectionStates);
#alert{} = Alert ->
Alert
end;
+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
+ %% Version mismatch is already asserted
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_typ_mismatch, Type}).
%%--------------------------------------------------------------------
@@ -169,7 +180,23 @@ encode_plain_text(#inner_plaintext{
AAD = start_additional_data(),
CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0),
{Encoded, WriteState} = cipher_aead(PlainText, WriteState0#{cipher_state => CipherS1}, AAD),
- {#tls_cipher_text{encoded_record = Encoded}, WriteState};
+ {#tls_cipher_text{opaque_type = Type,
+ legacy_version = {3,3},
+ encoded_record = Encoded}, WriteState};
+encode_plain_text(#inner_plaintext{
+ content = Data,
+ type = Type
+ }, #{security_parameters :=
+ #security_parameters{
+ cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
+ } = WriteState0) ->
+ %% 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}, WriteState0};
+
encode_plain_text(_, CS) ->
exit({cs, CS}).
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index 75409143a8..0631ed808a 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -103,7 +103,7 @@ send_alert(Pid, Alert) ->
%% in the connection state and recive an ack.
%%--------------------------------------------------------------------
send_and_ack_alert(Pid, Alert) ->
- gen_statem:cast(Pid, {ack_alert, Alert}).
+ gen_statem:call(Pid, {ack_alert, Alert}, ?DEFAULT_TIMEOUT).
%%--------------------------------------------------------------------
-spec setopts(pid(), [{packet, integer() | atom()}]) -> ok | {error, term()}.
%% Description: Send application data
@@ -203,8 +203,9 @@ connection({call, From}, renegotiate,
#data{connection_states = #{current_write := Write}} = StateData) ->
{next_state, handshake, StateData, [{reply, From, {ok, Write}}]};
connection({call, From}, {application_data, AppData},
- #data{socket_options = SockOpts} = StateData) ->
- case encode_packet(AppData, SockOpts) of
+ #data{socket_options = #socket_options{packet = Packet}} =
+ StateData) ->
+ case encode_packet(Packet, AppData) of
{error, _} = Error ->
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]};
Data ->
@@ -220,17 +221,30 @@ connection({call, From}, dist_get_tls_socket,
tracker = Tracker} = StateData) ->
TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker),
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]};
-connection({call, From}, {dist_handshake_complete, _Node, DHandle}, #data{connection_pid = Pid} = StateData) ->
+connection({call, From}, {dist_handshake_complete, _Node, DHandle},
+ #data{connection_pid = Pid,
+ socket_options = #socket_options{packet = Packet}} =
+ StateData) ->
ok = erlang:dist_ctrl_input_handler(DHandle, Pid),
ok = ssl_connection:dist_handshake_complete(Pid, DHandle),
%% From now on we execute on normal priority
process_flag(priority, normal),
- Events = dist_data_events(DHandle, []),
- {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, [{reply, From, ok} | Events]};
-connection(cast, {ack_alert, #alert{} = Alert}, #data{connection_pid = Pid} =StateData0) ->
+ {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle},
+ [{reply, From, ok}
+ | case dist_data(DHandle, Packet) of
+ [] ->
+ [];
+ Data ->
+ [{next_event, internal,
+ {application_packets,{self(),undefined},Data}}]
+ end]};
+connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) ->
StateData = send_tls_alert(Alert, StateData0),
- Pid ! {self(), ack_alert},
- {next_state, ?FUNCTION_NAME, StateData};
+ {next_state, ?FUNCTION_NAME, StateData,
+ [{reply,From,ok}]};
+connection(internal, {application_packets, From, Data}, StateData) ->
+ send_application_data(Data, From, ?FUNCTION_NAME, StateData);
+%%
connection(cast, #alert{} = Alert, StateData0) ->
StateData = send_tls_alert(Alert, StateData0),
{next_state, ?FUNCTION_NAME, StateData};
@@ -240,9 +254,19 @@ connection(cast, {new_write, WritesState, Version},
StateData#data{connection_states =
ConnectionStates0#{current_write => WritesState},
negotiated_version = Version}};
-connection(info, dist_data, #data{dist_handle = DHandle} = StateData) ->
- Events = dist_data_events(DHandle, []),
- {next_state, ?FUNCTION_NAME, StateData, Events};
+%%
+connection(info, dist_data,
+ #data{dist_handle = DHandle,
+ socket_options = #socket_options{packet = Packet}} =
+ StateData) ->
+ {next_state, ?FUNCTION_NAME, StateData,
+ case dist_data(DHandle, Packet) of
+ [] ->
+ [];
+ Data ->
+ [{next_event, internal,
+ {application_packets,{self(),undefined},Data}}]
+ end};
connection(info, tick, StateData) ->
consume_ticks(),
{next_state, ?FUNCTION_NAME, StateData,
@@ -275,6 +299,8 @@ handshake(cast, {new_write, WritesState, Version},
StateData#data{connection_states =
ConnectionStates0#{current_write => WritesState},
negotiated_version = Version}};
+handshake(internal, {application_packets,_,_}, _) ->
+ {keep_state_and_data, [postpone]};
handshake(info, Msg, StateData) ->
handle_info(Msg, ?FUNCTION_NAME, StateData).
@@ -351,12 +377,13 @@ send_application_data(Data, From, StateName,
log_level = LogLevel} = StateData0) ->
case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
- ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
+ ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
{next_state, handshake, StateData0,
- [{next_event, {call, From}, {application_data, Data}}]};
+ [{next_event, internal, {application_packets, From, Data}}]};
false ->
{Msgs, ConnectionStates} =
- Connection:encode_data(Data, Version, ConnectionStates0),
+ Connection:encode_data(
+ iolist_to_binary(Data), Version, ConnectionStates0),
StateData = StateData0#data{connection_states = ConnectionStates},
case Connection:send(Transport, Socket, Msgs) of
ok when DistHandle =/= undefined ->
@@ -378,21 +405,18 @@ send_application_data(Data, From, StateName,
end
end.
-encode_packet(Data, #socket_options{packet=Packet}) ->
+-compile({inline, encode_packet/2}).
+encode_packet(Packet, Data) ->
+ Len = iolist_size(Data),
case Packet of
- 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1);
- 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1);
- 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1);
- _ -> Data
- end.
-
-encode_size_packet(Bin, Size, Max) ->
- Len = erlang:byte_size(Bin),
- case Len > Max of
- true ->
- {error, {badarg, {packet_to_large, Len, Max}}};
- false ->
- <<Len:Size, Bin/binary>>
+ 1 when Len < (1 bsl 8) -> [<<Len:8>>,Data];
+ 2 when Len < (1 bsl 16) -> [<<Len:16>>,Data];
+ 4 when Len < (1 bsl 32) -> [<<Len:32>>,Data];
+ N when N =:= 1; N =:= 2; N =:= 4 ->
+ {error,
+ {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}};
+ _ ->
+ Data
end.
set_opts(SocketOptions, [{packet, N}]) ->
@@ -426,14 +450,18 @@ call(FsmPid, Event) ->
%%---------------Erlang distribution --------------------------------------
-dist_data_events(DHandle, Events) ->
+dist_data(DHandle, Packet) ->
case erlang:dist_ctrl_get_data(DHandle) of
none ->
erlang:dist_ctrl_get_data_notification(DHandle),
- lists:reverse(Events);
+ [];
Data ->
- Event = {next_event, {call, {self(), undefined}}, {application_data, Data}},
- dist_data_events(DHandle, [Event | Events])
+ %% This is encode_packet(4, Data) without Len check
+ %% since the emulator will always deliver a Data
+ %% smaller than 4 GB, and the distribution will
+ %% therefore always have to use {packet,4}
+ Len = iolist_size(Data),
+ [<<Len:32>>,Data|dist_data(DHandle, Packet)]
end.
consume_ticks() ->
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 68ba598612..83dd7585dd 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -34,7 +34,7 @@
ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1,
default_signature_algs/1, signature_algs/2,
default_signature_schemes/1, signature_schemes/2,
- groups/1, groups/2, group_to_enum/1, enum_to_group/1]).
+ 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]).
@@ -346,8 +346,8 @@ signature_algs({3, 3}, HashSigns) ->
end, [], HashSigns),
lists:reverse(Supported).
-default_signature_algs({3, 4}) ->
- default_signature_algs({3, 3});
+default_signature_algs({3, 4} = Version) ->
+ default_signature_schemes(Version);
default_signature_algs({3, 3} = Version) ->
Default = [%% SHA2
{sha512, ecdsa},
@@ -551,7 +551,7 @@ ecc_curves(_Minor, TLSCurves) ->
end
end, [], TLSCurves).
--spec groups(4 | all) -> [group()].
+-spec groups(4 | all | default) -> [group()].
groups(all) ->
[secp256r1,
secp384r1,
@@ -561,6 +561,11 @@ groups(all) ->
ffdhe4096,
ffdhe6144,
ffdhe8192];
+groups(default) ->
+ [secp256r1,
+ secp384r1,
+ secp521r1,
+ ffdhe2048];
groups(Minor) ->
TLSGroups = groups(all),
groups(Minor, TLSGroups).
@@ -571,6 +576,10 @@ groups(_Minor, TLSGroups) ->
CryptoGroups = crypto:ec_curves() ++ [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192],
lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups).
+default_groups(Minor) ->
+ TLSGroups = groups(default),
+ groups(Minor, TLSGroups).
+
group_to_enum(secp256r1) -> 23;
group_to_enum(secp384r1) -> 24;
group_to_enum(secp521r1) -> 25;