summaryrefslogtreecommitdiff
path: root/lib/ssl/src/tls_client_connection_1_3.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/tls_client_connection_1_3.erl')
-rw-r--r--lib/ssl/src/tls_client_connection_1_3.erl1007
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.