summaryrefslogtreecommitdiff
path: root/lib/ssl
diff options
context:
space:
mode:
authorPéter Dimitrov <peterdmv@erlang.org>2021-01-20 16:29:04 +0100
committerPéter Dimitrov <peterdmv@erlang.org>2021-01-25 14:19:09 +0100
commit438aad4bf821dcbad1d133bfb1bd837c92974437 (patch)
tree4e8441aa12caeab37a21f8756743bd23e3616742 /lib/ssl
parenta6a1d52b637505c73fda38aa5018c8f5180555e6 (diff)
downloaderlang-438aad4bf821dcbad1d133bfb1bd837c92974437.tar.gz
ssl: Fix various corner cases
Diffstat (limited to 'lib/ssl')
-rw-r--r--lib/ssl/src/ssl_record.erl8
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl9
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl31
-rw-r--r--lib/ssl/src/tls_record.erl7
-rw-r--r--lib/ssl/src/tls_record_1_3.erl78
-rw-r--r--lib/ssl/test/ssl_session_ticket_SUITE.erl294
-rw-r--r--lib/ssl/test/ssl_test_lib.erl16
-rw-r--r--lib/ssl/test/tls_1_3_record_SUITE.erl4
8 files changed, 398 insertions, 49 deletions
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index d81b60b87b..040c4f1ebc 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -460,8 +460,8 @@ nonce_seed(_,_, CipherState) ->
%%--------------------------------------------------------------------
empty_connection_state(ConnectionEnd, BeastMitigation) ->
- empty_connection_state(ConnectionEnd, BeastMitigation,
- ?DEFAULT_MAX_EARLY_DATA_SIZE).
+ MaxEarlyDataSize = ssl_config:get_max_early_data_size(),
+ empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize).
%%
empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
SecParams = empty_security_params(ConnectionEnd),
@@ -474,7 +474,9 @@ empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
client_verify_data => undefined,
server_verify_data => undefined,
max_early_data_size => MaxEarlyDataSize,
- max_fragment_length => undefined
+ max_fragment_length => undefined,
+ trial_decryption => false,
+ early_data_limit => false
}.
empty_security_params(ConnectionEnd = ?CLIENT) ->
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 65a56955d2..6c81933c23 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -308,8 +308,8 @@ negotiated(internal, Message, State0) ->
negotiated(info, Msg, State) ->
tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State).
-wait_cert(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert(internal, #change_cipher_spec{}, State0) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0);
wait_cert(internal,
#certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
@@ -338,9 +338,8 @@ wait_cv(info, Msg, State) ->
wait_cv(Type, Msg, State) ->
ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_finished(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_finished(internal, #change_cipher_spec{}, State0) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0);
wait_finished(internal,
#finished{} = Finished, State0) ->
case tls_handshake_1_3:do_wait_finished(Finished, State0) of
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 494eab5195..12852aea8b 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -58,7 +58,8 @@
maybe_add_binders/4,
maybe_add_early_data_indication/3,
maybe_automatic_session_resumption/1,
- maybe_send_early_data/1]).
+ maybe_send_early_data/1,
+ update_current_read/3]).
-export([get_max_early_data/1,
is_valid_binder/4,
@@ -821,7 +822,6 @@ do_negotiated({start_handshake, PSK0},
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{prf_algorithm = HKDF} = SecParamsR,
-
{Ref,Maybe} = maybe(),
try
%% Create server_hello
@@ -842,7 +842,14 @@ do_negotiated({start_handshake, PSK0},
true ->
ssl_record:step_encryption_state_write(State3);
false ->
- ssl_record:step_encryption_state(State3)
+ %% Read state is overwritten when hanshake secrets are set.
+ %% Trial_decryption and early_data_limit must be set here!
+ update_current_read(
+ ssl_record:step_encryption_state(State3),
+ true, %% trial_decryption
+ false %% early data limit
+ )
+
end,
%% Create EncryptedExtensions
@@ -1290,7 +1297,8 @@ session_resumption({#state{ssl_options = #{session_tickets := Tickets},
{_ , PSK} = PSK0,
State2 = calculate_client_early_traffic_secret(State1, PSK),
%% Set 0-RTT traffic keys for reading early_data
- State = ssl_record:step_encryption_state_read(State2),
+ State3 = ssl_record:step_encryption_state_read(State2),
+ State = update_current_read(State3, true, true),
{ok, {State, negotiated, PSK0}}.
%% Session resumption with early_data
@@ -1640,12 +1648,21 @@ calculate_client_early_traffic_secret(
State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}};
server ->
PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
- PendingRead = update_connection_state(PendingRead0, undefined, undefined,
- undefined,
- Key, IV, undefined),
+ PendingRead1 = update_connection_state(PendingRead0, undefined, undefined,
+ undefined,
+ Key, IV, undefined),
+ %% Signal start of early data. This is to prevent handshake messages to be
+ %% counted in max_early_data_size.
+ PendingRead = PendingRead1#{count_early_data => true},
State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}}
end.
+update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataLimit) ->
+ Read0 = ssl_record:current_connection_state(CS, read),
+ Read = Read0#{trial_decryption => TrialDecryption,
+ early_data_limit => EarlyDataLimit},
+ State#state{connection_states = CS#{current_read => Read}}.
+
%% Server
get_pre_shared_key(undefined, HKDFAlgo) ->
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 2ab7eaa836..9ec5490aa6 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -76,7 +76,8 @@
%% values for the initial SSL connection setup.
%%--------------------------------------------------------------------
init_connection_states(Role, BeastMitigation) ->
- init_connection_states(Role, BeastMitigation, ?DEFAULT_MAX_EARLY_DATA_SIZE).
+ MaxEarlyDataSize = ssl_config:get_max_early_data_size(),
+ init_connection_states(Role, BeastMitigation, MaxEarlyDataSize).
%%
-spec init_connection_states(Role, BeastMitigation, MaxEarlyDataSize) ->
ssl_record:connection_states() when
@@ -492,7 +493,9 @@ initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
client_verify_data => undefined,
server_verify_data => undefined,
max_early_data_size => MaxEarlyDataSize,
- max_fragment_length => undefined
+ max_fragment_length => undefined,
+ trial_decryption => false,
+ early_data_limit => false
}.
%% Used by logging to recreate the received bytes
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index cbf19416a5..3e42e3bf97 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -126,24 +126,31 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
cipher_type = ?AEAD,
bulk_cipher_algorithm =
BulkCipherAlgo},
- max_early_data_size := MaxEarlyDataSize0
+ max_early_data_size := MaxEarlyDataSize0,
+ trial_decryption := TrialDecryption,
+ early_data_limit := EarlyDataLimit
} = ReadState0} = ConnectionStates0) ->
case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of
- #alert{} when MaxEarlyDataSize0 > 0 -> %% Trial decryption
- MaxEarlyDataSize = update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment),
- ConnectionStates =
- ConnectionStates0#{current_read =>
- ReadState0#{max_early_data_size => MaxEarlyDataSize}},
- {trial_decryption_failed, ConnectionStates};
+ #alert{} when TrialDecryption =:= true andalso
+ MaxEarlyDataSize0 > 0 -> %% Trial decryption
+ trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0,
+ BulkCipherAlgo, CipherFragment);
#alert{} = Alert ->
Alert;
- PlainFragment ->
+ PlainFragment0 when EarlyDataLimit =:= true andalso
+ MaxEarlyDataSize0 > 0 ->
+ PlainFragment = remove_padding(PlainFragment0),
+ process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq,
+ BulkCipherAlgo, CipherFragment, PlainFragment);
+ PlainFragment0 ->
+ PlainFragment = remove_padding(PlainFragment0),
ConnectionStates =
ConnectionStates0#{current_read =>
ReadState0#{sequence_number => Seq + 1}},
{decode_inner_plaintext(PlainFragment), ConnectionStates}
end;
+
%% RFC8446 - TLS 1.3 (OpenSSL compatibility)
%% Handle unencrypted Alerts from openssl s_client when server's
%% connection states are already stepped into traffic encryption.
@@ -201,6 +208,50 @@ decode_cipher_text(#ssl_tls{type = Type}, _) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0,
+ BulkCipherAlgo, CipherFragment) ->
+ MaxEarlyDataSize = update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment),
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{max_early_data_size => MaxEarlyDataSize}},
+ if MaxEarlyDataSize < 0 ->
+ %% More early data is trial decrypted as the configured limit
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed);
+ true ->
+ {trial_decryption_failed, ConnectionStates}
+ end.
+
+process_early_data(ConnectionStates0, ReadState0, _MaxEarlyDataSize0, Seq,
+ _BulkCipherAlgo, _CipherFragment, PlainFragment)
+ when PlainFragment =:= <<5,0,0,0,22>> ->
+ %% struct {
+ %% opaque content[TLSPlaintext.length]; <<5,0,0,0>> - 5 = EndOfEarlyData
+ %% 0 = (uint24) size
+ %% ContentType type; <<22>> - Handshake
+ %% uint8 zeros[length_of_padding]; <<>> - no padding
+ %% } TLSInnerPlaintext;
+ %% EndOfEarlyData should not be counted into early data
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{sequence_number => Seq + 1}},
+ {decode_inner_plaintext(PlainFragment), ConnectionStates};
+process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq,
+ BulkCipherAlgo, CipherFragment, PlainFragment) ->
+ %% First packet is deciphered anyway so we must check if more early data is received
+ %% than the configured limit (max_early_data_size).
+ MaxEarlyDataSize =
+ update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment),
+ if MaxEarlyDataSize < 0 ->
+ %% Too much early data received, send alert unexpected_message
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, too_much_early_data);
+ true ->
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{sequence_number => Seq + 1,
+ max_early_data_size => MaxEarlyDataSize}},
+ {decode_inner_plaintext(PlainFragment), ConnectionStates}
+ end.
+
inner_plaintext(Type, Data, Length) ->
#inner_plaintext{
content = Data,
@@ -308,8 +359,6 @@ aead_ciphertext_split(CipherTextFragment, TagLen)
decode_inner_plaintext(PlainText) ->
case binary:last(PlainText) of
- 0 ->
- decode_inner_plaintext(init_binary(PlainText));
Type when Type =:= ?APPLICATION_DATA orelse
Type =:= ?HANDSHAKE orelse
Type =:= ?ALERT ->
@@ -325,6 +374,14 @@ init_binary(B) ->
split_binary(B, byte_size(B) - 1),
Init.
+remove_padding(InnerPlainText) ->
+ case binary:last(InnerPlainText) of
+ 0 ->
+ remove_padding(init_binary(InnerPlainText));
+ _ ->
+ InnerPlainText
+ end.
+
update_max_early_date_size(MaxEarlyDataSize, BulkCipherAlgo, CipherFragment) ->
%% CipherFragment is the binary encoded form of a TLSInnerPlaintext:
%%
@@ -343,4 +400,3 @@ bca_tag_len(?AES_CCM_8) ->
8;
bca_tag_len(_) ->
16.
-
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
index 4101c50f65..d39afaa5b7 100644
--- a/lib/ssl/test/ssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -55,10 +55,18 @@
multiple_tickets/1,
multiple_tickets_2hash/0,
multiple_tickets_2hash/1,
+ early_data_client_too_much_data/0,
+ early_data_client_too_much_data/1,
early_data_trial_decryption/0,
early_data_trial_decryption/1,
- early_data_trial_decryption_big/0,
- early_data_trial_decryption_big/1,
+ early_data_trial_decryption_failure/0,
+ early_data_trial_decryption_failure/1,
+ early_data_decryption_failure/0,
+ early_data_decryption_failure/1,
+ early_data_disabled_small_limit/0,
+ early_data_disabled_small_limit/1,
+ early_data_enabled_small_limit/0,
+ early_data_enabled_small_limit/1,
early_data_basic/0,
early_data_basic/1,
early_data_basic_auth/0,
@@ -92,8 +100,12 @@ session_tests() ->
hello_retry_request,
multiple_tickets,
multiple_tickets_2hash,
+ early_data_client_too_much_data,
early_data_trial_decryption,
- early_data_trial_decryption_big,
+ early_data_trial_decryption_failure,
+ early_data_decryption_failure,
+ early_data_disabled_small_limit,
+ early_data_enabled_small_limit,
early_data_basic,
early_data_basic_auth].
@@ -139,6 +151,7 @@ init_per_testcase(_, Config) ->
Config.
end_per_testcase(_TestCase, Config) ->
+ application:unset_env(ssl, server_session_ticket_max_early_data),
Config.
%%--------------------------------------------------------------------
@@ -655,9 +668,9 @@ early_data_trial_decryption(Config) when is_list(Config) ->
ssl_test_lib:close(Server0),
ssl_test_lib:close(Client1).
-early_data_trial_decryption_big() ->
- [{doc,"Test trial decryption (too much data) when server rejects early data (erlang client - erlang server)"}].
-early_data_trial_decryption_big(Config) when is_list(Config) ->
+early_data_client_too_much_data() ->
+ [{doc,"Client sending too much early data (erlang client - erlang server)"}].
+early_data_client_too_much_data(Config) when is_list(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -671,24 +684,77 @@ early_data_trial_decryption_big(Config) when is_list(Config) ->
MaxEarlyDataSize = 10000,
ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16384)}|ClientOpts1],
- %% TODO Implement testcase that verifies the failure of trial decryption
- %% in the server.
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, disabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+ ssl_test_lib:verify_session_ticket_extension(Tickets0, MaxEarlyDataSize),
+ %% ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [false, no_reply, no_tickets]}},
+ {from, self()}, {options, [{use_ticket, Tickets0}|ClientOpts2]}]),
+ ssl_test_lib:check_client_alert(Client1, illegal_parameter),
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0).
+
+early_data_trial_decryption_failure() ->
+ [{doc,"Emulate faulty client that sends too much early data (erlang client - erlang server)"}].
+early_data_trial_decryption_failure(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send more early data than max_early_data_size to verify calculation
+ %% of plain text size in the server.
+ MaxEarlyDataSize = 10000,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16385)}|ClientOpts1],
+
%% Disabled early data triggers trial decryption upon receiving early data
%% up to the configured amount. If more data is received the server triggers
%% a bad_record_mac alert.
- %% Currently it is not possible to trigger this condition:
+ %% It is not possible to trigger this condition in normal use cases:
%% - The ssl client in auto mode has a built in protection against sending
%% too much early data. It will not send any early data.
%% - The ssl client in manual mode can only send the mount that is received
%% in the ticket used for the 0-RTT handshake. If more data is sent the
%% client will trigger an illegal_parameter alert (too_much_early_data).
- %% - The server app variable max_early_data_size is not implemented yet. Its
- %% value is hardcoded to 16k. The configured value is reflected in the
- %% early_data_indication field of the session tickets send by the server.
- %% (This testcase was supposed to test what happened when the a faulty client
- %% sent more early data than what was allowed according to the received
- %% session ticket.)
-
ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, disabled},
{log_level, debug},
{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
@@ -701,7 +767,70 @@ early_data_trial_decryption_big(Config) when is_list(Config) ->
verify_active_session_resumption,
[false]}},
{options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+ %% Simulate a faulty client by updating the max_early_data_size extension in
+ %% the received session ticket
+ Tickets1 = ssl_test_lib:update_session_ticket_extension(Tickets0, 16385),
+ %% ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [false, no_reply, no_tickets]}},
+ {from, self()}, {options, [{use_ticket, Tickets1}|ClientOpts2]}]),
+ ssl_test_lib:check_server_alert(Server0, bad_record_mac),
+ process_flag(trap_exit, false),
application:unset_env(ssl, server_session_ticket_max_early_data),
+ ssl_test_lib:close(Server0).
+
+early_data_decryption_failure() ->
+ [{doc,"Emulate faulty client that sends too much early data - server early_data enabled (erlang client - erlang server)"}].
+early_data_decryption_failure(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send more early data than max_early_data_size to verify calculation
+ %% of plain text size in the server.
+ MaxEarlyDataSize = 10000,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16385)}|ClientOpts1],
+
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, enabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
Port0 = ssl_test_lib:inet_port(Server0),
%% Store ticket from first connection
@@ -712,7 +841,9 @@ early_data_trial_decryption_big(Config) when is_list(Config) ->
[false, no_reply, {tickets, 1}]}},
{from, self()}, {options, ClientOpts1}]),
Tickets0 = ssl_test_lib:check_tickets(Client0),
- ssl_test_lib:verify_session_ticket_extension(Tickets0, MaxEarlyDataSize),
+ %% Simulate a faulty client by updating the max_early_data_size extension in
+ %% the received session ticket
+ Tickets1 = ssl_test_lib:update_session_ticket_extension(Tickets0, 16385),
%% ssl_test_lib:check_result(Server0, ok, Client0, ok),
Server0 ! {listen, {mfa, {ssl_test_lib,
@@ -730,11 +861,136 @@ early_data_trial_decryption_big(Config) when is_list(Config) ->
{mfa, {ssl_test_lib, %% Short handshake
verify_active_session_resumption,
[false, no_reply, no_tickets]}},
- {from, self()}, {options, [{use_ticket, Tickets0}|ClientOpts2]}]),
- ssl_test_lib:check_client_alert(Client1, illegal_parameter),
+ {from, self()}, {options, [{use_ticket, Tickets1}|ClientOpts2]}]),
+ ssl_test_lib:check_server_alert(Server0, unexpected_message),
process_flag(trap_exit, false),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
ssl_test_lib:close(Server0).
+early_data_disabled_small_limit() ->
+ [{doc,"Test trial decryption when server rejects early data (erlang client - erlang server)"}].
+early_data_disabled_small_limit(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send maximum sized early data to verify calculation of plain text size
+ %% in the server.
+ MaxEarlyDataSize = 5,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 4)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, disabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts2}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+early_data_enabled_small_limit() ->
+ [{doc,"Test decryption when server accepts early data (erlang client - erlang server)"}].
+early_data_enabled_small_limit(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send maximum sized early data to verify calculation of plain text size
+ %% in the server.
+ MaxEarlyDataSize = 5,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 4)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, enabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts2}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
early_data_basic() ->
[{doc,"Test early data when client is not authenticated (erlang client - erlang server)"}].
early_data_basic(Config) when is_list(Config) ->
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index e02f9d310a..dbf8dda47c 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -25,7 +25,6 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
--include("tls_handshake_1_3.hrl").
-include_lib("ssl/src/tls_handshake_1_3.hrl").
-export([clean_start/0,
@@ -103,6 +102,7 @@
verify_active_session_resumption/5,
verify_server_early_data/3,
verify_session_ticket_extension/2,
+ update_session_ticket_extension/2,
check_sane_openssl_version/1,
check_ok/1,
check_result/4,
@@ -2698,6 +2698,20 @@ verify_session_ticket_extension([Ticket0|_], MaxEarlyDataSize) ->
[?MODULE, ?LINE, MaxEarlyDataSize, Else])
end.
+update_session_ticket_extension([Ticket|_], MaxEarlyDataSize) ->
+ #{ticket := #new_session_ticket{
+ extensions = #{early_data :=
+ #early_data_indication_nst{
+ indication = Size}}}} = Ticket,
+ ct:log("~p:~p~nOverwrite max_early_data_size (from ~p to ~p)!",
+ [?MODULE, ?LINE, Size, MaxEarlyDataSize]),
+ #{ticket := #new_session_ticket{
+ extensions = #{early_data := Extensions0}} = NST0} = Ticket,
+ Extensions = #{early_data => #early_data_indication_nst{
+ indication = MaxEarlyDataSize}},
+ NST = NST0#new_session_ticket{extensions = Extensions},
+ [Ticket#{ticket => NST}].
+
boolean_to_log_msg(true) ->
"OK";
boolean_to_log_msg(false) ->
diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl
index 24deef7781..0161f506e6 100644
--- a/lib/ssl/test/tls_1_3_record_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_record_SUITE.erl
@@ -104,7 +104,9 @@ encode_decode(_Config) ->
147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
undefined},
sequence_number => 0,server_verify_data => undefined,
- max_early_data_size => 0},
+ max_early_data_size => 0,
+ trial_decryption => false,
+ early_data_limit => false},
current_write =>
#{beast_mitigation => one_n_minus_one,
cipher_state =>