diff options
author | Péter Dimitrov <peterdmv@erlang.org> | 2021-01-20 16:29:04 +0100 |
---|---|---|
committer | Péter Dimitrov <peterdmv@erlang.org> | 2021-01-25 14:19:09 +0100 |
commit | 438aad4bf821dcbad1d133bfb1bd837c92974437 (patch) | |
tree | 4e8441aa12caeab37a21f8756743bd23e3616742 /lib/ssl | |
parent | a6a1d52b637505c73fda38aa5018c8f5180555e6 (diff) | |
download | erlang-438aad4bf821dcbad1d133bfb1bd837c92974437.tar.gz |
ssl: Fix various corner cases
Diffstat (limited to 'lib/ssl')
-rw-r--r-- | lib/ssl/src/ssl_record.erl | 8 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection_1_3.erl | 9 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.erl | 31 | ||||
-rw-r--r-- | lib/ssl/src/tls_record.erl | 7 | ||||
-rw-r--r-- | lib/ssl/src/tls_record_1_3.erl | 78 | ||||
-rw-r--r-- | lib/ssl/test/ssl_session_ticket_SUITE.erl | 294 | ||||
-rw-r--r-- | lib/ssl/test/ssl_test_lib.erl | 16 | ||||
-rw-r--r-- | lib/ssl/test/tls_1_3_record_SUITE.erl | 4 |
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 => |