diff options
Diffstat (limited to 'lib/ssl/src/tls_client_connection_1_3.erl')
-rw-r--r-- | lib/ssl/src/tls_client_connection_1_3.erl | 1007 |
1 files changed, 1007 insertions, 0 deletions
diff --git a/lib/ssl/src/tls_client_connection_1_3.erl b/lib/ssl/src/tls_client_connection_1_3.erl new file mode 100644 index 0000000000..d5742ea390 --- /dev/null +++ b/lib/ssl/src/tls_client_connection_1_3.erl @@ -0,0 +1,1007 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2022-2023. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: TLS-1.3 FSM (client side) +%%---------------------------------------------------------------------- +%% INITIAL_HELLO +%% Client send +%% first ClientHello +%% | ---> CONFIG_ERROR +%% | Send error to user +%% | and shutdown +%% | +%% V +%% RFC 8446 +%% A.1. Client +%% +%% START <----+ +%% Send ClientHello | | Recv HelloRetryRequest +%% [K_send = early data] | | +%% v | +%% / WAIT_SH ----+ +%% | | Recv ServerHello +%% | | K_recv = handshake +%% Can | V +%% send | WAIT_EE +%% early | | Recv EncryptedExtensions +%% data | +--------+--------+ +%% | Using | | Using certificate +%% | PSK | v +%% | | WAIT_CERT_CR +%% | | Recv | | Recv CertificateRequest +%% | | Certificate | v +%% | | | WAIT_CERT +%% | | | | Recv Certificate +%% | | v v +%% | | WAIT_CV +%% | | | Recv CertificateVerify +%% | +> WAIT_FINISHED <+ +%% | | Recv Finished +%% \ | [Send EndOfEarlyData] +%% | K_send = handshake +%% | [Send Certificate [+ CertificateVerify]] +%% Can send | Send Finished +%% app data --> | K_send = K_recv = application +%% after here v +%% CONNECTED +%% + +-module(tls_client_connection_1_3). + +-include_lib("public_key/include/public_key.hrl"). + +-include("ssl_alert.hrl"). +-include("ssl_connection.hrl"). +-include("tls_connection.hrl"). +-include("tls_handshake.hrl"). +-include("tls_handshake_1_3.hrl"). + +-behaviour(gen_statem). + +%% gen_statem callbacks +-export([init/1, + callback_mode/0, + terminate/3, + code_change/4, + format_status/2]). + +%% gen_statem state functions +-export([config_error/3, + initial_hello/3, + user_hello/3, + start/3, + wait_sh/3, + hello_middlebox_assert/3, + hello_retry_middlebox_assert/3, + wait_ee/3, + wait_cert_cr/3, + wait_cert/3, + wait_cv/3, + wait_finished/3, + connection/3, + downgrade/3 + ]). + +%% Internal API +-export([maybe_send_early_data/1, + maybe_automatic_session_resumption/1 + ]). + +%%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- +callback_mode() -> + [state_functions, state_enter]. + +init([?CLIENT_ROLE, Sender, Host, Port, Socket, Options, User, CbInfo]) -> + State0 = #state{protocol_specific = Map} = + tls_gen_connection_1_3:initial_state(?CLIENT_ROLE, Sender, + Host, Port, Socket, + Options, User, CbInfo), + try + State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, + ?CLIENT_ROLE, State0), + tls_gen_connection:initialize_tls_sender(State), + gen_statem:enter_loop(?MODULE, [], initial_hello, State) + catch throw:Error -> + EState = State0#state{protocol_specific = Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], config_error, EState) + end. + +terminate({shutdown, {sender_died, Reason}}, _StateName, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}} + = State) -> + ssl_gen_statem:handle_trusted_certs_db(State), + tls_gen_connection:close(Reason, Socket, Transport, undefined); +terminate(Reason, StateName, State) -> + ssl_gen_statem:terminate(Reason, StateName, State). + +format_status(Type, Data) -> + ssl_gen_statem:format_status(Type, Data). + +code_change(_OldVsn, StateName, State, _) -> + {ok, StateName, State}. + +%-------------------------------------------------------------------- +%% state functions +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +-spec initial_hello(gen_statem:event_type(), + {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). + +%%-------------------------------------------------------------------- +-spec config_error(gen_statem:event_type(), + {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). + +%%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +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), + ?FUNCTION_NAME, State); +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{handshake_env = #handshake_env{continue_status = pause} = HSEnv, + ssl_options = Options0} = State0) -> + try ssl:update_options(NewOptions, ?CLIENT_ROLE, Options0) of + Options -> + State = ssl_gen_statem:ssl_config(Options, ?CLIENT_ROLE, State0), + {next_state, wait_sh, State#state{start_or_recv_from = From, + handshake_env = + HSEnv#handshake_env{continue_status + = continue} + }, + [{{timeout, handshake}, Timeout, close}]} + catch + throw:{error, Reason} -> + gen_statem:reply(From, {error, Reason}), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0) + end; +user_hello(Type, Msg, State) -> + tls_gen_connection_1_3:user_hello(Type, Msg, State). + +%%-------------------------------------------------------------------- +-spec start(gen_statem:event_type(), + #server_hello{} | #change_cipher_spec{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +start(enter, _, State0) -> + State = tls_gen_connection_1_3:handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +start(internal = Type, #change_cipher_spec{} = Msg, State) -> + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, + ?FUNCTION_NAME, State); +start(internal, + #server_hello{extensions = + #{server_hello_selected_version := + #server_hello_selected_version{selected_version + = Version}}} + = ServerHello, + #state{ssl_options = #{handshake := full, + versions := SupportedVersions}} = State) -> + case tls_record:is_acceptable_version(Version, SupportedVersions) of + true -> + handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State); + false -> + ssl_gen_statem:handle_own_alert( + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State) + end; +start(internal, #server_hello{extensions = + #{server_hello_selected_version := + #server_hello_selected_version{ + selected_version = Version}} + = Extensions}, + #state{ssl_options = #{versions := SupportedVersions}, + start_or_recv_from = From, + handshake_env = #handshake_env{continue_status = pause}} + = State) -> + case tls_record:is_acceptable_version(Version, SupportedVersions) of + true -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined}, + [{postpone, true}, + {reply, From, {ok, Extensions}}]}; + false -> + ssl_gen_statem:handle_own_alert( + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State) + end; +start(internal, #server_hello{} = ServerHello, + #state{handshake_env = + #handshake_env{continue_status = continue}} = State) -> + handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State); +start(internal, #server_hello{}, State0) -> + %% Missing mandantory TLS-1.3 extensions, + %%so it is a previous version hello. + ssl_gen_statem:handle_own_alert( + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0); +start(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +start(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec wait_sh(gen_statem:event_type(), + #server_hello{} | #change_cipher_spec{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_sh(enter, _, State0) -> + State = tls_gen_connection_1_3:handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)-> + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, + ?FUNCTION_NAME, State); +wait_sh(internal, #server_hello{extensions = Extensions}, + #state{handshake_env = #handshake_env{continue_status = pause}, + start_or_recv_from = From} = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined}, + [{postpone, true},{reply, From, {ok, Extensions}}]}; +wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello, + #state{session = #session{session_id = ?EMPTY_ID}, + ssl_options = #{middlebox_comp_mode := false}} = State0) -> + case handle_server_hello(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, start, State1, [{next_event, internal, ServerHello}]}; + {State1, wait_ee} -> + tls_gen_connection:next_event(wait_ee, no_record, State1) + end; +wait_sh(internal, #server_hello{} = Hello, + #state{protocol_specific = PS, + ssl_options = SSLOpts} = State0) + when not is_map_key(middlebox_comp_mode, SSLOpts) -> + IsRetry = maps:get(hello_retry, PS, false), + case handle_server_hello(Hello, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0); + {State1 = #state{}, start, ServerHello} -> + %% hello_retry_request + {next_state, start, 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). + +%%-------------------------------------------------------------------- +-spec hello_middlebox_assert(gen_statem:event_type(), + #change_cipher_spec{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +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). + +%%-------------------------------------------------------------------- +-spec hello_retry_middlebox_assert(gen_statem:event_type(), + #server_hello{} | #change_cipher_spec{} + | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello_retry_middlebox_assert(enter, _, State) -> + {keep_state, State}; +hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(wait_sh, 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). + +%%-------------------------------------------------------------------- +-spec wait_ee(gen_statem:event_type(), + #encrypted_extensions{} | #change_cipher_spec{} + | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_ee(enter, _, State0) -> + State = tls_gen_connection_1_3:handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) -> + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, + ?FUNCTION_NAME, State); +wait_ee(internal, #encrypted_extensions{extensions = Extensions}, State0) -> + case handle_encrypted_extensions(Extensions, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0); + {State, NextState} -> + tls_gen_connection:next_event(NextState, no_record, State) + end; +wait_ee(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_ee(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec wait_cert_cr(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cert_cr(enter, _, State0) -> + State = tls_gen_connection_1_3:handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg, State) -> + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, + ?FUNCTION_NAME, State); +wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) -> + case handle_certificate(Certificate, State0) of + {#alert{} = Alert, State} -> + ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State); + {State1, NextState} -> + tls_gen_connection:next_event(NextState, no_record, State1) + end; +wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, + State0) -> + case handle_certificate_request(CertificateRequest, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State0); + {State1, NextState} -> + tls_gen_connection:next_event(NextState, no_record, State1) + end; +wait_cert_cr(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_cert_cr(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec wait_cert(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cert(Type, Msg, State) -> + tls_gen_connection_1_3:wait_cert(Type, Msg, State). + +%%-------------------------------------------------------------------- +-spec wait_cv(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cv(internal, + #certificate_verify_1_3{} = CertificateVerify, State0) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + {State, NextState} + = Maybe(tls_handshake_1_3:verify_certificate_verify(State0, + CertificateVerify)), + tls_gen_connection:next_event(NextState, no_record, State) + catch + {Ref, {#alert{} = Alert, AState}} -> + ssl_gen_statem:handle_own_alert(Alert, wait_cv, AState) + end; +wait_cv(Type, Msg, State) -> + tls_gen_connection_1_3:wait_cv(Type, Msg, State). + +%%-------------------------------------------------------------------- +-spec wait_finished(gen_statem:event_type(), + #finished{} | #change_cipher_spec{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_finished(enter, _, State0) -> + State = tls_gen_connection_1_3:handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) -> + tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, + ?FUNCTION_NAME, State); +wait_finished(internal, + #finished{verify_data = VerifyData}, + #state{static_env = #static_env{protocol_cb = Connection}} + = State0) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + Maybe(tls_handshake_1_3:validate_finished(State0, VerifyData)), + %% D.4. Middlebox Compatibility Mode + State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0, + first), + %% Signal change of cipher + State2 = maybe_send_end_of_early_data(State1), + %% Maybe send Certificate + CertificateVerify + State3 = Maybe(maybe_queue_cert_cert_cv(State2)), + Finished = tls_handshake_1_3:finished(State3), + %% Encode Finished + State4 = Connection:queue_handshake(Finished, State3), + %% Send first flight + {State5, _} = Connection:send_handshake_flight(State4), + State6 = tls_handshake_1_3:calculate_traffic_secrets(State5), + State7 = + tls_handshake_1_3:maybe_calculate_resumption_master_secret(State6), + State8 = tls_handshake_1_3:forget_master_secret(State7), + %% Configure traffic keys + State9 = ssl_record:step_encryption_state(State8), + {Record, State} = ssl_gen_statem:prepare_connection(State9, + tls_gen_connection), + tls_gen_connection:next_event(connection, Record, State, + [{{timeout, handshake}, cancel}]) + catch + {Ref, #alert{} = Alert} -> + ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0) + end; +wait_finished(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +wait_finished(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(Type, Msg, State) -> + tls_gen_connection_1_3:connection(Type, Msg, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), + term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(Type, Msg, State) -> + tls_gen_connection_1_3:downgrade(Type, Msg, State). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State0) -> + case do_handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, + State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, start, State0); + {State, NextState} -> + {next_state, NextState, State, []} + end. + +do_handle_exlusive_1_3_hello_or_hello_retry_request( + #server_hello{cipher_suite = SelectedCipherSuite, + session_id = SessionId, + extensions = Extensions}, + #state{static_env = #static_env{host = Host, + port = Port, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + protocol_cb = Connection, + transport_cb = Transport, + socket = Socket}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState}, + connection_env = #connection_env{negotiated_version = + NegotiatedVersion}, + protocol_specific = PS, + ssl_options = #{ciphers := ClientCiphers, + supported_groups := ClientGroups0, + use_ticket := UseTicket, + session_tickets := SessionTickets, + log_level := LogLevel} = SslOpts, + session = Session0, + connection_states = ConnectionStates0 + } = State0) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + ClientGroups = + Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)), + Cookie = maps:get(cookie, Extensions, undefined), + + KeyShare = maps:get(key_share, Extensions, undefined), + SelectedGroup = server_group(KeyShare), + + %% Upon receipt of this extension in a HelloRetryRequest, the client + %% MUST verify that (1) the selected_group field corresponds to a group + %% which was provided in the "supported_groups" extension in the + %% original ClientHello and (2) the selected_group field does not + %% correspond to a group which was provided in the "key_share" extension + %% in the original ClientHello. If either of these checks fails, then + %% the client MUST abort the handshake with an "illegal_parameter" + %% alert. + case KeyShare of + #key_share_hello_retry_request{} -> + Maybe(validate_selected_group(SelectedGroup, ClientGroups)); + _ -> + ok + end, + Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), + + %% Otherwise, when sending the new ClientHello, the client MUST + %% replace the original "key_share" extension with one containing only a + %% new KeyShareEntry for the group indicated in the selected_group field + %% of the triggering HelloRetryRequest. + ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]), + TicketData = + tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), + OcspNonce = maps:get(ocsp_nonce, OcspState, undefined), + Hello0 = tls_handshake:client_hello(Host, Port, + ConnectionStates0, SslOpts, + SessionId, Renegotiation, + ClientKeyShare, + TicketData, OcspNonce, + CertDbHandle, CertDbRef), + %% Echo cookie received in HelloRetryrequest + Hello1 = tls_handshake_1_3:maybe_add_cookie_extension(Cookie, Hello0), + + %% Update state + State1 = + tls_handshake_1_3:update_start_state(State0, + #{cipher => SelectedCipherSuite, + key_share => ClientKeyShare, + session_id => SessionId, + group => SelectedGroup}), + + %% Replace ClientHello1 with a special synthetic handshake message + State2 = tls_handshake_1_3:replace_ch1_with_message_hash(State1), + #state{handshake_env = + #handshake_env{tls_handshake_history = HHistory0}} = State2, + + %% Update pre_shared_key extension with binders (TLS 1.3) + Hello = + tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0, + TicketData, NegotiatedVersion), + + {BinMsg0, ConnectionStates, HHistory} = + Connection:encode_handshake(Hello, + NegotiatedVersion, + ConnectionStates0, + HHistory0), + + %% D.4. Middlebox Compatibility Mode + {#state{handshake_env = HsEnv} = State3, BinMsg} = + tls_gen_connection_1_3:maybe_prepend_change_cipher_spec(State2, + BinMsg0), + + tls_socket:send(Transport, Socket, BinMsg), + ssl_logger:debug(LogLevel, outbound, 'handshake', Hello), + ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), + + State = State3#state{ + connection_states = ConnectionStates, + session = Session0#session{session_id = + Hello#client_hello.session_id}, + handshake_env = + HsEnv#handshake_env{tls_handshake_history = HHistory}, + key_share = ClientKeyShare}, + + %% If it is a hello_retry and middlebox mode is + %% used assert the change_cipher_spec message + %% that the server should send next + case (maps:get(hello_retry, PS, false)) andalso + (maps:get(middlebox_comp_mode, SslOpts, true)) + of + true -> + {State, hello_retry_middlebox_assert}; + false -> + {State, wait_sh} + end + catch + {Ref, #alert{} = Alert} -> + Alert + end. + +handle_server_hello(#server_hello{cipher_suite = SelectedCipherSuite, + session_id = SessionId, + extensions = Extensions} = ServerHello, + #state{key_share = ClientKeyShare, + ssl_options = #{ciphers := ClientCiphers, + supported_groups := ClientGroups0, + session_tickets := SessionTickets, + use_ticket := UseTicket}} = State0) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + ClientGroups = + Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)), + ServerKeyShare = server_share(maps:get(key_share, Extensions)), + ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined), + + %% Go to state 'start' if server replies with 'HelloRetryRequest'. + Maybe(tls_handshake_1_3:maybe_hello_retry_request(ServerHello, State0)), + + %% Resumption and PSK + State1 = tls_gen_connection_1_3:handle_resumption(State0, + ServerPreSharedKey), + + Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), + Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)), + + %% Get server public key + #key_share_entry{group = SelectedGroup, + key_exchange = ServerPublicKey} = ServerKeyShare, + + ClientPrivateKey = + client_private_key(SelectedGroup, + ClientKeyShare#key_share_client_hello.client_shares), + %% Update state + State2 = tls_handshake_1_3:update_start_state(State1, + #{cipher => SelectedCipherSuite, + key_share => ClientKeyShare, + session_id => SessionId, + group => SelectedGroup, + peer_public_key => ServerPublicKey}), + + #state{connection_states = ConnectionStates} = State2, + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, read), + #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, + + PSK = Maybe(tls_handshake_1_3:get_pre_shared_key(SessionTickets, + UseTicket, + HKDFAlgo, + ServerPreSharedKey)), + State3 = + tls_handshake_1_3:calculate_handshake_secrets(ServerPublicKey, + ClientPrivateKey, + SelectedGroup, + PSK, State2), + State4 = ssl_record:step_encryption_state_read(State3), + {State4, wait_ee} + catch + {Ref, {State, StateName, ServerHello}} -> + {State, StateName, ServerHello}; + {Ref, #alert{} = Alert} -> + Alert + end. + +handle_encrypted_extensions(Extensions, State0) -> + {Ref, Maybe} = tls_gen_connection_1_3:do_maybe(), + try + ALPNProtocol0 = maps:get(alpn, Extensions, undefined), + ALPNProtocol = decode_alpn(ALPNProtocol0), + EarlyDataIndication = maps:get(early_data, Extensions, undefined), + + %% RFC 6066: handle received/expected maximum fragment length + Maybe(maybe_max_fragment_length(Extensions, State0)), + + %% Check if early_data is accepted/rejected + State1 = maybe_check_early_data_indication(EarlyDataIndication, State0), + + %% Go to state 'wait_finished' if using PSK. + Maybe(maybe_resumption(State1)), + + %% Update state + #state{handshake_env = HsEnv} = State1, + State2 = State1#state{handshake_env = + HsEnv#handshake_env{alpn = ALPNProtocol}}, + {State2, wait_cert_cr} + catch + {Ref, #alert{} = Alert} -> + Alert; + {Ref, {_, _} = Next} -> + Next + end. + +handle_certificate(#certificate_1_3{} = Certificate, State0) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + Maybe(tls_handshake_1_3:process_certificate(Certificate, State0)) + catch + {Ref, #alert{} = Alert} -> + {Alert, State0}; + {Ref, {#alert{} = Alert, State}} -> + {Alert, State} + end. +handle_certificate_request(#certificate_request_1_3{} = + CertificateRequest, State0) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + Maybe(tls_handshake_1_3:process_certificate_request( + CertificateRequest, State0)) + catch + {Ref, #alert{} = Alert} -> + {Alert, State0} + end. + +maybe_send_early_data(#state{ + handshake_env = + #handshake_env{tls_handshake_history = {Hist, _}}, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [?TLS_1_3|_], + use_ticket := UseTicket, + session_tickets := SessionTickets, + early_data := EarlyData} = _SslOpts0 + } = State0) when UseTicket =/= [undefined] andalso + EarlyData =/= undefined -> + %% D.4. Middlebox Compatibility Mode + State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0, last), + %% Early traffic secret + EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData), + case tls_handshake_1_3:get_pre_shared_key_early_data(SessionTickets, + UseTicket) of + {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize -> + State2 = + tls_handshake_1_3:calculate_client_early_traffic_secret(Hist, + PSK, + Cipher, + HKDF, + State1), + %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData + State3 = ssl_record:step_encryption_state_write(State2), + {ok, tls_handshake_1_3:encode_early_data(Cipher, State3)}; + {ok, {_, _, _, MaxSize}} -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, + {too_much_early_data, {max, MaxSize}})}; + {error, Alert} -> + {error, Alert} + end; +maybe_send_early_data(State) -> + {ok, State}. + +maybe_send_end_of_early_data( + #state{ + handshake_env = #handshake_env{early_data_accepted = true}, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [?TLS_1_3|_], + use_ticket := UseTicket, + early_data := EarlyData}, + static_env = #static_env{protocol_cb = Connection} + } = State0) when UseTicket =/= [undefined] andalso + EarlyData =/= undefined -> + %% EndOfEarlydata is encrypted with the 0-RTT traffic keys + State1 = Connection:queue_handshake(#end_of_early_data{}, State0), + %% Use handshake keys after EndOfEarlyData is sent + ssl_record:step_encryption_state_write(State1); +maybe_send_end_of_early_data(State) -> + State. + +%% Configure a suitable session ticket +maybe_automatic_session_resumption(#state{ssl_options = + #{versions := [Version|_], + ciphers := UserSuites, + early_data := EarlyData, + session_tickets := + SessionTickets, + server_name_indication := SNI} + = SslOpts0 + } = State0) + when ?TLS_GTE(Version, ?TLS_1_3) andalso + SessionTickets =:= auto -> + AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), + HashAlgos = cipher_hash_algos(AvailableCipherSuites), + Ciphers = tls_handshake_1_3:ciphers_for_early_data(AvailableCipherSuites), + %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where + %% Ticket0 satisfies requirements for early_data and session + %% resumption while Ticket2 can only be used for session + %% resumption. + EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData), + KeyPair = + tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos, + SNI, EarlyDataSize), + UseTicket = tls_handshake_1_3:choose_ticket(KeyPair, EarlyData), + tls_client_ticket_store:lock_tickets(self(), [UseTicket]), + State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}}, + {[UseTicket], State}; +maybe_automatic_session_resumption(#state{ + ssl_options = #{use_ticket := UseTicket} + } = State) -> + {UseTicket, State}. + +maybe_resumption(#state{handshake_env = + #handshake_env{resumption = true}} = State) -> + {error, {State, wait_finished}}; +maybe_resumption(_) -> + ok. + +server_group(undefined) -> + undefined; +server_group(#key_share_server_hello{server_share = #key_share_entry{group = Group}}) -> + Group; +server_group(#key_share_hello_retry_request{selected_group = Group}) -> + Group. + +server_share(#key_share_server_hello{server_share = Share}) -> + Share; +server_share(#key_share_hello_retry_request{selected_group = Share}) -> + Share. + +client_private_key(Group, ClientShares) -> + case lists:keysearch(Group, 2, ClientShares) of + {value, #key_share_entry{key_exchange = + ClientPrivateKey = #'ECPrivateKey'{}}} -> + ClientPrivateKey; + {value, #key_share_entry{key_exchange = {_, ClientPrivateKey}}} -> + ClientPrivateKey; + false -> + no_suitable_key + end. + +maybe_check_early_data_indication(EarlyDataIndication, + #state{ + handshake_env = HsEnv, + ssl_options = #{versions := [?TLS_1_3|_], + use_ticket := UseTicket, + early_data := EarlyData} + } = State) + when UseTicket =/= [undefined] andalso + EarlyData =/= undefined andalso + EarlyDataIndication =/= undefined -> + signal_user_early_data(State, accepted), + State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}}; +maybe_check_early_data_indication(EarlyDataIndication, + #state{ + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [?TLS_1_3|_], + use_ticket := UseTicket, + early_data := EarlyData} + = _SslOpts0 + } = State) + when UseTicket =/= [undefined] andalso + EarlyData =/= undefined andalso + EarlyDataIndication =:= undefined -> + signal_user_early_data(State, rejected), + %% Use handshake keys if early_data is rejected. + ssl_record:step_encryption_state_write(State); +maybe_check_early_data_indication(_, State) -> + %% Use handshake keys if there is no early_data. + ssl_record:step_encryption_state_write(State). + +signal_user_early_data(#state{ + connection_env = + #connection_env{ + user_application = {_, User}}, + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + trackers = Trackers}} = State, + Result) -> + CPids = Connection:pids(State), + SslSocket = Connection:socket(CPids, Transport, Socket, Trackers), + User ! {ssl, SslSocket, {early_data, Result}}. + +maybe_max_fragment_length(Extensions, State) -> + ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined), + ClientMaxFragEnum = ssl_handshake:max_frag_enum( + maps:get(max_fragment_length, + State#state.ssl_options, undefined)), + if ServerMaxFragEnum == ClientMaxFragEnum -> + ok; + true -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)} + end. + +cipher_hash_algos(Ciphers) -> + Fun = fun(Cipher) -> + #{prf := Hash} = ssl_cipher_format:suite_bin_to_map(Cipher), + Hash + end, + lists:map(Fun, Ciphers). + +maybe_queue_cert_cert_cv(#state{client_certificate_status = not_requested} + = State) -> + {ok, State}; +maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, + session = #session{session_id = _SessionId, + own_certificates = OwnCerts}, + ssl_options = #{} = _SslOpts, + key_share = _KeyShare, + handshake_env = + #handshake_env{tls_handshake_history = + _HHistory0}, + static_env = #static_env{ + protocol_cb = Connection, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + socket = _Socket, + transport_cb = _Transport} + } = State0) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + %% Create Certificate + Certificate = Maybe(tls_handshake_1_3:certificate(OwnCerts, + CertDbHandle, + CertDbRef, <<>>, + client)), + + %% Encode Certificate + State1 = Connection:queue_handshake(Certificate, State0), + %% Maybe create and queue CertificateVerify + State = Maybe(maybe_queue_cert_verify(Certificate, State1)), + {ok, State} + catch + {Ref, #alert{} = Alert} -> + {error, Alert} + end. + +%% Clients MUST send this message whenever authenticating via a certificate +%% (i.e., when the Certificate message is non-empty). +maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) -> + {ok, State}; +maybe_queue_cert_verify(_Certificate, + #state{connection_states = _ConnectionStates0, + session = #session{sign_alg = SignatureScheme, + private_key = CertPrivateKey}, + static_env = #static_env{protocol_cb = Connection} + } = State) -> + {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(), + try + CertificateVerify = + Maybe(tls_handshake_1_3:certificate_verify(CertPrivateKey, + SignatureScheme, + State, client)), + {ok, Connection:queue_handshake(CertificateVerify, State)} + catch + {Ref, #alert{} = Alert} -> + {error, Alert} + end. + +decode_alpn(undefined) -> + undefined; +decode_alpn(Encoded) -> + [Decoded] = ssl_handshake:decode_alpn(Encoded), + Decoded. + +%% Verify that selected group is offered by the client. +validate_server_key_share([], _) -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; +validate_server_key_share([Group |_ClientGroups], #key_share_entry{group = Group}) -> + ok; +validate_server_key_share([_|ClientGroups], #key_share_entry{} = ServerKeyShare) -> + validate_server_key_share(ClientGroups, ServerKeyShare). + + +validate_selected_group(SelectedGroup, [SelectedGroup|_]) -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, + "Selected group sent by the server shall not correspond to a group" + " which was provided in the key_share extension")}; +validate_selected_group(SelectedGroup, ClientGroups) -> + case lists:member(SelectedGroup, ClientGroups) of + true -> + ok; + false -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, + "Selected group sent by the server shall correspond to a group" + " which was provided in the supported_groups extension")} + end. + +%% RFC 8446 4.1.3 ServerHello +%% A client which receives a cipher suite that was not offered MUST abort the +%% handshake with an "illegal_parameter" alert. +validate_cipher_suite(Cipher, ClientCiphers) -> + case lists:member(Cipher, ClientCiphers) of + true -> + ok; + false -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)} + end. |