diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ssl/src/ssl_gen_statem.erl | 2 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection_1_3.erl | 162 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.erl | 5 | ||||
-rw-r--r-- | lib/ssl/test/tls_api_SUITE.erl | 57 |
4 files changed, 189 insertions, 37 deletions
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index caa6c6742d..a6053e7e3f 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -709,8 +709,6 @@ handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); handle_common_event(timeout, hibernate, _, _) -> {keep_state_and_data, [hibernate]}; -handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, State) -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), StateName, State); handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) -> {stop_and_reply, {shutdown, user_timeout}, diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 19c9a05a2f..a4f0e454f4 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -125,6 +125,8 @@ config_error/3, user_hello/3, start/3, + hello_middlebox_assert/3, + hello_retry_middlebox_assert/3, negotiated/3, wait_cert/3, wait_cv/3, @@ -183,7 +185,7 @@ update_cipher_key(ConnStateName, CS0) -> %% gen_statem callbacks %%-------------------------------------------------------------------- callback_mode() -> - state_functions. + [state_functions, state_enter]. init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, @@ -220,6 +222,8 @@ code_change(_OldVsn, StateName, State, _) -> {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- +initial_hello(enter, _, State) -> + {keep_state, State}; initial_hello(Type, Event, State) -> ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). @@ -228,10 +232,13 @@ initial_hello(Type, Event, State) -> {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- +config_error(enter, _, State) -> + {keep_state, State}; config_error(Type, Event, State) -> ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). - +user_hello(enter, _, State) -> + {keep_state, State}; user_hello({call, From}, cancel, State) -> gen_statem:reply(From, ok), ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), @@ -255,8 +262,21 @@ user_hello(info, {'DOWN', _, _, _, _} = Event, State) -> user_hello(_, _, _) -> {keep_state_and_data, [postpone]}. -start(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +start(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +start(internal = Type, #change_cipher_spec{} = Msg, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) -> + case ssl_handshake:init_handshake_history() of + Hist -> %% First message must always be client hello + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State); + _ -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State) + end; +start(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); start(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello}, start_or_recv_from = From, @@ -297,8 +317,12 @@ start(info, Msg, State) -> start(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -negotiated(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +negotiated(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +negotiated(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); negotiated(internal, Message, State0) -> case tls_handshake_1_3:do_negotiated(Message, State0) of #alert{} = Alert -> @@ -309,8 +333,12 @@ negotiated(internal, Message, State0) -> negotiated(info, Msg, State) -> tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State). -wait_cert(internal, #change_cipher_spec{}, State0) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0); +wait_cert(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_cert(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_cert(internal, #certificate_1_3{} = Certificate, State0) -> case tls_handshake_1_3:do_wait_cert(Certificate, State0) of @@ -324,8 +352,12 @@ wait_cert(info, Msg, State) -> wait_cert(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_cv(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cv(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_cv(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_cv(internal, #certificate_verify_1_3{} = CertificateVerify, State0) -> case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of @@ -339,8 +371,12 @@ 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{}, State0) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0); +wait_finished(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_finished(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_finished(internal, #finished{} = Finished, State0) -> case tls_handshake_1_3:do_wait_finished(Finished, State0) of @@ -356,34 +392,76 @@ wait_finished(info, Msg, State) -> wait_finished(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). - -wait_sh(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_sh(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello}, - start_or_recv_from = From, - handshake_env = HsEnv} = State) -> +wait_sh(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_sh(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); +wait_sh(internal, #server_hello{extensions = Extensions} = Hello, + #state{ssl_options = #{handshake := hello}, + start_or_recv_from = From, + handshake_env = HsEnv} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, handshake_env = HsEnv#handshake_env{ hello = Hello}}, [{reply, From, {ok, Extensions}}]}; -wait_sh(internal, #server_hello{} = Hello, State0) -> +wait_sh(internal, #server_hello{} = Hello, + #state{ssl_options = #{middlebox_comp_mode := false}} = State0) -> case tls_handshake_1_3:do_wait_sh(Hello, State0) of - #alert{} = Alert -> + #alert{} = Alert -> ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0); {State1, start, ServerHello} -> %% hello_retry_request: go to start - {next_state, start, State1, [{next_event, internal, ServerHello}]}; + {next_state, start, State1, [{next_event, internal, ServerHello}]}; {State1, wait_ee} -> - tls_gen_connection:next_event(wait_ee, no_record, State1) + tls_gen_connection:next_event(wait_ee, no_record, State1) + end; +wait_sh(internal, #server_hello{} = Hello, + #state{ssl_options = #{middlebox_comp_mode := true}, protocol_specific = PS} = State0) -> + IsRetry = maps:get(hello_retry, PS, false), + case tls_handshake_1_3:do_wait_sh(Hello, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0); + {State1, start, ServerHello} -> + %% hello_retry_request: go to start + {next_state, hello_retry_middlebox_assert, State1, [{next_event, internal, ServerHello}]}; + {State1, wait_ee} when IsRetry == true -> + tls_gen_connection:next_event(wait_ee, no_record, State1); + {State1, wait_ee} when IsRetry == false -> + tls_gen_connection:next_event(hello_middlebox_assert, no_record, State1) end; wait_sh(info, Msg, State) -> tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); wait_sh(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). +hello_middlebox_assert(enter, _, State) -> + {keep_state, State}; +hello_middlebox_assert(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(wait_ee, no_record, State); +hello_middlebox_assert(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +hello_middlebox_assert(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_ee(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +hello_retry_middlebox_assert(enter, _, State) -> + {keep_state, State}; +hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(start, no_record, State); +hello_retry_middlebox_assert(internal, #server_hello{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, [postpone]); +hello_retry_middlebox_assert(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +hello_retry_middlebox_assert(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +wait_ee(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_ee(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_ee(internal, #encrypted_extensions{} = EE, State0) -> case tls_handshake_1_3:do_wait_ee(EE, State0) of #alert{} = Alert -> @@ -396,9 +474,12 @@ wait_ee(info, Msg, State) -> wait_ee(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). - -wait_cert_cr(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cert_cr(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}} = State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) -> case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of {#alert{} = Alert, State} -> @@ -418,8 +499,12 @@ wait_cert_cr(info, Msg, State) -> wait_cert_cr(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_eoed(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_eoed(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_eoed(internal = Type, #change_cipher_spec{} = Msg, + #state{ssl_options = #{middlebox_comp_mode := true}}= State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_eoed(internal, #end_of_early_data{} = EOED, State0) -> case tls_handshake_1_3:do_wait_eoed(EOED, State0) of {#alert{} = Alert, State} -> @@ -432,6 +517,8 @@ wait_eoed(info, Msg, State) -> wait_eoed(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). +connection(enter, _, State) -> + {keep_state, State}; connection(internal, #new_session_ticket{} = NewSessionTicket, State) -> handle_new_session_ticket(NewSessionTicket, State), tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); @@ -455,6 +542,8 @@ connection({call, From}, negotiated_protocol, connection(Type, Event, State) -> ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). +downgrade(enter, _, State) -> + {keep_state, State}; downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) -> _ = handle_new_session_ticket(NewSessionTicket, State), {next_state, ?FUNCTION_NAME, State}; @@ -591,3 +680,18 @@ init_max_early_data_size(client) -> init_max_early_data_size(server) -> ssl_config:get_max_early_data_size(). +handle_middlebox(#state{ssl_options = #{middlebox_comp_mode := true}, + protocol_specific = PS} = State0) -> + State0#state{protocol_specific = PS#{change_cipher_spec => ignore}}; +handle_middlebox(State) -> + State. + +handle_change_cipher_spec(Type, Msg, StateName, #state{protocol_specific = PS0} = State) -> + case maps:get(change_cipher_spec, PS0) of + ignore -> + PS = PS0#{change_cipher_spec => fail}, + tls_gen_connection:next_event(StateName, no_record, + State#state{protocol_specific = PS}); + fail -> + ssl_gen_statem:handle_common_event(Type, Msg, StateName, State) + end. diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index cd48226c87..3e6117735f 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -1128,8 +1128,9 @@ do_wait_eoed(#end_of_early_data{}, State0) -> %% Upon receiving a message with type server_hello, implementations MUST %% first examine the Random value and, if it matches this value, process %% it as described in Section 4.1.4). -maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, State0) -> - {error, {State0, start, ServerHello}}; +maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, + #state{protocol_specific = PS} = State0) -> + {error, {State0#state{protocol_specific = PS#{hello_retry => true}}, start, ServerHello}}; maybe_hello_retry_request(_, _) -> ok. diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl index 7e5f312152..c81d29370e 100644 --- a/lib/ssl/test/tls_api_SUITE.erl +++ b/lib/ssl/test/tls_api_SUITE.erl @@ -28,6 +28,7 @@ -include_lib("ssl/src/ssl_api.hrl"). -include_lib("ssl/src/tls_handshake.hrl"). -include_lib("ssl/src/ssl_alert.hrl"). +-include_lib("ssl/src/ssl_cipher.hrl"). %% Common test -export([all/0, @@ -85,6 +86,10 @@ tls_reject_fake_warning_alert_in_initial_hs/1, tls_app_data_in_initial_hs_state/0, tls_app_data_in_initial_hs_state/1, + tls_13_reject_change_cipher_spec_as_first_msg/0, + tls_13_reject_change_cipher_spec_as_first_msg/1, + tls_13_middlebox_reject_change_cipher_spec_as_first_msg/0, + tls_13_middlebox_reject_change_cipher_spec_as_first_msg/1, peername/0, peername/1, sockname/0, @@ -133,7 +138,8 @@ all() -> groups() -> [ - {'tlsv1.3', [], api_tests() -- [sockname]}, + {'tlsv1.3', [], (api_tests() ++ [tls_13_reject_change_cipher_spec_as_first_msg, + tls_13_middlebox_reject_change_cipher_spec_as_first_msg]) -- [sockname]}, {'tlsv1.2', [], api_tests()}, {'tlsv1.1', [], api_tests()}, {'tlsv1', [], api_tests()} @@ -765,16 +771,59 @@ tls_app_data_in_initial_hs_state(Config) when is_list(Config) -> {ok, Socket} = gen_tcp:connect("localhost", Port, [{active, false}, binary]), AppData = case Version of {3, 4} -> - <<?BYTE(?APPLICATION_DATA), ?BYTE(3), ?BYTE(3), ?UINT16(4), ?BYTE($F), ?BYTE($O), ?BYTE($O), ?BYTE(?APPLICATION_DATA)>>; + <<?BYTE(?APPLICATION_DATA), ?BYTE(3), ?BYTE(3), ?UINT16(4), ?BYTE($F), + ?BYTE($O), ?BYTE($O), ?BYTE(?APPLICATION_DATA)>>; _ -> - <<?BYTE(?APPLICATION_DATA), ?BYTE(Major), ?BYTE(Minor), ?UINT16(3), ?BYTE($F), ?BYTE($O), ?BYTE($O)>> + <<?BYTE(?APPLICATION_DATA), ?BYTE(Major), ?BYTE(Minor), + ?UINT16(3), ?BYTE($F), ?BYTE($O), ?BYTE($O)>> end, gen_tcp:send(Socket, AppData), - UnexpectedMsgAlert = <<?BYTE(?ALERT), ?BYTE(Major), ?BYTE(Minor), ?UINT16(2), ?BYTE(?FATAL), ?BYTE(?UNEXPECTED_MESSAGE)>>, + UnexpectedMsgAlert = <<?BYTE(?ALERT), ?BYTE(Major), ?BYTE(Minor), ?UINT16(2), + ?BYTE(?FATAL), ?BYTE(?UNEXPECTED_MESSAGE)>>, + {ok, UnexpectedMsgAlert} = gen_tcp:recv(Socket, 7), + {error, closed} = gen_tcp:recv(Socket, 0). +%%-------------------------------------------------------------------- +tls_13_reject_change_cipher_spec_as_first_msg() -> + [{doc,"change_cipher_spec messages can be sent in TLS-1.3 middlebox_comp_mode, but can not be sent as first msg"}]. +tls_13_reject_change_cipher_spec_as_first_msg(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{versions, [ssl_test_lib:protocol_version(Config)]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {ok, Socket} = gen_tcp:connect("localhost", Port, [{active, false}, binary]), + ChangeCipherSpec = <<?BYTE(?CHANGE_CIPHER_SPEC), ?BYTE(3), ?BYTE(3), + ?UINT16(1), ?BYTE(?CHANGE_CIPHER_SPEC_PROTO)>>, + gen_tcp:send(Socket, ChangeCipherSpec), + UnexpectedMsgAlert = <<?BYTE(?ALERT), ?BYTE(3), ?BYTE(3), ?UINT16(2), + ?BYTE(?FATAL), ?BYTE(?UNEXPECTED_MESSAGE)>>, {ok, UnexpectedMsgAlert} = gen_tcp:recv(Socket, 7), {error, closed} = gen_tcp:recv(Socket, 0). %%-------------------------------------------------------------------- +tls_13_middlebox_reject_change_cipher_spec_as_first_msg() -> + [{doc,"change_cipher_spec messages can be sent in TLS-1.3 middlebox_comp_mode, but can not be sent as first msg"}]. +tls_13_middlebox_reject_change_cipher_spec_as_first_msg(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{middlebox_comp_mode, false}, + {versions, [ssl_test_lib:protocol_version(Config)]} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {ok, Socket} = gen_tcp:connect("localhost", Port, [{active, false}, binary]), + ChangeCipherSpec = <<?BYTE(?CHANGE_CIPHER_SPEC), ?BYTE(3), ?BYTE(3), + ?UINT16(1), ?BYTE(?CHANGE_CIPHER_SPEC_PROTO)>>, + gen_tcp:send(Socket, ChangeCipherSpec), + UnexpectedMsgAlert = <<?BYTE(?ALERT), ?BYTE(3), ?BYTE(3), ?UINT16(2), + ?BYTE(?FATAL), ?BYTE(?UNEXPECTED_MESSAGE)>>, + {ok, UnexpectedMsgAlert} = gen_tcp:recv(Socket, 7), + {error, closed} = gen_tcp:recv(Socket, 0). +%%-------------------------------------------------------------------- peername() -> [{doc,"Test API function peername/1"}]. |