aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/ssl_connection.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/ssl_connection.erl')
-rw-r--r--lib/ssl/src/ssl_connection.erl1856
1 files changed, 1856 insertions, 0 deletions
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
new file mode 100644
index 0000000000..82106935cb
--- /dev/null
+++ b/lib/ssl/src/ssl_connection.erl
@@ -0,0 +1,1856 @@
+%%
+%% %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_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").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Setup
+-export([connect/8, ssl_accept/7, handshake/2,
+ socket_control/4]).
+
+%% User Events
+-export([send/2, recv/3, close/1, shutdown/2,
+ new_user/2, get_opts/2, set_opts/2, info/1, session_info/1,
+ peer_certificate/1, renegotiation/1, negotiated_next_protocol/1, prf/5
+ ]).
+
+-export([handle_session/6]).
+
+%% SSL FSM state functions
+-export([hello/3, abbreviated/3, certify/3, cipher/3, connection/3]).
+%% SSL all state functions
+-export([handle_sync_event/4, handle_info/3, terminate/3]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec connect(tls_connection | dtls_connection,
+ host(), inet:port_number(), port(), {#ssl_options{}, #socket_options{}},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Connect to an ssl server.
+%%--------------------------------------------------------------------
+connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+%%--------------------------------------------------------------------
+-spec ssl_accept(tls_connection | dtls_connection,
+ inet:port_number(), port(), {#ssl_options{}, #socket_options{}},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}.
+%%
+%% Description: Starts ssl handshake.
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = Pid}, Timeout) ->
+ case sync_send_all_state_event(Pid, {start, Timeout}) of
+ connected ->
+ ok;
+ Error ->
+ Error
+ end.
+%--------------------------------------------------------------------
+-spec socket_control(tls_connection | dtls_connection, port(), pid(), atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Set the ssl process to own the accept socket
+%%--------------------------------------------------------------------
+socket_control(Connection, Socket, Pid, Transport) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, ssl_socket:socket(Pid, Transport, Socket, Connection)};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%%--------------------------------------------------------------------
+-spec send(pid(), iodata()) -> ok | {error, reason()}.
+%%
+%% Description: Sends data over the ssl connection
+%%--------------------------------------------------------------------
+send(Pid, Data) ->
+ sync_send_all_state_event(Pid, {application_data,
+ %% iolist_to_binary should really
+ %% be called iodata_to_binary()
+ erlang:iolist_to_binary(Data)}).
+
+%%--------------------------------------------------------------------
+-spec recv(pid(), integer(), timeout()) ->
+ {ok, binary() | list()} | {error, reason()}.
+%%
+%% Description: Receives data when active = false
+%%--------------------------------------------------------------------
+recv(Pid, Length, Timeout) ->
+ sync_send_all_state_event(Pid, {recv, Length, Timeout}).
+
+%%--------------------------------------------------------------------
+-spec close(pid()) -> ok | {error, reason()}.
+%%
+%% Description: Close an ssl connection
+%%--------------------------------------------------------------------
+close(ConnectionPid) ->
+ case sync_send_all_state_event(ConnectionPid, close) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+-spec shutdown(pid(), atom()) -> ok | {error, reason()}.
+%%
+%% Description: Same as gen_tcp:shutdown/2
+%%--------------------------------------------------------------------
+shutdown(ConnectionPid, How) ->
+ sync_send_all_state_event(ConnectionPid, {shutdown, How}).
+
+%%--------------------------------------------------------------------
+-spec new_user(pid(), pid()) -> ok | {error, reason()}.
+%%
+%% Description: Changes process that receives the messages when active = true
+%% or once.
+%%--------------------------------------------------------------------
+new_user(ConnectionPid, User) ->
+ sync_send_all_state_event(ConnectionPid, {new_user, User}).
+
+%%--------------------------------------------------------------------
+-spec negotiated_next_protocol(pid()) -> {ok, binary()} | {error, reason()}.
+%%
+%% Description: Returns the negotiated protocol
+%%--------------------------------------------------------------------
+negotiated_next_protocol(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, negotiated_next_protocol).
+
+%%--------------------------------------------------------------------
+-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Same as inet:getopts/2
+%%--------------------------------------------------------------------
+get_opts(ConnectionPid, OptTags) ->
+ sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}).
+%%--------------------------------------------------------------------
+-spec set_opts(pid(), list()) -> ok | {error, reason()}.
+%%
+%% Description: Same as inet:setopts/2
+%%--------------------------------------------------------------------
+set_opts(ConnectionPid, Options) ->
+ sync_send_all_state_event(ConnectionPid, {set_opts, Options}).
+
+%%--------------------------------------------------------------------
+-spec info(pid()) -> {ok, {atom(), tuple()}} | {error, reason()}.
+%%
+%% Description: Returns ssl protocol and cipher used for the connection
+%%--------------------------------------------------------------------
+info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, info).
+
+%%--------------------------------------------------------------------
+-spec session_info(pid()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Returns info about the ssl session
+%%--------------------------------------------------------------------
+session_info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, session_info).
+
+%%--------------------------------------------------------------------
+-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
+%%
+%% Description: Returns the peer cert
+%%--------------------------------------------------------------------
+peer_certificate(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, peer_certificate).
+
+%%--------------------------------------------------------------------
+-spec renegotiation(pid()) -> ok | {error, reason()}.
+%%
+%% Description: Starts a renegotiation of the ssl session.
+%%--------------------------------------------------------------------
+renegotiation(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, renegotiate).
+
+%%--------------------------------------------------------------------
+-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) ->
+ sync_send_all_state_event(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
+
+
+handle_session(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Version, NewId, ConnectionStates, NextProtocol,
+ #state{session = #session{session_id = OldId},
+ negotiated_version = ReqVersion} = State0) ->
+ {KeyAlgorithm, _, _, _} =
+ ssl_cipher:suite_definition(CipherSuite),
+
+ PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
+
+ NewNextProtocol = case NextProtocol of
+ undefined ->
+ State0#state.next_protocol;
+ _ ->
+ NextProtocol
+ end,
+
+ State = State0#state{key_algorithm = KeyAlgorithm,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ premaster_secret = PremasterSecret,
+ expecting_next_protocol_negotiation = NextProtocol =/= undefined,
+ next_protocol = NewNextProtocol},
+
+ 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.
+
+%%--------------------------------------------------------------------
+-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,
+ 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,
+ 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 = 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).
+
+%%--------------------------------------------------------------------
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+handle_sync_event({application_data, Data}, From, connection,
+ #state{protocol_cb = Connection} = State) ->
+ %% We should look into having a worker process to do this to
+ %% parallize send and receive decoding and not block the receiver
+ %% if sending is overloading the socket.
+ try
+ Connection:write_application_data(Data, From, State)
+ catch throw:Error ->
+ {reply, Error, connection, State, get_timeout(State)}
+ end;
+handle_sync_event({application_data, Data}, From, StateName,
+ #state{send_queue = Queue} = State) ->
+ %% In renegotiation priorities handshake, send data when handshake is finished
+ {next_state, StateName,
+ State#state{send_queue = queue:in({From, Data}, Queue)},
+ get_timeout(State)};
+
+handle_sync_event({start, Timeout}, StartFrom, hello, #state{protocol_cb = Connection} = State) ->
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ Connection:hello(start, State#state{start_or_recv_from = StartFrom,
+ timer = Timer});
+
+%% The two clauses below could happen if a server upgrades a socket in
+%% active mode. Note that in this case we are lucky that
+%% controlling_process has been evalueated before receiving handshake
+%% messages from client. The server should put the socket in passive
+%% mode before telling the client that it is willing to upgrade
+%% and before calling ssl:ssl_accept/2. These clauses are
+%% here to make sure it is the users problem and not owers if
+%% they upgrade an active socket.
+handle_sync_event({start,_}, _, connection, State) ->
+ {reply, connected, connection, State, get_timeout(State)};
+handle_sync_event({start,_}, _From, error, {Error, State = #state{}}) ->
+ {stop, {shutdown, Error}, {error, Error}, State};
+
+handle_sync_event({start, Timeout}, StartFrom, StateName, State) ->
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ {next_state, StateName, State#state{start_or_recv_from = StartFrom,
+ timer = Timer}, get_timeout(State)};
+
+handle_sync_event(close, _, StateName, #state{protocol_cb = Connection} = State) ->
+ %% Run terminate before returning
+ %% so that the reuseaddr inet-option will work
+ %% as intended.
+ (catch Connection:terminate(user_close, StateName, State)),
+ {stop, normal, ok, State#state{terminated = true}};
+
+handle_sync_event({shutdown, How0}, _, StateName,
+ #state{transport_cb = Transport,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ socket = Socket} = State) ->
+ case How0 of
+ How when How == write; How == both ->
+ Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ {BinMsg, _} =
+ ssl_alert:encode(Alert, Version, ConnectionStates),
+ Transport:send(Socket, BinMsg);
+ _ ->
+ ok
+ end,
+
+ case Transport:shutdown(Socket, How0) of
+ ok ->
+ {reply, ok, StateName, State, get_timeout(State)};
+ Error ->
+ {stop, normal, Error, State}
+ end;
+
+handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName,
+ #state{protocol_cb = Connection} = State0) ->
+ Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
+ Connection:passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom, timer = Timer}, StateName);
+
+%% Doing renegotiate wait with handling request until renegotiate is
+%% finished. Will be handled by next_state_is_connection/2.
+handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) ->
+ Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
+ {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom,
+ timer = Timer},
+ get_timeout(State)};
+
+handle_sync_event({new_user, User}, _From, StateName,
+ State =#state{user_application = {OldMon, _}}) ->
+ NewMon = erlang:monitor(process, User),
+ erlang:demonitor(OldMon, [flush]),
+ {reply, ok, StateName, State#state{user_application = {NewMon,User}},
+ get_timeout(State)};
+
+handle_sync_event({get_opts, OptTags}, _From, StateName,
+ #state{socket = Socket,
+ transport_cb = Transport,
+ socket_options = SockOpts} = State) ->
+ OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []),
+ {reply, OptsReply, StateName, State, get_timeout(State)};
+
+handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) ->
+ {reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)};
+handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) ->
+ {reply, {ok, NextProtocol}, StateName, State, get_timeout(State)};
+
+handle_sync_event({set_opts, Opts0}, _From, StateName0,
+ #state{socket_options = Opts1,
+ protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport,
+ user_data_buffer = Buffer} = State0) ->
+ {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []),
+ State1 = State0#state{socket_options = Opts},
+ if
+ Opts#socket_options.active =:= false ->
+ {reply, Reply, StateName0, State1, get_timeout(State1)};
+ Buffer =:= <<>>, Opts1#socket_options.active =:= false ->
+ %% Need data, set active once
+ {Record, State2} = Connection:next_record_if_active(State1),
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_state(StateName0, StateName0, Record, State2) of
+ {next_state, StateName, State, Timeout} ->
+ {reply, Reply, StateName, State, Timeout};
+ {stop, Reason, State} ->
+ {stop, Reason, State}
+ end;
+ Buffer =:= <<>> ->
+ %% Active once already set
+ {reply, Reply, StateName0, State1, get_timeout(State1)};
+ true ->
+ case Connection:read_application_data(<<>>, State1) of
+ Stop = {stop,_,_} ->
+ Stop;
+ {Record, State2} ->
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_state(StateName0, StateName0, Record, State2) of
+ {next_state, StateName, State, Timeout} ->
+ {reply, Reply, StateName, State, Timeout};
+ {stop, Reason, State} ->
+ {stop, Reason, State}
+ end
+ end
+ end;
+
+handle_sync_event(renegotiate, From, connection, #state{protocol_cb = Connection} = State) ->
+ Connection:renegotiate(State#state{renegotiation = {true, From}});
+
+handle_sync_event(renegotiate, _, StateName, State) ->
+ {reply, {error, already_renegotiating}, StateName, State, get_timeout(State)};
+
+handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName,
+ #state{connection_states = ConnectionStates,
+ negotiated_version = Version} = State) ->
+ ConnectionState =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{master_secret = MasterSecret,
+ client_random = ClientRandom,
+ server_random = ServerRandom} = 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(Version, SecretToUse, Label, SeedToUse, WantedLength)
+ catch
+ exit:_ -> {error, badarg};
+ error:Reason -> {error, Reason}
+ end,
+ {reply, Reply, StateName, State, get_timeout(State)};
+
+handle_sync_event(info, _, StateName,
+ #state{negotiated_version = Version,
+ session = #session{cipher_suite = Suite}} = State) ->
+
+ AtomVersion = tls_record:protocol_version(Version),
+ {reply, {ok, {AtomVersion, ssl:suite_definition(Suite)}},
+ StateName, State, get_timeout(State)};
+
+handle_sync_event(session_info, _, StateName,
+ #state{session = #session{session_id = Id,
+ cipher_suite = Suite}} = State) ->
+ {reply, [{session_id, Id},
+ {cipher_suite, ssl:suite_definition(Suite)}],
+ StateName, State, get_timeout(State)};
+
+handle_sync_event(peer_certificate, _, StateName,
+ #state{session = #session{peer_certificate = Cert}}
+ = State) ->
+ {reply, {ok, Cert}, StateName, State, get_timeout(State)}.
+
+handle_info({ErrorTag, Socket, econnaborted}, StateName,
+ #state{socket = Socket, transport_cb = Transport,
+ start_or_recv_from = StartFrom, role = Role,
+ protocol_cb = Connection,
+ error_tag = ErrorTag} = State) when StateName =/= connection ->
+ Connection:alert_user(Transport, Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role),
+ {stop, normal, State};
+
+handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
+ protocol_cb = Connection,
+ error_tag = ErrorTag} = State) ->
+ Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
+ error_logger:info_report(Report),
+ Connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
+ {stop, normal, State};
+
+handle_info({'DOWN', MonitorRef, _, _, _}, _,
+ State = #state{user_application={MonitorRef,_Pid}}) ->
+ {stop, normal, State};
+
+%%% So that terminate will be run when supervisor issues shutdown
+handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
+ {stop, shutdown, State};
+handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) ->
+ %% Handle as transport close"
+ {stop, {shutdown, transport_closed}, State};
+
+handle_info(allow_renegotiate, StateName, State) ->
+ {next_state, StateName, State#state{allow_renegotiate = true}, get_timeout(State)};
+
+handle_info({cancel_start_or_recv, StartFrom}, StateName,
+ #state{renegotiation = {false, first}} = State) when StateName =/= connection ->
+ gen_fsm:reply(StartFrom, {error, timeout}),
+ {stop, {shutdown, user_timeout}, State#state{timer = undefined}};
+
+handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) ->
+ gen_fsm:reply(RecvFrom, {error, timeout}),
+ {next_state, StateName, State#state{start_or_recv_from = undefined,
+ bytes_to_read = undefined,
+ timer = undefined}, get_timeout(State)};
+
+handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
+ {next_state, StateName, State#state{timer = undefined}, get_timeout(State)};
+
+handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) ->
+ Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]),
+ error_logger:info_report(Report),
+ {next_state, StateName, State, get_timeout(State)}.
+
+
+terminate(_, _, #state{terminated = true}) ->
+ %% Happens when user closes the connection using ssl:close/1
+ %% we want to guarantee that Transport:close has been called
+ %% when ssl:close/1 returns.
+ ok;
+
+terminate({shutdown, transport_closed}, StateName, #state{send_queue = SendQueue,
+ renegotiation = Renegotiate} = State) ->
+ handle_unrecv_data(StateName, State),
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate);
+
+terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue,
+ renegotiation = Renegotiate} = State) ->
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate);
+
+terminate(Reason, connection, #state{negotiated_version = Version,
+ protocol_cb = Connection,
+ connection_states = ConnectionStates,
+ transport_cb = Transport, socket = Socket,
+ send_queue = SendQueue, renegotiation = Renegotiate} = State) ->
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
+ BinAlert = terminate_alert(Reason, Version, ConnectionStates),
+ Transport:send(Socket, BinAlert),
+ case Connection of
+ tls_connection ->
+ tls_connection:workaround_transport_delivery_problems(Socket, Transport);
+ _ ->
+ ok
+ end;
+
+terminate(_Reason, _StateName, #state{transport_cb = Transport,
+ socket = Socket, send_queue = SendQueue,
+ renegotiation = Renegotiate} = State) ->
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
+ Transport:close(Socket).
+
+%%--------------------------------------------------------------------
+%%% 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,
+ 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, 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, ?secp256r1}.
+
+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.
+
+sync_send_all_state_event(FsmPid, Event) ->
+ try gen_fsm:sync_send_all_state_event(FsmPid, Event, infinity)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{{shutdown, _},_} ->
+ {error, closed}
+ end.
+
+get_socket_opts(_,_,[], _, Acc) ->
+ {ok, Acc};
+get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts,
+ [{mode, SockOpts#socket_options.mode} | Acc]);
+get_socket_opts(Transport, Socket, [packet | Tags], SockOpts, Acc) ->
+ case SockOpts#socket_options.packet of
+ {Type, headers} ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
+ Type ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
+ end;
+get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts,
+ [{header, SockOpts#socket_options.header} | Acc]);
+get_socket_opts(Transport, Socket, [active | Tags], SockOpts, Acc) ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts,
+ [{active, SockOpts#socket_options.active} | Acc]);
+get_socket_opts(Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
+ try ssl_socket:getopts(Transport, Socket, [Tag]) of
+ {ok, [Opt]} ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]);
+ {error, Error} ->
+ {error, {options, {socket_options, Tag, Error}}}
+ catch
+ %% So that inet behavior does not crash our process
+ _:Error -> {error, {options, {socket_options, Tag, Error}}}
+ end;
+get_socket_opts(_, _,Opts, _,_) ->
+ {error, {options, {socket_options, Opts, function_clause}}}.
+
+set_socket_opts(_,_, [], SockOpts, []) ->
+ {ok, SockOpts};
+set_socket_opts(Transport, Socket, [], SockOpts, Other) ->
+ %% Set non emulated options
+ try ssl_socket:setopts(Transport, Socket, Other) of
+ ok ->
+ {ok, SockOpts};
+ {error, InetError} ->
+ {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
+ catch
+ _:Error ->
+ %% So that inet behavior does not crash our process
+ {{error, {options, {socket_options, Other, Error}}}, SockOpts}
+ end;
+
+set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) when Mode == list; Mode == binary ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{mode = Mode}, Other);
+set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) when Packet == raw;
+ Packet == 0;
+ Packet == 1;
+ Packet == 2;
+ Packet == 4;
+ Packet == asn1;
+ Packet == cdr;
+ Packet == sunrm;
+ Packet == fcgi;
+ Packet == tpkt;
+ Packet == line;
+ Packet == http;
+ Packet == httph;
+ Packet == http_bin;
+ Packet == httph_bin ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{packet = Packet}, Other);
+set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) when is_integer(Header) ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{header = Header}, Other);
+set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) ->
+ {{error,{options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) when Active == once;
+ Active == true;
+ Active == false ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+set_socket_opts(Transport, Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]).
+
+start_or_recv_cancel_timer(infinity, _RecvFrom) ->
+ undefined;
+start_or_recv_cancel_timer(Timeout, RecvFrom) ->
+ erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}).
+
+get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) ->
+ infinity;
+get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) ->
+ HibernateAfter.
+
+terminate_alert(Reason, Version, ConnectionStates) when Reason == normal;
+ Reason == user_close ->
+ {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ Version, ConnectionStates),
+ BinAlert;
+terminate_alert({shutdown, _}, Version, ConnectionStates) ->
+ {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ Version, ConnectionStates),
+ BinAlert;
+
+terminate_alert(_, Version, ConnectionStates) ->
+ {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR),
+ Version, ConnectionStates),
+ BinAlert.
+
+handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport,
+ protocol_cb = Connection} = State) ->
+ ssl_socket:setopts(Transport, Socket, [{active, false}]),
+ case Transport:recv(Socket, 0, 0) of
+ {error, closed} ->
+ ok;
+ {ok, Data} ->
+ Connection:handle_close_alert(Data, StateName, State)
+ end.
+
+handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>}}) ->
+ %% No trusted certs specified
+ ok;
+handle_trusted_certs_db(#state{cert_db_ref = Ref,
+ cert_db = CertDb,
+ ssl_options = #ssl_options{cacertfile = undefined}}) ->
+ %% Certs provided as DER directly can not be shared
+ %% with other connections and it is safe to delete them when the connection ends.
+ ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
+handle_trusted_certs_db(#state{file_ref_db = undefined}) ->
+ %% Something went wrong early (typically cacertfile does not exist) so there is nothing to handle
+ ok;
+handle_trusted_certs_db(#state{cert_db_ref = Ref,
+ file_ref_db = RefDb,
+ ssl_options = #ssl_options{cacertfile = File}}) ->
+ case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
+ 0 ->
+ ssl_manager:clean_cert_db(Ref, File);
+ _ ->
+ ok
+ end.
+
+notify_senders(SendQueue) ->
+ lists:foreach(fun({From, _}) ->
+ gen_fsm:reply(From, {error, closed})
+ end, queue:to_list(SendQueue)).
+
+notify_renegotiater({true, From}) when not is_atom(From) ->
+ gen_fsm:reply(From, {error, closed});
+notify_renegotiater(_) ->
+ ok.
+
+ack_connection(#state{renegotiation = {true, Initiater}} = State)
+ when Initiater == internal;
+ Initiater == peer ->
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {true, From}} = State) ->
+ gen_fsm:reply(From, ok),
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {false, first},
+ start_or_recv_from = StartFrom,
+ timer = Timer} = State) when StartFrom =/= undefined ->
+ gen_fsm:reply(StartFrom, connected),
+ cancel_timer(Timer),
+ State#state{renegotiation = undefined, start_or_recv_from = undefined, timer = undefined};
+ack_connection(State) ->
+ State.
+
+cancel_timer(undefined) ->
+ ok;
+cancel_timer(Timer) ->
+ erlang:cancel_timer(Timer),
+ ok.
+
+register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Host, Port, Session),
+ Session;
+register_session(server, _, Port, #session{is_resumable = new} = Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Port, Session),
+ Session;
+register_session(_, _, _, Session) ->
+ Session. %% Already registered
+
+handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0,
+ protocol_cb = Connection} = State0) ->
+ Session = Session0#session{session_id = NewId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {Record, State} = Connection:next_record(State0#state{session = Session}),
+ Connection:next_state(hello, certify, Record, State).
+
+handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ host = Host, port = Port,
+ protocol_cb = Connection,
+ session_cache = Cache,
+ session_cache_cb = CacheCb} = State0) ->
+ Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
+ case ssl_handshake:master_secret(tls_record, Version, Session,
+ ConnectionStates0, client) of
+ {_, ConnectionStates} ->
+ {Record, State} =
+ Connection:next_record(State0#state{
+ connection_states = ConnectionStates,
+ session = Session}),
+ Connection:next_state(hello, abbreviated, Record, State);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+make_premaster_secret({MajVer, MinVer}, rsa) ->
+ Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
+make_premaster_secret(_, _) ->
+ undefined.