File: //usr/lib/erlang/lib/ssl-10.8.3/src/tls_dtls_connection.erl
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2013-2022. 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: Common handling of a TLS/SSL/DTLS connection, see also
%% tls_connection.erl and dtls_connection.erl
%%
%% NOTE: All alerts are thrown out of this module
%%----------------------------------------------------------------------
-module(tls_dtls_connection).
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/logger.hrl").
-include("ssl_api.hrl").
-include("ssl_connection.hrl").
-include("ssl_handshake.hrl").
-include("ssl_alert.hrl").
-include("ssl_record.hrl").
-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
%% TLS-1.0 to TLS-1.2 Specific User Events
-export([renegotiation/1, renegotiation/2, prf/5]).
%% Data handling. Note renegotiation is replaced by sesion key update mechanism in TLS-1.3
-export([internal_renegotiation/2]).
%% Help functions for tls|dtls_connection.erl
-export([handle_session/7,
handle_sni_extension/2]).
%% General state handlingfor TLS-1.0 to TLS-1.2 and gen_handshake that wraps
%% handling of common state handling for handshake messages for error handling
-export([hello/3,
user_hello/3,
abbreviated/3,
certify/3,
wait_cert_verify/3,
wait_ocsp_stapling/3,
cipher/3,
connection/3,
downgrade/3,
gen_handshake/4]).
%%--------------------------------------------------------------------
-spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
ok.
%%
%% Description: Starts a renegotiation of the ssl session.
%%--------------------------------------------------------------------
internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
%%====================================================================
%% User events
%%====================================================================
%%--------------------------------------------------------------------
-spec renegotiation(pid()) -> ok | {error, reason()}.
%%
%% Description: Starts a renegotiation of the ssl session.
%%--------------------------------------------------------------------
renegotiation(ConnectionPid) ->
ssl_gen_statem:call(ConnectionPid, renegotiate).
renegotiation(Pid, WriteState) ->
ssl_gen_statem:call(Pid, {user_renegotiate, WriteState}).
%%--------------------------------------------------------------------
-spec prf(pid(), binary() | 'master_secret', binary(),
[binary() | ssl:prf_random()], non_neg_integer()) ->
{ok, binary()} | {error, reason()} | {'EXIT', term()}.
%%
%% Description: use a ssl sessions TLS PRF to generate key material
%%--------------------------------------------------------------------
prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
ssl_gen_statem:call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
%%====================================================================
%% Help functions for tls|dtls_connection.erl
%%====================================================================
%%--------------------------------------------------------------------
-spec handle_session(#server_hello{}, ssl_record:ssl_version(),
binary(), ssl_record:connection_states(), _,_, #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
handle_session(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression},
Version, NewId, ConnectionStates, ProtoExt, Protocol0,
#state{session = #session{session_id = OldId},
handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv,
connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) ->
#{key_exchange := KeyAlgorithm} =
ssl_cipher_format:suite_bin_to_map(CipherSuite),
PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
{ExpectNPN, Protocol} =
case Protocol0 of
undefined -> {false, CurrentProtocol};
_ -> {ProtoExt =:= npn, Protocol0}
end,
State = State0#state{connection_states = ConnectionStates,
handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm,
premaster_secret = PremasterSecret,
expecting_next_protocol_negotiation = ExpectNPN,
negotiated_protocol = Protocol},
connection_env = CEnv#connection_env{negotiated_version = Version}},
case ssl_session:is_new(OldId, NewId) of
true ->
handle_new_session(NewId, CipherSuite, Compression,
State#state{connection_states = ConnectionStates});
false ->
handle_resumed_session(NewId,
State#state{connection_states = ConnectionStates})
end.
%%====================================================================
%% gen_statem general state functions with connection cb argument
%%====================================================================
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
#hello_request{} | #server_hello{} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
hello({call, From}, Msg, State) ->
handle_call(Msg, From, ?FUNCTION_NAME, State);
hello(internal, {common_client_hello, Type, ServerHelloExt}, State) ->
do_server_hello(Type, ServerHelloExt, State);
hello(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
hello(internal, #hello_request{}, _) ->
keep_state_and_data;
hello(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
-spec user_hello(gen_statem:event_type(),
#hello_request{} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
user_hello({call, From}, cancel, _State) ->
gen_statem:reply(From, ok),
throw(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled));
user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
#state{static_env = #static_env{role = Role},
handshake_env = HSEnv,
ssl_options = Options0} = State0) ->
Options = ssl:handle_options(NewOptions, Role, Options0),
State = ssl_gen_statem:ssl_config(Options, Role, State0),
{next_state, hello, State#state{start_or_recv_from = From,
handshake_env = HSEnv#handshake_env{continue_status = continue}
},
[{{timeout, handshake}, Timeout, close}]};
user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
user_hello(_, _, _) ->
{keep_state_and_data, [postpone]}.
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(),
#hello_request{} | #finished{} | term(),
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
abbreviated({call, From}, Msg, State) ->
handle_call(Msg, From, ?FUNCTION_NAME, State);
abbreviated(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = server,
protocol_cb = Connection},
handshake_env = #handshake_env{tls_handshake_history = Hist,
expecting_finished = true} = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} =
State0) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
get_current_prf(ConnectionStates0, write),
MasterSecret, Hist) of
verified ->
ConnectionStates =
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
{Record, State} =
ssl_gen_statem:prepare_connection(State0#state{connection_states = ConnectionStates,
handshake_env = HsEnv#handshake_env{expecting_finished = false}},
Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
#alert{} = Alert ->
throw(Alert)
end;
abbreviated(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
handshake_env = #handshake_env{tls_handshake_history = Hist0},
connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} = State0) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
get_pending_prf(ConnectionStates0, write),
MasterSecret, Hist0) of
verified ->
ConnectionStates1 =
ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
{#state{handshake_env = HsEnv} = State1, Actions} =
finalize_handshake(State0#state{connection_states = ConnectionStates1},
?FUNCTION_NAME, Connection),
{Record, State} =
ssl_gen_statem:prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}},
Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]);
#alert{} = Alert ->
throw(Alert)
end;
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
#state{static_env = #static_env{role = server,
protocol_cb = Connection},
handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
expecting_next_protocol_negotiation = false}});
abbreviated(internal,
#change_cipher_spec{type = <<1>>},
#state{static_env = #static_env{protocol_cb = Connection},
connection_states = ConnectionStates0,
handshake_env = HsEnv} = State) ->
ConnectionStates1 =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
ConnectionStates1,
handshake_env = HsEnv#handshake_env{expecting_finished = true}});
abbreviated(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
abbreviated(internal, #hello_request{}, _) ->
keep_state_and_data;
abbreviated(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
-spec wait_ocsp_stapling(gen_statem:event_type(),
#certificate{} | #certificate_status{} | term(),
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_ocsp_stapling(internal, #certificate{},
#state{static_env = #static_env{protocol_cb = _Connection}} = State) ->
%% Postpone message, should be handled in certify after receiving staple message
{next_state, ?FUNCTION_NAME, State, [{postpone, true}]};
%% Receive OCSP staple message
wait_ocsp_stapling(internal, #certificate_status{} = CertStatus,
#state{static_env = #static_env{protocol_cb = _Connection},
handshake_env = #handshake_env{
ocsp_stapling_state = OcspState} = HsEnv} = State) ->
{next_state, certify, State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
OcspState#{ocsp_expect => stapled,
ocsp_response => CertStatus}}}};
%% Server did not send OCSP staple message
wait_ocsp_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = _Connection},
handshake_env = #handshake_env{
ocsp_stapling_state = OcspState} = HsEnv} = State)
when is_record(Msg, server_key_exchange) orelse
is_record(Msg, hello_request) orelse
is_record(Msg, certificate_request) orelse
is_record(Msg, server_hello_done) orelse
is_record(Msg, client_key_exchange) ->
{next_state, certify, State#state{handshake_env =
HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
[{postpone, true}]};
wait_ocsp_stapling(internal, #hello_request{}, _) ->
keep_state_and_data;
wait_ocsp_stapling(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(),
#hello_request{} | #certificate{} | #server_key_exchange{} |
#certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
certify({call, From}, Msg, State) ->
handle_call(Msg, From, ?FUNCTION_NAME, State);
certify(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
#state{static_env = #static_env{role = server},
ssl_options = #{verify := verify_peer, fail_if_no_peer_cert := true}}) ->
throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided));
certify(internal, #certificate{asn1_certificates = []},
#state{static_env = #static_env{role = server,
protocol_cb = Connection},
ssl_options = #{verify := verify_peer,
fail_if_no_peer_cert := false}} =
State0) ->
Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_status = empty});
certify(internal, #certificate{},
#state{static_env = #static_env{role = server},
ssl_options = #{verify := verify_none}}) ->
throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate));
certify(internal, #certificate{},
#state{static_env = #static_env{protocol_cb = Connection},
handshake_env = #handshake_env{
ocsp_stapling_state = #{ocsp_expect := staple}}} = State) ->
Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]);
certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert,
#state{static_env = #static_env{
role = Role,
host = Host,
protocol_cb = Connection,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
crl_db = CRLDbInfo},
handshake_env = #handshake_env{
ocsp_stapling_state = #{ocsp_expect := Status} = OcspState},
connection_env = #connection_env{
negotiated_version = Version},
ssl_options = Opts} = State0) when Status =/= staple ->
OcspInfo = ocsp_info(OcspState, Opts, Peer),
case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
Opts, CRLDbInfo, Role, Host,
ensure_tls(Version), OcspInfo) of
{PeerCert, PublicKeyInfo} ->
State = case Role of
server ->
State0#state{client_certificate_status = needs_verifying};
client ->
State0
end,
handle_peer_cert(Role, PeerCert, PublicKeyInfo, State, Connection, []);
#alert{} = Alert ->
throw(Alert)
end;
certify(internal, #server_key_exchange{exchange_keys = Keys},
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
public_key_info = PubKeyInfo} = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
session = Session,
connection_states = ConnectionStates} = State)
when KexAlg == dhe_dss;
KexAlg == dhe_rsa;
KexAlg == ecdhe_rsa;
KexAlg == ecdhe_ecdsa;
KexAlg == dh_anon;
KexAlg == ecdh_anon;
KexAlg == psk;
KexAlg == dhe_psk;
KexAlg == ecdhe_psk;
KexAlg == rsa_psk;
KexAlg == srp_dss;
KexAlg == srp_rsa;
KexAlg == srp_anon ->
Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)),
%% Use negotiated value if TLS-1.2 otherwise return default
HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)),
case is_anonymous(KexAlg) of
true ->
calculate_secret(Params#server_key_params.params,
State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection);
false ->
case ssl_handshake:verify_server_key(Params, HashSign,
ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of
true ->
calculate_secret(Params#server_key_params.params,
State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign},
session = session_handle_params(Params#server_key_params.params, Session)},
Connection);
false ->
throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
end
end;
certify(internal, #certificate_request{},
#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = KexAlg}})
when KexAlg == dh_anon;
KexAlg == ecdh_anon;
KexAlg == psk;
KexAlg == dhe_psk;
KexAlg == ecdhe_psk;
KexAlg == rsa_psk;
KexAlg == srp_dss;
KexAlg == srp_rsa;
KexAlg == srp_anon ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE));
certify(internal, #certificate_request{},
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
session = Session0,
connection_env = #connection_env{cert_key_alts = [#{certs := [[]]}]}} = State) ->
%% The client does not have a certificate and will send an empty reply, the server may fail
%% or accept the connection by its own preference. No signature algorithms needed as there is
%% no certificate to verify.
Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_status = requested,
session = Session0#session{own_certificates = [[]],
private_key = #{}}});
certify(internal, #certificate_request{} = CertRequest,
#state{static_env = #static_env{role = client,
protocol_cb = Connection,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
connection_env = #connection_env{negotiated_version = Version,
cert_key_alts = CertKeyAlts
},
session = Session0,
ssl_options = #{signature_algs := SupportedHashSigns}} = State) ->
TLSVersion = ssl:tls_version(Version),
CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, ssl:tls_version(Version)),
Session = select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs,
SupportedHashSigns, TLSVersion,
CertDbHandle, CertDbRef),
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{client_certificate_status = requested,
session = Session});
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
session = #session{master_secret = undefined},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
premaster_secret = undefined,
server_psk_identity = PSKIdentity} = HsEnv,
ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
when KexAlg == psk ->
case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of
#alert{} = Alert ->
throw(Alert);
PremasterSecret ->
State = master_secret(PremasterSecret,
State0#state{handshake_env =
HsEnv#handshake_env{premaster_secret = PremasterSecret}}),
client_certify_and_key_exchange(State, Connection)
end;
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = {Major, Minor}},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
premaster_secret = undefined,
server_psk_identity = PSKIdentity} = HsEnv,
session = #session{master_secret = undefined},
ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
when KexAlg == rsa_psk ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
RSAPremasterSecret) of
#alert{} = Alert ->
throw(Alert);
PremasterSecret ->
State = master_secret(PremasterSecret,
State0#state{handshake_env =
HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}),
client_certify_and_key_exchange(State, Connection)
end;
%% Master secret was determined with help of server-key exchange msg
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version},
handshake_env = #handshake_env{premaster_secret = undefined},
session = #session{master_secret = MasterSecret} = Session,
connection_states = ConnectionStates0} = State0) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
State = State0#state{connection_states = ConnectionStates},
client_certify_and_key_exchange(State, Connection);
#alert{} = Alert ->
throw(Alert)
end;
%% Master secret is calculated from premaster_secret
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client,
protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version},
handshake_env = #handshake_env{premaster_secret = PremasterSecret},
session = Session0,
connection_states = ConnectionStates0} = State0) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
State = State0#state{connection_states = ConnectionStates,
session = Session},
client_certify_and_key_exchange(State, Connection);
#alert{} = Alert ->
throw(Alert)
end;
certify(internal = Type, #client_key_exchange{} = Msg,
#state{static_env = #static_env{role = server},
client_certificate_status = requested,
ssl_options = #{fail_if_no_peer_cert := true}}) ->
%% We expect a certificate here
throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}));
certify(internal, #client_key_exchange{exchange_keys = Keys},
State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg},
static_env = #static_env{protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version}}) ->
try
certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)),
State, Connection)
catch
#alert{} = Alert ->
throw(Alert)
end;
certify(internal, #hello_request{}, _) ->
keep_state_and_data;
certify(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
-spec wait_cert_verify(gen_statem:event_type(),
#hello_request{} | #certificate_verify{} | term(),
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_cert_verify(internal, #certificate_verify{signature = Signature,
hashsign_algorithm = CertHashSign},
#state{static_env = #static_env{role = server,
protocol_cb = Connection},
client_certificate_status = needs_verifying,
handshake_env = #handshake_env{tls_handshake_history = Hist,
kex_algorithm = KexAlg,
public_key_info = PubKeyInfo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret} = Session0
} = State) ->
TLSVersion = ssl:tls_version(Version),
%% Use negotiated value if TLS-1.2 otherwise return default
HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion),
case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
TLSVersion, HashSign, MasterSecret, Hist) of
valid ->
Connection:next_event(cipher, no_record,
State#state{client_certificate_status = verified,
session = Session0#session{sign_alg = HashSign}});
#alert{} = Alert ->
throw(Alert)
end;
wait_cert_verify(internal, #hello_request{}, _) ->
keep_state_and_data;
wait_cert_verify(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(),
#hello_request{} | #finished{} | term(),
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
cipher({call, From}, Msg, State) ->
handle_call(Msg, From, ?FUNCTION_NAME, State);
cipher(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
cipher(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = Role,
host = Host,
port = Port,
trackers = Trackers},
handshake_env = #handshake_env{tls_handshake_history = Hist,
expecting_finished = true} = HsEnv,
connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret}
= Session0,
ssl_options = SslOpts,
connection_states = ConnectionStates0} = State) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
opposite_role(Role),
get_current_prf(ConnectionStates0, read),
MasterSecret, Hist) of
verified ->
Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0),
cipher_role(Role, Data, Session,
State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}});
#alert{} = Alert ->
throw(Alert)
end;
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
#state{static_env = #static_env{role = server, protocol_cb = Connection},
handshake_env = #handshake_env{expecting_finished = true,
expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
expecting_next_protocol_negotiation = false}});
cipher(internal, #change_cipher_spec{type = <<1>>},
#state{handshake_env = HsEnv,
static_env = #static_env{protocol_cb = Connection},
connection_states = ConnectionStates0} = State) ->
ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true},
connection_states = ConnectionStates});
cipher(internal, #hello_request{}, _) ->
keep_state_and_data;
cipher(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = tls_gen_connection},
handshake_env = HsEnv} = State) ->
tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = dtls_gen_connection},
handshake_env = HsEnv} = State) ->
dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
connection({call, From}, negotiated_protocol,
#state{handshake_env = #handshake_env{alpn = undefined,
negotiated_protocol = undefined}} = State) ->
ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
connection({call, From}, negotiated_protocol,
#state{handshake_env = #handshake_env{alpn = undefined,
negotiated_protocol = SelectedProtocol}} = State) ->
ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
[{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, negotiated_protocol,
#state{handshake_env = #handshake_env{alpn = SelectedProtocol,
negotiated_protocol = undefined}} = State) ->
ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
[{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, Msg, State) when element(1, Msg) =:= prf ->
handle_call(Msg, From, ?FUNCTION_NAME, State);
connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = tls_gen_connection},
handshake_env = HsEnv,
connection_states = ConnectionStates}
= State) ->
tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
connection_states = ConnectionStates#{current_write => WriteState}}, []);
connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = dtls_gen_connection},
handshake_env = HsEnv,
connection_states = ConnectionStates}
= State) ->
dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
connection_states = ConnectionStates#{current_write => WriteState}}, []);
connection(internal, {handshake, {#hello_request{} = Handshake, _}},
#state{handshake_env = HsEnv} = State) ->
%% Should not be included in handshake history
{next_state, ?FUNCTION_NAME, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
[{next_event, internal, Handshake}]};
connection(Type, Event, State) ->
ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
downgrade(Type, Event, State) ->
ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
gen_handshake(StateName, Type, Event, State) ->
try
tls_dtls_connection:StateName(Type, Event, State)
catch error:_ ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data))
end.
%%--------------------------------------------------------------------
%% Event handling functions called by state functions to handle
%% common or unexpected events for the state.
%%--------------------------------------------------------------------
handle_call(renegotiate, From, StateName, _) when StateName =/= connection ->
{keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
#state{connection_states = ConnectionStates,
connection_env = #connection_env{negotiated_version = Version}}) ->
#{security_parameters := SecParams} =
ssl_record:current_connection_state(ConnectionStates, read),
#security_parameters{master_secret = MasterSecret,
client_random = ClientRandom,
server_random = ServerRandom,
prf_algorithm = PRFAlgorithm} = SecParams,
Reply = try
SecretToUse = case Secret of
_ when is_binary(Secret) -> Secret;
master_secret -> MasterSecret
end,
SeedToUse = lists:reverse(
lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
(client_random, Acc) -> [ClientRandom|Acc];
(server_random, Acc) -> [ServerRandom|Acc]
end, [], Seed)),
ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
catch
exit:_ -> {error, badarg};
error:Reason -> {error, Reason}
end,
{keep_state_and_data, [{reply, From, Reply}]};
handle_call(Msg, From, StateName, State) ->
ssl_gen_statem:handle_call(Msg, From, StateName, State).
handle_info(Msg, StateName, State) ->
ssl_gen_statem:handle_info(Msg, StateName, State).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
ServerHelloExt,
#state{connection_env = #connection_env{negotiated_version = Version},
static_env = #static_env{protocol_cb = Connection},
handshake_env = HsEnv,
session = #session{session_id = SessId},
connection_states = ConnectionStates0,
ssl_options = #{versions := [HighestVersion|_]}}
= State0) when is_atom(Type) ->
%% TLS 1.3 - Section 4.1.3
%% Override server random values for TLS 1.3 downgrade protection mechanism.
ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion),
State1 = State0#state{connection_states = ConnectionStates1},
ServerHello =
ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
ConnectionStates1, ServerHelloExt),
State = server_hello(ServerHello,
State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
NextProtocols =/= undefined}}, Connection),
case Type of
new ->
new_server_hello(ServerHello, State, Connection);
resumed ->
resumed_server_hello(State, Connection)
end.
update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} =
ReadState0,
pending_write := #{security_parameters := WriteSecParams0} =
WriteState0} = ConnectionStates,
Version, HighestVersion) ->
ReadRandom = override_server_random(
ReadSecParams0#security_parameters.server_random,
Version,
HighestVersion),
WriteRandom = override_server_random(
WriteSecParams0#security_parameters.server_random,
Version,
HighestVersion),
ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom},
WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom},
ReadState = ReadState0#{security_parameters => ReadSecParams},
WriteState = WriteState0#{security_parameters => WriteSecParams},
ConnectionStates#{pending_read => ReadState, pending_write => WriteState}.
%% TLS 1.3 - Section 4.1.3
%%
%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes
%% of their Random value to the bytes:
%%
%% 44 4F 57 4E 47 52 44 01
%%
%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2
%% servers SHOULD set the last eight bytes of their Random value to the
%% bytes:
%%
%% 44 4F 57 4E 47 52 44 00
override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above
if M =:= 3 andalso N =:= 3 -> %% Negotiating TLS 1.2
Down = ?RANDOM_OVERRIDE_TLS12,
<<Random0/binary,Down/binary>>;
M =:= 3 andalso N < 3 -> %% Negotiating TLS 1.1 or prior
Down = ?RANDOM_OVERRIDE_TLS11,
<<Random0/binary,Down/binary>>;
true ->
Random
end;
override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2
if M =:= 3 andalso N < 3 -> %% Negotiating TLS 1.1 or prior
Down = ?RANDOM_OVERRIDE_TLS11,
<<Random0/binary,Down/binary>>;
true ->
Random
end;
override_server_random(Random, _, _) ->
Random.
new_server_hello(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression,
session_id = SessionId},
#state{session = Session0,
static_env = #static_env{protocol_cb = Connection}} = State0, Connection) ->
#state{} = State1 = server_certify_and_key_exchange(State0, Connection),
{State, Actions} = server_hello_done(State1, Connection),
Session = Session0#session{session_id = SessionId,
cipher_suite = CipherSuite,
compression_method = Compression},
Connection:next_event(certify, no_record, State#state{session = Session}, Actions).
resumed_server_hello(#state{session = Session,
connection_states = ConnectionStates0,
static_env = #static_env{protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, server) of
{_, ConnectionStates1} ->
State1 = State0#state{connection_states = ConnectionStates1,
session = Session},
{State, Actions} =
finalize_handshake(State1, abbreviated, Connection),
Connection:next_event(abbreviated, no_record, State, Actions);
#alert{} = Alert ->
throw(Alert)
end.
server_hello(ServerHello, State0, Connection) ->
CipherSuite = ServerHello#server_hello.cipher_suite,
#{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
#state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0),
State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}.
server_hello_done(State, Connection) ->
HelloDone = ssl_handshake:server_hello_done(),
Connection:send_handshake(HelloDone, State).
handle_peer_cert(Role, PeerCert, PublicKeyInfo,
#state{handshake_env = HsEnv,
static_env = #static_env{protocol_cb = Connection},
session = #session{cipher_suite = CipherSuite} = Session} = State0,
Connection, Actions) ->
State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo},
session =
Session#session{peer_certificate = PeerCert}},
#{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
Connection:next_event(certify, no_record, State, Actions).
handle_peer_cert_key(client, _,
{?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
PublicKeyParams},
KeyAlg, #state{handshake_env = HsEnv,
session = Session} = State) when KeyAlg == ecdh_rsa;
KeyAlg == ecdh_ecdsa ->
ECDHKey = public_key:generate_key(PublicKeyParams),
PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey},
session = Session#session{ecc = PublicKeyParams}});
handle_peer_cert_key(_, _, _, _, State) ->
State.
certify_client(#state{static_env = #static_env{role = client,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
client_certificate_status = requested,
session = #session{own_certificates = OwnCerts}}
= State, Connection) ->
Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client),
Connection:queue_handshake(Certificate, State);
certify_client(#state{client_certificate_status = not_requested} = State, _) ->
State.
verify_client_cert(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{tls_handshake_history = Hist},
connection_env = #connection_env{negotiated_version = Version},
client_certificate_status = requested,
session = #session{sign_alg = HashSign,
master_secret = MasterSecret,
private_key = PrivateKey,
own_certificates = OwnCerts}} = State, Connection) ->
case ssl_handshake:client_certificate_verify(OwnCerts, MasterSecret,
ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
#certificate_verify{} = Verified ->
Connection:queue_handshake(Verified, State);
ignore ->
State;
#alert{} = Alert ->
throw(Alert)
end;
verify_client_cert(#state{client_certificate_status = not_requested} = State, _) ->
State.
client_certify_and_key_exchange(State0, Connection) ->
State1 = do_client_certify_and_key_exchange(State0, Connection),
{State2, Actions} = finalize_handshake(State1, certify, Connection),
State = State2#state{client_certificate_status = not_requested}, %% Reinitialize
Connection:next_event(cipher, no_record, State, Actions).
do_client_certify_and_key_exchange(State0, Connection) ->
State1 = certify_client(State0, Connection),
State2 = key_exchange(State1, Connection),
verify_client_cert(State2, Connection).
server_certify_and_key_exchange(State0, Connection) ->
State1 = certify_server(State0, Connection),
State2 = key_exchange(State1, Connection),
request_client_cert(State2, Connection).
certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
#state{session = #session{private_key = PrivateKey},
handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version},
client_certificate_status = CCStatus}
= State, Connection) ->
FakeSecret = make_premaster_secret(Version, rsa),
%% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
%% and fail handshake later.RFC 5246 section 7.4.7.1.
PremasterSecret =
try ssl_handshake:premaster_secret(EncPMS, PrivateKey) of
Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES ->
case Secret of
<<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct
<<?BYTE(Major), ?BYTE(Minor), Rest/binary>>;
<<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch
<<?BYTE(Major), ?BYTE(Minor), Rest/binary>>
end;
_ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES
FakeSecret
catch
#alert{description = ?DECRYPT_ERROR} ->
FakeSecret
end,
calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
#state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
kex_keys = {_, ServerDhPrivateKey}},
client_certificate_status = CCStatus
} = State,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
#state{handshake_env = #handshake_env{kex_keys = ECDHKey},
client_certificate_status = CCStatus
} = State, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_psk_identity{} = ClientKey,
#state{ssl_options =
#{user_lookup_fun := PSKLookup},
client_certificate_status = CCStatus
} = State0,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
#state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
kex_keys = {_, ServerDhPrivateKey}},
ssl_options =
#{user_lookup_fun := PSKLookup},
client_certificate_status = CCStatus
} = State0,
Connection) ->
PremasterSecret =
ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
#state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey},
ssl_options =
#{user_lookup_fun := PSKLookup},
client_certificate_status = CCStatus
} = State,
Connection) ->
PremasterSecret =
ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup),
calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
#state{session = #session{private_key = PrivateKey},
ssl_options =
#{user_lookup_fun := PSKLookup},
client_certificate_status = CCStatus
} = State0,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PrivateKey, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus));
certify_client_key_exchange(#client_srp_public{} = ClientKey,
#state{handshake_env = #handshake_env{srp_params = Params,
kex_keys = Key},
client_certificate_status = CCStatus
} = State0, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)).
client_kex_next_state(needs_verifying) ->
wait_cert_verify;
client_kex_next_state(empty) ->
cipher;
client_kex_next_state(not_requested) ->
cipher.
certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} =
State, _) when KexAlg == dh_anon;
KexAlg == ecdh_anon;
KexAlg == psk;
KexAlg == dhe_psk;
KexAlg == ecdhe_psk;
KexAlg == srp_anon ->
State;
certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
session = #session{own_certificates = OwnCerts}} = State, Connection) ->
Cert = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, server),
#certificate{} = Cert, %% Assert
Connection:queue_handshake(Cert, State).
key_exchange(#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) ->
State;
key_exchange(#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
diffie_hellman_params = #'DHParameter'{} = Params,
hashsign_algorithm = HashSignAlgo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{private_key = PrivateKey},
connection_states = ConnectionStates0} = State0, Connection)
when KexAlg == dhe_dss;
KexAlg == dhe_rsa;
KexAlg == dh_anon ->
DHKeys = public_key:generate_key(Params),
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
#state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
key_exchange(#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv,
session = #session{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key} = Session} = State, _)
when KexAlg == ecdh_ecdsa;
KexAlg == ecdh_rsa ->
State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key},
session = Session#session{ecc = ECCurve}};
key_exchange(#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
hashsign_algorithm = HashSignAlgo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{ecc = ECCCurve, private_key = PrivateKey},
connection_states = ConnectionStates0} = State0, Connection)
when KexAlg == ecdhe_ecdsa;
KexAlg == ecdhe_rsa;
KexAlg == ecdh_anon ->
ECDHKeys = public_key:generate_key(ECCCurve),
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
{ecdh, ECDHKeys,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
#state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
key_exchange(#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{kex_algorithm = psk},
ssl_options = #{psk_identity := undefined}} = State, _) ->
State;
key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #{psk_identity := PskIdentityHint},
handshake_env = #handshake_env{kex_algorithm = psk,
hashsign_algorithm = HashSignAlgo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{private_key = PrivateKey},
connection_states = ConnectionStates0} = State0, Connection) ->
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
{psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #{psk_identity := PskIdentityHint},
handshake_env = #handshake_env{kex_algorithm = dhe_psk,
diffie_hellman_params = #'DHParameter'{} = Params,
hashsign_algorithm = HashSignAlgo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{private_key = PrivateKey},
connection_states = ConnectionStates0
} = State0, Connection) ->
DHKeys = public_key:generate_key(Params),
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
{dhe_psk,
PskIdentityHint, DHKeys, Params,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
#state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #{psk_identity := PskIdentityHint},
handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
hashsign_algorithm = HashSignAlgo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{ecc = ECCCurve, private_key = PrivateKey},
connection_states = ConnectionStates0
} = State0, Connection) ->
ECDHKeys = public_key:generate_key(ECCCurve),
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
{ecdhe_psk,
PskIdentityHint, ECDHKeys,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
#state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
key_exchange(#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{kex_algorithm = rsa_psk},
ssl_options = #{psk_identity := undefined}} = State, _) ->
State;
key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #{psk_identity := PskIdentityHint},
handshake_env = #handshake_env{kex_algorithm = rsa_psk,
hashsign_algorithm = HashSignAlgo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{private_key = PrivateKey},
connection_states = ConnectionStates0
} = State0, Connection) ->
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
{psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #{user_lookup_fun := LookupFun},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
hashsign_algorithm = HashSignAlgo},
connection_env = #connection_env{negotiated_version = Version},
session = #session{srp_username = Username, private_key = PrivateKey},
connection_states = ConnectionStates0
} = State0, Connection)
when KexAlg == srp_dss;
KexAlg == srp_rsa;
KexAlg == srp_anon ->
SrpParams = handle_srp_identity(Username, LookupFun),
Keys = generate_srp_server_keys(SrpParams, 0),
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
{srp, Keys, SrpParams,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
#state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams,
kex_keys = Keys}};
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = rsa,
public_key_info = PublicKeyInfo,
premaster_secret = PremasterSecret},
connection_env = #connection_env{negotiated_version = Version}
} = State0, Connection) ->
Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
kex_keys = {DhPubKey, _}},
connection_env = #connection_env{negotiated_version = Version}
} = State0, Connection)
when KexAlg == dhe_dss;
KexAlg == dhe_rsa;
KexAlg == dh_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key},
connection_env = #connection_env{negotiated_version = Version},
session = Session
} = State0, Connection)
when KexAlg == ecdhe_ecdsa;
KexAlg == ecdhe_rsa;
KexAlg == ecdh_ecdsa;
KexAlg == ecdh_rsa;
KexAlg == ecdh_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = psk},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{psk, PSKIdentity}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = dhe_psk,
kex_keys = {DhPubKey, _}},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{dhe_psk,
PSKIdentity, DhPubKey}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
kex_keys = ECDHKeys},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{ecdhe_psk,
PSKIdentity, ECDHKeys}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = rsa_psk,
public_key_info = PublicKeyInfo,
premaster_secret = PremasterSecret},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = #{psk_identity := PSKIdentity}}
= State0, Connection) ->
Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity,
PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
handshake_env = #handshake_env{kex_algorithm = KexAlg,
kex_keys = {ClientPubKey, _}},
connection_env = #connection_env{negotiated_version = Version}}
= State0, Connection)
when KexAlg == srp_dss;
KexAlg == srp_rsa;
KexAlg == srp_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}),
Connection:queue_handshake(Msg, State0).
rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
when Algorithm == ?rsaEncryption;
Algorithm == ?md2WithRSAEncryption;
Algorithm == ?md5WithRSAEncryption;
Algorithm == ?sha1WithRSAEncryption;
Algorithm == ?sha224WithRSAEncryption;
Algorithm == ?sha256WithRSAEncryption;
Algorithm == ?sha384WithRSAEncryption;
Algorithm == ?sha512WithRSAEncryption
->
ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{premaster_secret, PremasterSecret,
PublicKeyInfo});
rsa_key_exchange(_, _, _) ->
throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
PublicKeyInfo = {Algorithm, _, _})
when Algorithm == ?rsaEncryption;
Algorithm == ?md2WithRSAEncryption;
Algorithm == ?md5WithRSAEncryption;
Algorithm == ?sha1WithRSAEncryption;
Algorithm == ?sha224WithRSAEncryption;
Algorithm == ?sha256WithRSAEncryption;
Algorithm == ?sha384WithRSAEncryption;
Algorithm == ?sha512WithRSAEncryption
->
ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{psk_premaster_secret, PskIdentity, PremasterSecret,
PublicKeyInfo});
rsa_psk_key_exchange(_, _, _, _) ->
throw(?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _)
when Alg == dh_anon;
Alg == ecdh_anon;
Alg == psk;
Alg == dhe_psk;
Alg == ecdhe_psk;
Alg == rsa_psk;
Alg == srp_dss;
Alg == srp_rsa;
Alg == srp_anon ->
State;
request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
connection_env = #connection_env{negotiated_version = Version},
ssl_options = #{verify := verify_peer} = Opts} = State0, Connection) ->
SupportedHashSigns = maps:get(signature_algs, Opts, undefined),
TLSVersion = ssl:tls_version(Version),
HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns,
TLSVersion),
Msg = ssl_handshake:certificate_request(CertDbHandle, CertDbRef,
HashSigns, TLSVersion),
State = Connection:queue_handshake(Msg, State0),
State#state{client_certificate_status = requested};
request_client_cert(#state{ssl_options = #{verify := verify_none}} =
State, _) ->
State.
calculate_master_secret(PremasterSecret,
#state{connection_env = #connection_env{negotiated_version = Version},
connection_states = ConnectionStates0,
session = Session0} = State0, Connection,
_Current, Next) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, server) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
State = State0#state{connection_states = ConnectionStates,
session = Session},
Connection:next_event(Next, no_record, State);
#alert{} = Alert ->
throw(Alert)
end.
finalize_handshake(State0, StateName, Connection) ->
#state{connection_states = ConnectionStates0} =
State1 = cipher_protocol(State0, Connection),
ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0,
write, Connection),
State2 = State1#state{connection_states = ConnectionStates},
State = next_protocol(State2, Connection),
finished(State, StateName, Connection).
next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
State;
next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
State;
next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) ->
State;
next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) ->
NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
Connection:queue_handshake(NextProtocolMessage, State0).
cipher_protocol(State, Connection) ->
Connection:queue_change_cipher(#change_cipher_spec{}, State).
finished(#state{static_env = #static_env{role = Role},
handshake_env = #handshake_env{tls_handshake_history = Hist},
connection_env = #connection_env{negotiated_version = Version},
session = Session,
connection_states = ConnectionStates0} = State0,
StateName, Connection) ->
MasterSecret = Session#session.master_secret,
Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
get_current_prf(ConnectionStates0, write),
MasterSecret, Hist),
ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
Connection:send_handshake(Finished, State0#state{connection_states =
ConnectionStates}).
save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
ssl_record:set_client_verify_data(current_write, Data, ConnectionStates);
save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) ->
ssl_record:set_server_verify_data(current_both, Data, ConnectionStates);
save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates);
save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
ssl_record:set_server_verify_data(current_write, Data, ConnectionStates).
calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base,
dh_y = ServerPublicDhKey} = Params,
#state{handshake_env = HsEnv} = State, Connection) ->
Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
PremasterSecret =
ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
calculate_master_secret(PremasterSecret,
State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
Connection, certify, certify);
calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
#state{handshake_env = HsEnv,
session = Session} = State, Connection) ->
ECDHKeys = public_key:generate_key(ECCurve),
PremasterSecret =
ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
calculate_master_secret(PremasterSecret,
State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
session = Session#session{ecc = ECCurve}},
Connection, certify, certify);
calculate_secret(#server_psk_params{
hint = IdentityHint},
#state{handshake_env = HsEnv} = State, Connection) ->
%% store for later use
Connection:next_event(certify, no_record,
State#state{handshake_env =
HsEnv#handshake_env{server_psk_identity = IdentityHint}});
calculate_secret(#server_dhe_psk_params{
dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
#state{handshake_env = HsEnv,
ssl_options = #{user_lookup_fun := PSKLookup}} =
State, Connection) ->
Keys = {_, PrivateDhKey} =
crypto:generate_key(dh, [Prime, Base]),
PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup),
calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
Connection, certify, certify);
calculate_secret(#server_ecdhe_psk_params{
dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey,
#state{ssl_options = #{user_lookup_fun := PSKLookup}} =
#state{handshake_env = HsEnv,
session = Session} = State, Connection) ->
ECDHKeys = public_key:generate_key(ECCurve),
PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup),
calculate_master_secret(PremasterSecret,
State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
session = Session#session{ecc = ECCurve}},
Connection, certify, certify);
calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
#state{handshake_env = HsEnv,
ssl_options = #{srp_identity := SRPId}} = State,
Connection) ->
Keys = generate_srp_client_keys(Generator, Prime, 0),
PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId),
calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection,
certify, certify).
master_secret(#alert{} = Alert, _) ->
throw(Alert);
master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
connection_env = #connection_env{negotiated_version = Version},
session = Session,
connection_states = ConnectionStates0} = State) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, Role) of
{MasterSecret, ConnectionStates} ->
State#state{
session =
Session#session{master_secret = MasterSecret},
connection_states = ConnectionStates};
#alert{} = Alert ->
throw(Alert)
end.
generate_srp_server_keys(_SrpParams, 10) ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
generate_srp_server_keys(SrpParams =
#srp_user{generator = Generator, prime = Prime,
verifier = Verifier}, N) ->
try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
Keys ->
Keys
catch
error:_ ->
generate_srp_server_keys(SrpParams, N+1)
end.
generate_srp_client_keys(_Generator, _Prime, 10) ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
generate_srp_client_keys(Generator, Prime, N) ->
try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
Keys ->
Keys
catch
error:_ ->
generate_srp_client_keys(Generator, Prime, N+1)
end.
handle_srp_identity(Username, {Fun, UserState}) ->
case Fun(srp, Username, UserState) of
{ok, {SRPParams, Salt, DerivedKey}}
when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) ->
{Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams),
Verifier = crypto:mod_pow(Generator, DerivedKey, Prime),
#srp_user{generator = Generator, prime = Prime,
salt = Salt, verifier = Verifier};
#alert{} = Alert ->
throw(Alert);
_ ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
end.
cipher_role(client, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
connection_states = ConnectionStates0} = State0) ->
ConnectionStates = ssl_record:set_server_verify_data(current_both, Data,
ConnectionStates0),
{Record, State} = ssl_gen_statem:prepare_connection(State0#state{session = Session,
connection_states = ConnectionStates},
Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
cipher_role(server, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
connection_states = ConnectionStates0} = State0) ->
ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data,
ConnectionStates0),
{State1, Actions} =
finalize_handshake(State0#state{connection_states = ConnectionStates1,
session = Session}, cipher, Connection),
{Record, State} = ssl_gen_statem:prepare_connection(State1, Connection),
Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]).
is_anonymous(KexAlg) when KexAlg == dh_anon;
KexAlg == ecdh_anon;
KexAlg == psk;
KexAlg == dhe_psk;
KexAlg == ecdhe_psk;
KexAlg == rsa_psk;
KexAlg == srp_anon ->
true;
is_anonymous(_) ->
false.
get_current_prf(CStates, Direction) ->
#{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction),
SecParams#security_parameters.prf_algorithm.
get_pending_prf(CStates, Direction) ->
#{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction),
SecParams#security_parameters.prf_algorithm.
opposite_role(client) ->
server;
opposite_role(server) ->
client.
session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
Session#session{ecc = ECCurve};
session_handle_params(_, Session) ->
Session.
handle_session(server, #{reuse_sessions := true},
_Host, _Port, Trackers, #session{is_resumable = false} = Session) ->
Tracker = proplists:get_value(session_id_tracker, Trackers),
server_register_session(Tracker, Session#session{is_resumable = true});
handle_session(Role = client, #{verify := verify_peer,
reuse_sessions := Reuse} = SslOpts,
Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false ->
client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true},
reg_type(Reuse));
handle_session(_,_,_,_,_, Session) ->
Session.
reg_type(save) ->
true;
reg_type(true) ->
unique.
client_register_session(Host, Port, Session, Save) ->
ssl_manager:register_session(Host, Port, Session, Save),
Session.
server_register_session(Tracker, Session) ->
ssl_server_session_cache:register_session(Tracker, Session),
Session.
host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) ->
Hostname;
host_id(_, Host, _) ->
Host.
handle_new_session(NewId, CipherSuite, Compression,
#state{static_env = #static_env{protocol_cb = Connection},
session = Session0
} = State0) ->
Session = Session0#session{session_id = NewId,
cipher_suite = CipherSuite,
compression_method = Compression},
Connection:next_event(certify, no_record, State0#state{session = Session}).
handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
port = Port,
protocol_cb = Connection,
session_cache = Cache,
session_cache_cb = CacheCb},
connection_env = #connection_env{negotiated_version = Version},
connection_states = ConnectionStates0,
ssl_options = Opts} = State) ->
Session = case maps:get(reuse_session, Opts, undefined) of
{SessId,SessionData} when is_binary(SessId), is_binary(SessionData) ->
binary_to_term(SessionData, [safe]);
_Else ->
CacheCb:lookup(Cache, {{Host, Port}, SessId})
end,
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{_, ConnectionStates} ->
Connection:next_event(abbreviated, no_record, State#state{
connection_states = ConnectionStates,
session = Session});
#alert{} = Alert ->
throw(Alert)
end.
make_premaster_secret({MajVer, MinVer}, rsa) ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
<<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
make_premaster_secret(_, _) ->
undefined.
negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
%% Not negotiated choose default
case is_anonymous(KexAlg) of
true ->
{null, anon};
false ->
{PubAlg, _, _} = PubKeyInfo,
ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version)
end;
negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
HashSign.
%% Handle SNI extension in pre-TLS 1.3 and DTLS
handle_sni_extension(#state{static_env =
#static_env{protocol_cb = Connection}} = State0,
Hello) ->
PossibleSNI = Connection:select_sni_extension(Hello),
case ssl_gen_statem:handle_sni_extension(PossibleSNI, State0) of
{ok, State} ->
State;
{error, #alert{}=Alert} ->
throw(Alert)
end.
ensure_tls({254, _} = Version) ->
dtls_v1:corresponding_tls_version(Version);
ensure_tls(Version) ->
Version.
ocsp_info(#{ocsp_expect := stapled,
ocsp_response := CertStatus} = OcspState,
#{ocsp_responder_certs := OcspResponderCerts}, PeerCert) ->
#{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]},
ocsp_responder_certs => OcspResponderCerts,
ocsp_state => OcspState
};
ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) ->
#{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []},
ocsp_responder_certs => [],
ocsp_state => OcspState
}.
select_client_cert_key_pair(Session0,_,
[#{private_key := NoKey, certs := [[]] = NoCerts}],
_,_,_,_) ->
%% No certificate supplied : empty certificate will be sent
Session0#session{own_certificates = NoCerts,
private_key = NoKey};
select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef) ->
select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, undefined).
select_client_cert_key_pair(Session0,_,[], _, _,_,_, undefined) ->
%% No certificate compliant with supported algorithms: empty certificate will be sent
Session0#session{own_certificates = [[]],
private_key = #{}};
select_client_cert_key_pair(_,_,[], _, _,_,_,#session{}=Session) ->
%% No certificate compliant with guide lines send default
Session;
select_client_cert_key_pair(Session0, #certificate_request{certificate_authorities = CertAuths} = CertRequest,
[#{private_key := PrivateKey, certs := [Cert| _] = Certs} | Rest],
SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default) ->
case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, TLSVersion) of
#alert{} ->
select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default);
SelectedHashSign ->
case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of
{ok, EncodedChain} ->
Session0#session{sign_alg = SelectedHashSign,
own_certificates = EncodedChain,
private_key = PrivateKey
};
{error, EncodedChain, not_in_auth_domain} ->
Session = Session0#session{sign_alg = SelectedHashSign,
own_certificates = EncodedChain,
private_key = PrivateKey
},
select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion,
CertDbHandle, CertDbRef, default_cert_key_pair_return(Default, Session))
end
end.
default_cert_key_pair_return(undefined, Session) ->
Session;
default_cert_key_pair_return(Default, _) ->
Default.