%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2013-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also
%% tls_connection.erl and dtls_connection.erl
%%----------------------------------------------------------------------
-module(ssl_connection).
-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").
-include_lib("public_key/include/public_key.hrl").
-export([hello/3, abbreviated/3, certify/3, cipher/3, connection/3]).
%%--------------------------------------------------------------------
-spec hello(start | #hello_request{} | #server_hello{} | term(),
#state{}, tls_connection | dtls_connection) ->
gen_fsm_state_return().
%%--------------------------------------------------------------------
hello(start, #state{role = server} = State0, Connection) ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(hello, hello, Record, State);
hello(#hello_request{}, #state{role = client} = State0, Connection) ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(hello, hello, Record, State);
hello({common_client_hello, Type, ServerHelloExt, HashSign},
#state{session = #session{cipher_suite = CipherSuite},
negotiated_version = Version} = State, Connection) ->
{KeyAlg, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
NegotiatedHashSign = negotiated_hashsign(HashSign, KeyAlg, Version),
do_server_hello(Type, ServerHelloExt,
State#state{hashsign_algorithm = NegotiatedHashSign}, Connection);
hello(timeout, State, _) ->
{next_state, hello, State, hibernate};
hello(Msg, State, Connection) ->
Connection:handle_unexpected_message(Msg, hello, State).
%%--------------------------------------------------------------------
-spec abbreviated(#hello_request{} | #finished{} | term(),
#state{}, tls_connection | dtls_connection) ->
gen_fsm_state_return().
%%--------------------------------------------------------------------
abbreviated(#hello_request{}, State0, Connection) ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(abbreviated, hello, Record, State);
abbreviated(#finished{verify_data = Data} = Finished,
#state{role = server,
negotiated_version = Version,
tls_handshake_history = Handshake,
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} =
State, Connection) ->
case ssl_handshake:verify_connection(Version, Finished, client,
get_current_prf(ConnectionStates0, write),
MasterSecret, Handshake) of
verified ->
ConnectionStates =
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
Connection:next_state_connection(abbreviated,
Connection:ack_connection(
State#state{connection_states = ConnectionStates}));
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, abbreviated, State)
end;
abbreviated(#finished{verify_data = Data} = Finished,
#state{role = client, tls_handshake_history = Handshake0,
session = #session{master_secret = MasterSecret},
negotiated_version = Version,
connection_states = ConnectionStates0} = State0, Connection) ->
case ssl_handshake:verify_connection(Version, Finished, server,
get_pending_prf(ConnectionStates0, write),
MasterSecret, Handshake0) of
verified ->
ConnectionStates1 =
ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
State =
finalize_handshake(State0#state{connection_states = ConnectionStates1},
abbreviated, Connection),
Connection:next_state_connection(abbreviated,
Connection:ack_connection(State));
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, abbreviated, State0)
end;
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
abbreviated(#next_protocol{selected_protocol = SelectedProtocol},
#state{role = server, expecting_next_protocol_negotiation = true} = State0,
Connection) ->
{Record, State} = Connection:next_record(State0#state{next_protocol = SelectedProtocol}),
Connection:next_state(abbreviated, abbreviated, Record, State);
abbreviated(timeout, State, _) ->
{next_state, abbreviated, State, hibernate };
abbreviated(Msg, State, Connection) ->
Connection:handle_unexpected_message(Msg, abbreviated, State).
%%--------------------------------------------------------------------
-spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} |
#certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
#state{}, tls_connection | dtls_connection) ->
gen_fsm_state_return().
%%--------------------------------------------------------------------
certify(#hello_request{}, State0, Connection) ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(certify, hello, Record, State);
certify(#certificate{asn1_certificates = []},
#state{role = server, negotiated_version = Version,
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = true}} =
State, Connection) ->
Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
Connection:handle_own_alert(Alert, Version, certify, State);
certify(#certificate{asn1_certificates = []},
#state{role = server,
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = false}} =
State0, Connection) ->
{Record, State} = Connection:next_record(State0#state{client_certificate_requested = false}),
Connection:next_state(certify, certify, Record, State);
certify(#certificate{} = Cert,
#state{negotiated_version = Version,
role = Role,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
ssl_options = Opts} = State, Connection) ->
case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth,
Opts#ssl_options.verify,
Opts#ssl_options.verify_fun, Role) of
{PeerCert, PublicKeyInfo} ->
handle_peer_cert(Role, PeerCert, PublicKeyInfo,
State#state{client_certificate_requested = false}, Connection);
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, certify, State)
end;
certify(#server_key_exchange{exchange_keys = Keys},
#state{role = client, negotiated_version = Version,
key_algorithm = Alg,
public_key_info = PubKeyInfo,
connection_states = ConnectionStates} = State, Connection)
when Alg == dhe_dss; Alg == dhe_rsa;
Alg == ecdhe_rsa; Alg == ecdhe_ecdsa;
Alg == dh_anon; Alg == ecdh_anon;
Alg == psk; Alg == dhe_psk; Alg == rsa_psk;
Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
Params = ssl_handshake:decode_server_key(Keys, Alg, Version),
HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, Version),
case is_anonymous(Alg) of
true ->
calculate_secret(Params#server_key_params.params,
State#state{hashsign_algorithm = HashSign}, Connection);
false ->
case ssl_handshake:verify_server_key(Params, HashSign, ConnectionStates, Version, PubKeyInfo) of
true ->
calculate_secret(Params#server_key_params.params,
State#state{hashsign_algorithm = HashSign}, Connection);
false ->
?ALERT_REC(?FATAL, ?DECRYPT_ERROR)
end
end;
certify(#server_key_exchange{} = Msg,
#state{role = client, key_algorithm = rsa} = State, Connection) ->
Connection:handle_unexpected_message(Msg, certify_server_keyexchange, State);
certify(#certificate_request{hashsign_algorithms = HashSigns},
#state{session = #session{own_certificate = Cert}} = State0, Connection) ->
HashSign = ssl_handshake:select_hashsign(HashSigns, Cert),
{Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
Connection:next_state(certify, certify, Record,
State#state{cert_hashsign_algorithm = HashSign});
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(#server_hello_done{},
#state{session = #session{master_secret = undefined},
negotiated_version = Version,
psk_identity = PSKIdentity,
ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
premaster_secret = undefined,
role = client,
key_algorithm = Alg} = State0, Connection)
when Alg == psk ->
case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, certify, State0);
PremasterSecret ->
State = master_secret(PremasterSecret,
State0#state{premaster_secret = PremasterSecret}),
client_certify_and_key_exchange(State, Connection)
end;
certify(#server_hello_done{},
#state{session = #session{master_secret = undefined},
ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
negotiated_version = {Major, Minor},
psk_identity = PSKIdentity,
premaster_secret = undefined,
role = client,
key_algorithm = Alg} = State0, Connection)
when Alg == rsa_psk ->
Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of
#alert{} = Alert ->
Alert;
PremasterSecret ->
State = master_secret(PremasterSecret, State0#state{premaster_secret = RSAPremasterSecret}),
client_certify_and_key_exchange(State, Connection)
end;
%% Master secret was determined with help of server-key exchange msg
certify(#server_hello_done{},
#state{session = #session{master_secret = MasterSecret} = Session,
connection_states = ConnectionStates0,
negotiated_version = Version,
premaster_secret = undefined,
role = client} = State0, Connection) ->
case ssl_handshake:master_secret(record_cb(Connection), Version, Session,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
State = State0#state{connection_states = ConnectionStates},
client_certify_and_key_exchange(State, Connection);
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, certify, State0)
end;
%% Master secret is calculated from premaster_secret
certify(#server_hello_done{},
#state{session = Session0,
connection_states = ConnectionStates0,
negotiated_version = Version,
premaster_secret = PremasterSecret,
role = client} = State0, Connection) ->
case ssl_handshake:master_secret(record_cb(Connection), 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 ->
Connection:handle_own_alert(Alert, Version, certify, State0)
end;
certify(#client_key_exchange{} = Msg,
#state{role = server,
client_certificate_requested = true,
ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, Connection) ->
%% We expect a certificate here
Connection:handle_unexpected_message(Msg, certify_client_key_exchange, State);
certify(#client_key_exchange{exchange_keys = Keys},
State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) ->
try
certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version),
State, Connection)
catch
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, certify, State)
end;
certify(timeout, State, _) ->
{next_state, certify, State, hibernate};
certify(Msg, State, Connection) ->
Connection:handle_unexpected_message(Msg, certify, State).
%%--------------------------------------------------------------------
-spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(),
#state{}, tls_connection | dtls_connection) ->
gen_fsm_state_return().
%%--------------------------------------------------------------------
cipher(#hello_request{}, State0, Connection) ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(cipher, hello, Record, State);
cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign},
#state{role = server,
public_key_info = {Algo, _, _} =PublicKeyInfo,
negotiated_version = Version,
session = #session{master_secret = MasterSecret},
tls_handshake_history = Handshake
} = State0, Connection) ->
HashSign = ssl_handshake:select_cert_hashsign(CertHashSign, Algo, Version),
case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
Version, HashSign, MasterSecret, Handshake) of
valid ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(cipher, cipher, Record,
State#state{cert_hashsign_algorithm = HashSign});
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, cipher, State0)
end;
%% client must send a next protocol message if we are expecting it
cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true,
next_protocol = undefined, negotiated_version = Version} = State0,
Connection) ->
Connection:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0);
cipher(#finished{verify_data = Data} = Finished,
#state{negotiated_version = Version,
host = Host,
port = Port,
role = Role,
session = #session{master_secret = MasterSecret}
= Session0,
connection_states = ConnectionStates0,
tls_handshake_history = Handshake0} = State, Connection) ->
case ssl_handshake:verify_connection(Version, Finished,
opposite_role(Role),
get_current_prf(ConnectionStates0, read),
MasterSecret, Handshake0) of
verified ->
Session = Connection:register_session(Role, Host, Port, Session0),
cipher_role(Role, Data, Session, State, Connection);
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, cipher, State)
end;
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
cipher(#next_protocol{selected_protocol = SelectedProtocol},
#state{role = server, expecting_next_protocol_negotiation = true} = State0, Connection) ->
{Record, State} = Connection:next_record(State0#state{next_protocol = SelectedProtocol}),
Connection:next_state(cipher, cipher, Record, State);
cipher(timeout, State, _) ->
{next_state, cipher, State, hibernate};
cipher(Msg, State, Connection) ->
Connection:handle_unexpected_message(Msg, cipher, State).
%%--------------------------------------------------------------------
-spec connection(term(), #state{}, tls_connection | dtls_connection) ->
gen_fsm_state_return().
%%--------------------------------------------------------------------
connection(timeout, State, _) ->
{next_state, connection, State, hibernate};
connection(Msg, State, Connection) ->
Connection:handle_unexpected_message(Msg, connection, State).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} =
ServerHelloExt,
#state{negotiated_version = Version,
session = #session{session_id = SessId},
connection_states = ConnectionStates0}
= State0, Connection) when is_atom(Type) ->
ServerHello =
ssl_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt),
State = server_hello(ServerHello,
State0#state{expecting_next_protocol_negotiation =
NextProtocols =/= undefined}, Connection),
case Type of
new ->
new_server_hello(ServerHello, State, Connection);
resumed ->
resumed_server_hello(State, Connection)
end.
new_server_hello(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression,
session_id = SessionId},
#state{session = Session0,
negotiated_version = Version} = State0, Connection) ->
try server_certify_and_key_exchange(State0, Connection) of
#state{} = State1 ->
State2 = server_hello_done(State1, Connection),
Session =
Session0#session{session_id = SessionId,
cipher_suite = CipherSuite,
compression_method = Compression},
{Record, State} = Connection:next_record(State2#state{session = Session}),
Connection:next_state(hello, certify, Record, State)
catch
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, hello, State0)
end.
resumed_server_hello(#state{session = Session,
connection_states = ConnectionStates0,
negotiated_version = Version} = State0, Connection) ->
case ssl_handshake:master_secret(record_cb(Connection), Version, Session,
ConnectionStates0, server) of
{_, ConnectionStates1} ->
State1 = State0#state{connection_states = ConnectionStates1,
session = Session},
State2 =
finalize_handshake(State1, abbreviated, Connection),
{Record, State} = Connection:next_record(State2),
Connection:next_state(hello, abbreviated, Record, State);
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, hello, State0)
end.
server_hello(ServerHello, State0, Connection) ->
CipherSuite = ServerHello#server_hello.cipher_suite,
{KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
State = Connection:send_handshake(ServerHello, State0),
State#state{key_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{session = #session{cipher_suite = CipherSuite} = Session} = State0,
Connection) ->
State1 = State0#state{session =
Session#session{peer_certificate = PeerCert},
public_key_info = PublicKeyInfo},
{KeyAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1),
{Record, State} = Connection:next_record(State2),
Connection:next_state(certify, certify, Record, State).
handle_peer_cert_key(client, _,
{?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
PublicKeyParams},
KeyAlg, 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{diffie_hellman_keys = ECDHKey});
%% We do currently not support cipher suites that use fixed DH.
%% If we want to implement that the following clause can be used
%% to extract DH parameters form cert.
%% handle_peer_cert_key(client, _PeerCert, {?dhpublicnumber, PublicKey, PublicKeyParams},
%% {_,SignAlg},
%% #state{diffie_hellman_keys = {_, MyPrivatKey}} = State) when
%% SignAlg == dh_rsa;
%% SignAlg == dh_dss ->
%% dh_master_secret(PublicKeyParams, PublicKey, MyPrivatKey, State);
handle_peer_cert_key(_, _, _, _, State) ->
State.
certify_client(#state{client_certificate_requested = true, role = client,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
session = #session{own_certificate = OwnCert}}
= State, Connection) ->
Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
Connection:send_handshake(Certificate, State);
certify_client(#state{client_certificate_requested = false} = State, _) ->
State.
verify_client_cert(#state{client_certificate_requested = true, role = client,
negotiated_version = Version,
private_key = PrivateKey,
session = #session{master_secret = MasterSecret,
own_certificate = OwnCert},
cert_hashsign_algorithm = HashSign,
tls_handshake_history = Handshake0} = State, Connection) ->
case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
Version, HashSign, PrivateKey, Handshake0) of
#certificate_verify{} = Verified ->
Connection:send_handshake(Verified, State);
ignore ->
State;
#alert{} = Alert ->
throw(Alert)
end;
verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
State.
client_certify_and_key_exchange(#state{negotiated_version = Version} =
State0, Connection) ->
try do_client_certify_and_key_exchange(State0, Connection) of
State1 = #state{} ->
State2 = finalize_handshake(State1, certify, Connection),
State3 = State2#state{
%% Reinitialize
client_certificate_requested = false},
{Record, State} = Connection:next_record(State3),
Connection:next_state(certify, cipher, Record, State)
catch
throw:#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, certify, State0)
end.
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{private_key = Key} = State, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(EncPMS, Key),
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
#state{diffie_hellman_params = #'DHParameter'{} = Params,
diffie_hellman_keys = {_, ServerDhPrivateKey}} = State,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
#state{diffie_hellman_keys = ECDHKey} = State, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_psk_identity{} = ClientKey,
#state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
#state{diffie_hellman_params = #'DHParameter'{} = Params,
diffie_hellman_keys = {_, ServerDhPrivateKey},
ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
#state{private_key = Key,
ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
certify_client_key_exchange(#client_srp_public{} = ClientKey,
#state{srp_params = Params,
srp_keys = Key
} = State0, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
certify_server(#state{key_algorithm = Algo} = State, _)
when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; Algo == srp_anon ->
State;
certify_server(#state{cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
session = #session{own_certificate = OwnCert}} = State, Connection) ->
case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
Cert = #certificate{} ->
Connection:send_handshake(Cert, State);
Alert = #alert{} ->
throw(Alert)
end.
key_exchange(#state{role = server, key_algorithm = rsa} = State,_) ->
State;
key_exchange(#state{role = server, key_algorithm = Algo,
hashsign_algorithm = HashSignAlgo,
diffie_hellman_params = #'DHParameter'{} = Params,
private_key = PrivateKey,
connection_states = ConnectionStates0,
negotiated_version = Version
} = State0, Connection)
when Algo == dhe_dss;
Algo == dhe_rsa;
Algo == dh_anon ->
DHKeys = public_key:generate_key(Params),
ConnectionState =
ssl_record:pending_connection_state(ConnectionStates0, read),
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
State = Connection:send_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State, _)
when Algo == ecdh_ecdsa; Algo == ecdh_rsa ->
State#state{diffie_hellman_keys = Key};
key_exchange(#state{role = server, key_algorithm = Algo,
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
connection_states = ConnectionStates0,
negotiated_version = Version
} = State0, Connection)
when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa;
Algo == ecdh_anon ->
ECDHKeys = public_key:generate_key(select_curve(State0)),
ConnectionState =
ssl_record:pending_connection_state(ConnectionStates0, read),
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
State = Connection:send_handshake(Msg, State0),
State#state{diffie_hellman_keys = ECDHKeys};
key_exchange(#state{role = server, key_algorithm = psk,
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
key_exchange(#state{role = server, key_algorithm = psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
connection_states = ConnectionStates0,
negotiated_version = Version
} = State0, Connection) ->
ConnectionState =
ssl_record:pending_connection_state(ConnectionStates0, read),
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = server, key_algorithm = dhe_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
diffie_hellman_params = #'DHParameter'{} = Params,
private_key = PrivateKey,
connection_states = ConnectionStates0,
negotiated_version = Version
} = State0, Connection) ->
DHKeys = public_key:generate_key(Params),
ConnectionState =
ssl_record:pending_connection_state(ConnectionStates0, read),
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
State = Connection:send_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
key_exchange(#state{role = server, key_algorithm = rsa_psk,
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
key_exchange(#state{role = server, key_algorithm = rsa_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
connection_states = ConnectionStates0,
negotiated_version = Version
} = State0, Connection) ->
ConnectionState =
ssl_record:pending_connection_state(ConnectionStates0, read),
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = server, key_algorithm = Algo,
ssl_options = #ssl_options{user_lookup_fun = LookupFun},
hashsign_algorithm = HashSignAlgo,
session = #session{srp_username = Username},
private_key = PrivateKey,
connection_states = ConnectionStates0,
negotiated_version = Version
} = State0, Connection)
when Algo == srp_dss;
Algo == srp_rsa;
Algo == srp_anon ->
SrpParams = handle_srp_identity(Username, LookupFun),
Keys = case generate_srp_server_keys(SrpParams, 0) of
Alert = #alert{} ->
throw(Alert);
Keys0 = {_,_} ->
Keys0
end,
ConnectionState =
ssl_record:pending_connection_state(ConnectionStates0, read),
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{client_random = ClientRandom,
server_random = ServerRandom} = SecParams,
Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
State = Connection:send_handshake(Msg, State0),
State#state{srp_params = SrpParams,
srp_keys = Keys};
key_exchange(#state{role = client,
key_algorithm = rsa,
public_key_info = PublicKeyInfo,
negotiated_version = Version,
premaster_secret = PremasterSecret} = State0, Connection) ->
Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = client,
key_algorithm = Algorithm,
negotiated_version = Version,
diffie_hellman_keys = {DhPubKey, _}
} = State0, Connection)
when Algorithm == dhe_dss;
Algorithm == dhe_rsa;
Algorithm == dh_anon ->
Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = client,
key_algorithm = Algorithm,
negotiated_version = Version,
diffie_hellman_keys = Keys} = State0, Connection)
when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa;
Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa;
Algorithm == ecdh_anon ->
Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = client,
ssl_options = SslOpts,
key_algorithm = psk,
negotiated_version = Version} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = client,
ssl_options = SslOpts,
key_algorithm = dhe_psk,
negotiated_version = Version,
diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, Version,
{dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = client,
ssl_options = SslOpts,
key_algorithm = rsa_psk,
public_key_info = PublicKeyInfo,
negotiated_version = Version,
premaster_secret = PremasterSecret}
= State0, Connection) ->
Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity,
PremasterSecret, PublicKeyInfo),
Connection:send_handshake(Msg, State0);
key_exchange(#state{role = client,
key_algorithm = Algorithm,
negotiated_version = Version,
srp_keys = {ClientPubKey, _}}
= State0, Connection)
when Algorithm == srp_dss;
Algorithm == srp_rsa;
Algorithm == srp_anon ->
Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}),
Connection:send_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, Version,
{premaster_secret, PremasterSecret,
PublicKeyInfo});
rsa_key_exchange(_, _, _) ->
throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
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, Version,
{psk_premaster_secret, PskIdentity, PremasterSecret,
PublicKeyInfo});
rsa_psk_key_exchange(_, _, _, _) ->
throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
connection_states = ConnectionStates0,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
negotiated_version = Version} = State0, Connection) ->
#connection_state{security_parameters =
#security_parameters{cipher_suite = CipherSuite}} =
ssl_record:pending_connection_state(ConnectionStates0, read),
Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version),
State = Connection:send_handshake(Msg, State0),
State#state{client_certificate_requested = true};
request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
State, _) ->
State.
calculate_master_secret(PremasterSecret, #state{negotiated_version = Version,
connection_states = ConnectionStates0,
session = Session0} = State0, Connection,
Current, Next) ->
case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret,
ConnectionStates0, server) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
State1 = State0#state{connection_states = ConnectionStates,
session = Session},
{Record, State} = Connection:next_record(State1),
Connection:next_state(Current, Next, Record, State);
#alert{} = Alert ->
Connection:handle_own_alert(Alert, Version, certify, State0)
end.
finalize_handshake(State0, StateName, Connection) ->
#state{connection_states = ConnectionStates0} =
State1 = cipher_protocol(State0, Connection),
ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0,
write),
State2 = State1#state{connection_states = ConnectionStates},
State = next_protocol(State2, Connection),
finished(State, StateName, Connection).
next_protocol(#state{role = server} = State, _) ->
State;
next_protocol(#state{next_protocol = undefined} = State, _) ->
State;
next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) ->
State;
next_protocol(#state{next_protocol = NextProtocol} = State0, Connection) ->
NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
Connection:send_handshake(NextProtocolMessage, State0).
cipher_protocol(State, Connection) ->
Connection:send_change_cipher(#change_cipher_spec{}, State).
finished(#state{role = Role, negotiated_version = Version,
session = Session,
connection_states = ConnectionStates0,
tls_handshake_history = Handshake0} = State0, StateName, Connection) ->
MasterSecret = Session#session.master_secret,
Finished = ssl_handshake:finished(Version, Role,
get_current_prf(ConnectionStates0, write),
MasterSecret, Handshake0),
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, Connection) ->
Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
PremasterSecret =
ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
calculate_master_secret(PremasterSecret,
State#state{diffie_hellman_keys = Keys}, Connection, certify, certify);
calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
State, Connection) ->
ECDHKeys = public_key:generate_key(ECCurve),
PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
calculate_master_secret(PremasterSecret,
State#state{diffie_hellman_keys = ECDHKeys}, Connection, certify, certify);
calculate_secret(#server_psk_params{
hint = IdentityHint},
State0, Connection) ->
%% store for later use
{Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}),
Connection:next_state(certify, certify, Record, State);
calculate_secret(#server_dhe_psk_params{
dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
#state{ssl_options = #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{diffie_hellman_keys = Keys},
Connection, certify, certify);
calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
#state{ssl_options = #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{srp_keys = Keys}, Connection, certify, certify).
master_secret(#alert{} = Alert, _) ->
Alert;
master_secret(PremasterSecret, #state{session = Session,
negotiated_version = Version, role = Role,
connection_states = ConnectionStates0} = State) ->
case ssl_handshake:master_secret(tls_record, Version, PremasterSecret,
ConnectionStates0, Role) of
{MasterSecret, ConnectionStates} ->
State#state{
session =
Session#session{master_secret = MasterSecret},
connection_states = ConnectionStates};
#alert{} = Alert ->
Alert
end.
generate_srp_server_keys(_SrpParams, 10) ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
generate_srp_server_keys(SrpParams =
#srp_user{generator = Generator, prime = Prime,
verifier = Verifier}, N) ->
case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
error ->
generate_srp_server_keys(SrpParams, N+1);
Keys ->
Keys
end.
generate_srp_client_keys(_Generator, _Prime, 10) ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
generate_srp_client_keys(Generator, Prime, N) ->
case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
error ->
generate_srp_client_keys(Generator, Prime, N+1);
Keys ->
Keys
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{connection_states = ConnectionStates0} = State,
Connection) ->
ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0),
Connection:next_state_connection(cipher,
Connection:ack_connection(
State#state{session = Session,
connection_states = ConnectionStates}));
cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0,
Connection) ->
ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0),
State =
finalize_handshake(State0#state{connection_states = ConnectionStates1,
session = Session}, cipher, Connection),
Connection:next_state_connection(cipher, Connection:ack_connection(State#state{session = Session})).
negotiated_hashsign(undefined, Algo, Version) ->
default_hashsign(Version, Algo);
negotiated_hashsign(HashSign = {_, _}, _, _) ->
HashSign.
%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms
%% If the client does not send the signature_algorithms extension, the
%% server MUST do the following:
%%
%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA,
%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had
%% sent the value {sha1,rsa}.
%%
%% - If the negotiated key exchange algorithm is one of (DHE_DSS,
%% DH_DSS), behave as if the client had sent the value {sha1,dsa}.
%%
%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA,
%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
default_hashsign(_Version = {Major, Minor}, KeyExchange)
when Major >= 3 andalso Minor >= 3 andalso
(KeyExchange == rsa orelse
KeyExchange == dhe_rsa orelse
KeyExchange == dh_rsa orelse
KeyExchange == ecdhe_rsa orelse
KeyExchange == ecdh_rsa orelse
KeyExchange == srp_rsa) ->
{sha, rsa};
default_hashsign(_Version, KeyExchange)
when KeyExchange == rsa;
KeyExchange == dhe_rsa;
KeyExchange == dh_rsa;
KeyExchange == ecdhe_rsa;
KeyExchange == ecdh_rsa;
KeyExchange == srp_rsa ->
{md5sha, rsa};
default_hashsign(_Version, KeyExchange)
when KeyExchange == ecdhe_ecdsa;
KeyExchange == ecdh_ecdsa ->
{sha, ecdsa};
default_hashsign(_Version, KeyExchange)
when KeyExchange == dhe_dss;
KeyExchange == dh_dss;
KeyExchange == srp_dss ->
{sha, dsa};
default_hashsign(_Version, KeyExchange)
when KeyExchange == dh_anon;
KeyExchange == ecdh_anon;
KeyExchange == psk;
KeyExchange == dhe_psk;
KeyExchange == rsa_psk;
KeyExchange == srp_anon ->
{null, anon}.
select_curve(#state{client_ecc = {[Curve|_], _}}) ->
{namedCurve, Curve};
select_curve(_) ->
{namedCurve, ?secp256k1}.
is_anonymous(Algo) when Algo == dh_anon;
Algo == ecdh_anon;
Algo == psk;
Algo == dhe_psk;
Algo == rsa_psk;
Algo == srp_anon ->
true;
is_anonymous(_) ->
false.
get_current_prf(CStates, Direction) ->
CS = ssl_record:current_connection_state(CStates, Direction),
CS#connection_state.security_parameters#security_parameters.prf_algorithm.
get_pending_prf(CStates, Direction) ->
CS = ssl_record:pending_connection_state(CStates, Direction),
CS#connection_state.security_parameters#security_parameters.prf_algorithm.
opposite_role(client) ->
server;
opposite_role(server) ->
client.
record_cb(tls_connection) ->
tls_record;
record_cb(dtls_connection) ->
dtls_record.