summaryrefslogtreecommitdiff
path: root/lib/ssl/src/tls_connection.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/tls_connection.erl')
-rw-r--r--lib/ssl/src/tls_connection.erl2945
1 files changed, 407 insertions, 2538 deletions
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 5618837506..ffa04ee8ba 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -29,21 +29,38 @@
-behaviour(gen_fsm).
+-include("tls_connection.hrl").
-include("tls_handshake.hrl").
-include("ssl_alert.hrl").
-include("tls_record.hrl").
--include("ssl_cipher.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
-include_lib("public_key/include/public_key.hrl").
%% Internal application API
--export([send/2, recv/3, connect/7, ssl_accept/6, handshake/2,
- socket_control/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]).
-%% Called by ssl_connection_sup
+%% Setup
+-export([start_fsm/8]).
+
+%% State transition handling
+-export([next_record/1, next_state/4, next_state_connection/2]).
+
+%% Handshake handling
+-export([renegotiate/1, send_handshake/2, send_change_cipher/2]).
+
+%% Alert and close handling
+-export([send_alert/2, handle_own_alert/4, handle_close_alert/3,
+ handle_normal_shutdown/3, handle_unexpected_message/3,
+ workaround_transport_delivery_problems/2, alert_user/5, alert_user/8
+ ]).
+
+%% Data handling
+-export([write_application_data/3, read_application_data/2,
+ passive_receive/2, next_record_if_active/1]).
+
+%% Called by tls_connection_sup
-export([start_link/7]).
%% gen_fsm callbacks
@@ -51,238 +68,69 @@
abbreviated/2, connection/2, handle_event/3,
handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
--record(state, {
- role, % client | server
- user_application, % {MonitorRef, pid()}
- transport_cb, % atom() - callback module
- data_tag, % atom() - ex tcp.
- close_tag, % atom() - ex tcp_closed
- error_tag, % atom() - ex tcp_error
- host, % string() | ipadress()
- port, % integer()
- socket, % socket()
- ssl_options, % #ssl_options{}
- socket_options, % #socket_options{}
- connection_states, % #connection_states{} from ssl_record.hrl
- tls_packets = [], % Not yet handled decode ssl/tls packets.
- tls_record_buffer, % binary() buffer of incomplete records
- tls_handshake_buffer, % binary() buffer of incomplete handshakes
- tls_handshake_history, % tls_handshake_history()
- tls_cipher_texts, % list() received but not deciphered yet
- cert_db, %
- session, % #session{} from tls_handshake.hrl
- session_cache, %
- session_cache_cb, %
- negotiated_version, % tls_version()
- client_certificate_requested = false,
- key_algorithm, % atom as defined by cipher_suite
- hashsign_algorithm = {undefined, undefined},
- cert_hashsign_algorithm,
- public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams}
- private_key, % PKIX: #'RSAPrivateKey'{}
- diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side
- diffie_hellman_keys, % {PublicKey, PrivateKey}
- psk_identity, % binary() - server psk identity hint
- srp_params, % #srp_user{}
- srp_keys, % {PublicKey, PrivateKey}
- premaster_secret, %
- file_ref_db, % ets()
- cert_db_ref, % ref()
- bytes_to_read, % integer(), # bytes to read in passive mode
- user_data_buffer, % binary()
- renegotiation, % {boolean(), From | internal | peer}
- start_or_recv_from, % "gen_fsm From"
- timer, % start_or_recv_timer
- send_queue, % queue()
- terminated = false, %
- allow_renegotiate = true,
- expecting_next_protocol_negotiation = false :: boolean(),
- next_protocol = undefined :: undefined | binary(),
- client_ecc % {Curves, PointFmt}
- }).
-
--define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
- #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME,
- base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}).
--define(WAIT_TO_ALLOW_RENEGOTIATION, 12000).
-
-
%%====================================================================
%% Internal application API
%%====================================================================
-
-%%--------------------------------------------------------------------
--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 connect(host(), inet:port_number(), port(), {#ssl_options{}, #socket_options{}},
- pid(), tuple(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Connect to an ssl server.
-%%--------------------------------------------------------------------
-connect(Host, Port, Socket, Options, User, CbInfo, Timeout) ->
- try start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
- Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssl_not_started}
- end.
-%%--------------------------------------------------------------------
--spec ssl_accept(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(Port, Socket, Opts, User, CbInfo, Timeout) ->
- try start_fsm(server, "localhost", Port, Socket, Opts, User,
- CbInfo, Timeout)
+start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts,
+ User, {CbModule, _,_, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule),
+ ok = ssl_connection:handshake(SslSocket, Timeout),
+ {ok, SslSocket}
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:{badmatch, {error, _} = Error} ->
Error
- end.
-%--------------------------------------------------------------------
--spec socket_control(port(), pid(), atom()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Set the ssl process to own the accept socket
-%%--------------------------------------------------------------------
-socket_control(Socket, Pid, Transport) ->
- case Transport:controlling_process(Socket, Pid) of
- ok ->
- {ok, ssl_socket:socket(Pid, Transport, Socket)};
- {error, Reason} ->
- {error, Reason}
- end.
+ end;
-%%--------------------------------------------------------------------
--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
+start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts,
+ User, {CbModule, _,_, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule),
+ ok = ssl_connection:handshake(SslSocket, Timeout),
+ {ok, SslSocket}
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
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).
+send_handshake(Handshake, #state{negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport,
+ tls_handshake_history = Hist0,
+ connection_states = ConnectionStates0} = State0) ->
+ {BinHandshake, ConnectionStates, Hist} =
+ encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
+ Transport:send(Socket, BinHandshake),
+ State0#state{connection_states = ConnectionStates,
+ tls_handshake_history = Hist
+ }.
-%%--------------------------------------------------------------------
--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).
+send_alert(Alert, #state{negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0} = State0) ->
+ {BinMsg, ConnectionStates} =
+ ssl_alert:encode(Alert, Version, ConnectionStates0),
+ Transport:send(Socket, BinMsg),
+ State0#state{connection_states = ConnectionStates}.
-%%--------------------------------------------------------------------
--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}).
+send_change_cipher(Msg, #state{connection_states = ConnectionStates0,
+ socket = Socket,
+ negotiated_version = Version,
+ transport_cb = Transport} = State0) ->
+ {BinChangeCipher, ConnectionStates} =
+ encode_change_cipher(Msg, Version, ConnectionStates0),
+ Transport:send(Socket, BinChangeCipher),
+ State0#state{connection_states = ConnectionStates}.
%%====================================================================
-%% ssl_connection_sup API
+%% tls_connection_sup API
%%====================================================================
%%--------------------------------------------------------------------
@@ -297,10 +145,11 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
{ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) ->
+ process_flag(trap_exit, true),
State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
- Handshake = tls_handshake:init_handshake_history(),
+ Handshake = ssl_handshake:init_handshake_history(),
TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
- try ssl_init(SSLOpts0, Role) of
+ try ssl_config:init(SSLOpts0, Role) of
{ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, Key, DHParams} ->
Session = State0#state.session,
State = State0#state{
@@ -326,23 +175,18 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) ->
%% same name as the current state name StateName is called to handle
%% the event. It is also called if a timeout occurs.
%%
-
-%%--------------------------------------------------------------------
--spec hello(start | #hello_request{} | #client_hello{} | #server_hello{} | term(),
- #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
hello(start, #state{host = Host, port = Port, role = client,
- ssl_options = SslOpts,
- session = #session{own_certificate = Cert} = Session0,
- session_cache = Cache, session_cache_cb = CacheCb,
- transport_cb = Transport, socket = Socket,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}} = State0) ->
+ ssl_options = SslOpts,
+ session = #session{own_certificate = Cert} = Session0,
+ session_cache = Cache, session_cache_cb = CacheCb,
+ transport_cb = Transport, socket = Socket,
+ connection_states = ConnectionStates0,
+ renegotiation = {Renegotiation, _}} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
- Handshake0 = tls_handshake:init_handshake_history(),
+ Handshake0 = ssl_handshake:init_handshake_history(),
{BinMsg, ConnectionStates, Handshake} =
encode_handshake(Hello, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinMsg),
@@ -354,56 +198,10 @@ hello(start, #state{host = Host, port = Port, role = client,
{Record, State} = next_record(State1),
next_state(hello, hello, Record, State);
-hello(start, #state{role = server} = State0) ->
- {Record, State} = next_record(State0),
- next_state(hello, hello, Record, State);
-
-hello(#hello_request{}, #state{role = client} = State0) ->
- {Record, State} = next_record(State0),
- next_state(hello, hello, Record, State);
-
-hello(#server_hello{cipher_suite = CipherSuite,
- compression_method = Compression} = Hello,
- #state{session = #session{session_id = OldId},
- connection_states = ConnectionStates0,
- role = client,
- negotiated_version = ReqVersion,
- renegotiation = {Renegotiation, _},
- ssl_options = SslOptions} = State0) ->
- case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- handle_own_alert(Alert, ReqVersion, hello, State0);
- {Version, NewId, ConnectionStates, NextProtocol} ->
- {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
- end;
-
hello(Hello = #client_hello{client_version = ClientVersion,
- extensions = #hello_extensions{hash_signs = HashSigns}},
+ extensions = #hello_extensions{hash_signs = HashSigns,
+ ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves}},
State = #state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
renegotiation = {Renegotiation, _},
@@ -412,434 +210,58 @@ hello(Hello = #client_hello{client_version = ClientVersion,
ssl_options = SslOpts}) ->
HashSign = ssl_handshake:select_hashsign(HashSigns, Cert),
case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
- ConnectionStates0, Cert}, Renegotiation) of
- {Version, {Type, #session{cipher_suite = CipherSuite} = Session},
- ConnectionStates,
- #hello_extensions{ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves} = ServerHelloExt} ->
- {KeyAlg, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
- NegotiatedHashSign = negotiated_hashsign(HashSign, KeyAlg, Version),
- do_server_hello(Type, ServerHelloExt,
- State#state{connection_states = ConnectionStates,
- negotiated_version = Version,
- session = Session,
- hashsign_algorithm = NegotiatedHashSign,
- client_ecc = {EllipticCurves, EcPointFormats}});
+ ConnectionStates0, Cert}, Renegotiation) of
+ {Version, {Type, Session},
+ ConnectionStates, ServerHelloExt} ->
+ ssl_connection:hello({common_client_hello, Type, ServerHelloExt, HashSign},
+ State#state{connection_states = ConnectionStates,
+ negotiated_version = Version,
+ session = Session,
+ client_ecc = {EllipticCurves, EcPointFormats}}, ?MODULE);
#alert{} = Alert ->
handle_own_alert(Alert, ClientVersion, hello, State)
end;
-
-hello(timeout, State) ->
- { next_state, hello, State, hibernate };
-
-hello(Msg, State) ->
- handle_unexpected_message(Msg, hello, State).
-%%--------------------------------------------------------------------
--spec abbreviated(#hello_request{} | #finished{} | term(),
- #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
-abbreviated(#hello_request{}, State0) ->
- {Record, State} = next_record(State0),
- 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) ->
- case ssl_handshake:verify_connection(Version, Finished, client,
- get_current_connection_state_prf(ConnectionStates0, write),
- MasterSecret, Handshake) of
- verified ->
- ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
- next_state_connection(abbreviated,
- ack_connection(State#state{connection_states = ConnectionStates}));
+hello(Hello,
+ #state{connection_states = ConnectionStates0,
+ negotiated_version = ReqVersion,
+ role = client,
+ renegotiation = {Renegotiation, _},
+ ssl_options = SslOptions} = State) ->
+ case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
- 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} = State) ->
- case ssl_handshake:verify_connection(Version, Finished, server,
- get_pending_connection_state_prf(ConnectionStates0, write),
- MasterSecret, Handshake0) of
- verified ->
- ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
- {ConnectionStates, Handshake} =
- finalize_handshake(State#state{connection_states = ConnectionStates1}, abbreviated),
- next_state_connection(abbreviated,
- ack_connection(State#state{tls_handshake_history = Handshake,
- connection_states =
- ConnectionStates}));
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, abbreviated, State)
+ handle_own_alert(Alert, ReqVersion, hello, State);
+ {Version, NewId, ConnectionStates, NextProtocol} ->
+ ssl_connection:handle_session(Hello,
+ Version, NewId, ConnectionStates, NextProtocol, State)
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) ->
- {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}),
- next_state(abbreviated, abbreviated, Record, State);
-
-abbreviated(timeout, State) ->
- { next_state, abbreviated, State, hibernate };
+hello(Msg, State) ->
+ ssl_connection:hello(Msg, State, ?MODULE).
abbreviated(Msg, State) ->
- handle_unexpected_message(Msg, abbreviated, State).
-
-%%--------------------------------------------------------------------
--spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} |
- #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
- #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
-certify(#hello_request{}, State0) ->
- {Record, State} = next_record(State0),
- 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) ->
- Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
- 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) ->
- {Record, State} = next_record(State0#state{client_certificate_requested = false}),
- 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) ->
- 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});
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State)
- end;
-
-certify(#server_key_exchange{} = KeyExchangeMsg,
- #state{role = client, negotiated_version = Version,
- key_algorithm = Alg} = State0)
- 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 ->
- case handle_server_key(KeyExchangeMsg, State0) of
- #state{} = State1 ->
- {Record, State} = next_record(State1),
- next_state(certify, certify, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify(#server_key_exchange{} = Msg,
- #state{role = client, key_algorithm = rsa} = State) ->
- handle_unexpected_message(Msg, certify_server_keyexchange, State);
-
-certify(#certificate_request{hashsign_algorithms = HashSigns},
- #state{session = #session{own_certificate = Cert}} = State0) ->
- HashSign = ssl_handshake:select_hashsign(HashSigns, Cert),
- {Record, State} = next_record(State0#state{client_certificate_requested = true}),
- 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,
- premaster_secret = undefined,
- role = client,
- key_algorithm = Alg} = State0)
- when Alg == psk ->
- case server_psk_master_secret(PSKIdentity, State0) of
- #state{} = State ->
- client_certify_and_key_exchange(State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify(#server_hello_done{},
- #state{session = #session{master_secret = undefined},
- ssl_options = SslOpts,
- negotiated_version = Version,
- psk_identity = PSKIdentity,
- premaster_secret = undefined,
- role = client,
- key_algorithm = Alg} = State0)
- when Alg == rsa_psk ->
- case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of
- {ok, PSK} when is_binary(PSK) ->
- PremasterSecret = make_premaster_secret(Version, rsa),
- Len = byte_size(PSK),
- RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>,
- State1 = State0#state{premaster_secret = PremasterSecret},
- State = master_from_premaster_secret(RealPMS, State1),
- client_certify_and_key_exchange(State);
- #alert{} = Alert ->
- Alert;
- _ ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
- 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) ->
- case ssl_handshake:master_secret(tls_record, Version, Session,
- ConnectionStates0, client) of
- {MasterSecret, ConnectionStates} ->
- State = State0#state{connection_states = ConnectionStates},
- client_certify_and_key_exchange(State);
- #alert{} = Alert ->
- 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) ->
- case ssl_handshake:master_secret(tls_record, 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);
- #alert{} = Alert ->
- 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) ->
- %% We expect a certificate here
- handle_unexpected_message(Msg, certify_client_key_exchange, State);
-
-certify(#client_key_exchange{exchange_keys = Keys},
- State = #state{key_algorithm = KeyAlg, negotiated_version = Version}) ->
- try
- certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), State)
- catch
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State)
- end;
-
-
-certify(timeout, State) ->
- { next_state, certify, State, hibernate };
+ ssl_connection:abbreviated(Msg, State, ?MODULE).
certify(Msg, State) ->
- handle_unexpected_message(Msg, certify, State).
-
-certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
- #state{negotiated_version = Version,
- connection_states = ConnectionStates0,
- session = Session0,
- private_key = Key} = State0) ->
- PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key),
- case ssl_handshake:master_secret(tls_record, Version, PremasterSecret,
- ConnectionStates0, server) of
- {MasterSecret, ConnectionStates} ->
- Session = Session0#session{master_secret = MasterSecret},
- State1 = State0#state{connection_states = ConnectionStates,
- session = Session},
- {Record, State} = next_record(State1),
- next_state(certify, cipher, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
- #state{negotiated_version = Version,
- diffie_hellman_params = #'DHParameter'{} = Params,
- diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) ->
- case dh_master_secret(Params, ClientPublicDhKey, ServerDhPrivateKey, State0) of
- #state{} = State1 ->
- {Record, State} = next_record(State1),
- next_state(certify, cipher, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
- #state{negotiated_version = Version,
- diffie_hellman_keys = ECDHKey} = State0) ->
- case ec_dh_master_secret(ECDHKey, #'ECPoint'{point = ClientPublicEcDhPoint}, State0) of
- #state{} = State1 ->
- {Record, State} = next_record(State1),
- next_state(certify, cipher, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify_client_key_exchange(#client_psk_identity{identity = ClientPSKIdentity},
- #state{negotiated_version = Version} = State0) ->
- case server_psk_master_secret(ClientPSKIdentity, State0) of
- #state{} = State1 ->
- {Record, State} = next_record(State1),
- next_state(certify, cipher, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify_client_key_exchange(#client_dhe_psk_identity{
- identity = ClientPSKIdentity,
- dh_public = ClientPublicDhKey},
- #state{negotiated_version = Version,
- diffie_hellman_params = #'DHParameter'{prime = P,
- base = G},
- diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) ->
- case dhe_psk_master_secret(ClientPSKIdentity, P, G, ClientPublicDhKey, ServerDhPrivateKey, State0) of
- #state{} = State1 ->
- {Record, State} = next_record(State1),
- next_state(certify, cipher, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify_client_key_exchange(#client_rsa_psk_identity{
- identity = PskIdentity,
- exchange_keys =
- #encrypted_premaster_secret{premaster_secret= EncPMS}},
- #state{negotiated_version = Version,
- private_key = Key} = State0) ->
- PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key),
- case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of
- #state{} = State1 ->
- {Record, State} = next_record(State1),
- next_state(certify, cipher, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end;
-
-certify_client_key_exchange(#client_srp_public{srp_a = ClientPublicKey},
- #state{negotiated_version = Version,
- srp_params =
- #srp_user{prime = Prime,
- verifier = Verifier}
- } = State0) ->
- case server_srp_master_secret(Verifier, Prime, ClientPublicKey, State0) of
- #state{} = State1 ->
- {Record, State} = next_record(State1),
- next_state(certify, cipher, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end.
-
-%%--------------------------------------------------------------------
--spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(),
- #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
-cipher(#hello_request{}, State0) ->
- {Record, State} = next_record(State0),
- 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) ->
-
- HashSign = ssl_handshake:select_cert_hashsign(CertHashSign, Algo, Version),
- case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
- Version, HashSign, MasterSecret, Handshake) of
- valid ->
- {Record, State} = next_record(State0),
- next_state(cipher, cipher, Record, State#state{cert_hashsign_algorithm = HashSign});
- #alert{} = Alert ->
- 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) ->
- 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) ->
- case ssl_handshake:verify_connection(Version, Finished,
- opposite_role(Role),
- get_current_connection_state_prf(ConnectionStates0, read),
- MasterSecret, Handshake0) of
- verified ->
- Session = register_session(Role, Host, Port, Session0),
- cipher_role(Role, Data, Session, State);
- #alert{} = Alert ->
- 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) ->
- {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}),
- next_state(cipher, cipher, Record, State);
-
-cipher(timeout, State) ->
- { next_state, cipher, State, hibernate };
+ ssl_connection:certify(Msg, State, ?MODULE).
cipher(Msg, State) ->
- handle_unexpected_message(Msg, cipher, State).
+ ssl_connection:cipher(Msg, State, ?MODULE).
-%%--------------------------------------------------------------------
--spec connection(#hello_request{} | #client_hello{} | term(),
- #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
connection(#hello_request{}, #state{host = Host, port = Port,
- socket = Socket,
session = #session{own_certificate = Cert} = Session0,
session_cache = Cache, session_cache_cb = CacheCb,
ssl_options = SslOpts,
- negotiated_version = Version,
- transport_cb = Transport,
connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- tls_handshake_history = Handshake0} = State0) ->
+ renegotiation = {Renegotiation, _}} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
-
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Hello, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- {Record, State} = next_record(State0#state{connection_states =
- ConnectionStates,
- session = Session0#session{session_id = Hello#client_hello.session_id},
- tls_handshake_history = Handshake}),
+ State1 = send_handshake(Hello, State0),
+ {Record, State} =
+ next_record(
+ State1#state{session = Session0#session{session_id
+ = Hello#client_hello.session_id}}),
next_state(connection, hello, Record, State);
+
connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
@@ -849,21 +271,13 @@ connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = tr
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
hello(Hello, State#state{allow_renegotiate = false});
-connection(#client_hello{}, #state{role = server, allow_renegotiate = false,
- connection_states = ConnectionStates0,
- socket = Socket, transport_cb = Transport,
- negotiated_version = Version} = State0) ->
+connection(#client_hello{}, #state{role = server, allow_renegotiate = false} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
- {BinMsg, ConnectionStates} =
- encode_alert(Alert, Version, ConnectionStates0),
- Transport:send(Socket, BinMsg),
- next_state_connection(connection, State0#state{connection_states = ConnectionStates});
+ State = send_alert(Alert, State0),
+ next_state_connection(connection, State);
-connection(timeout, State) ->
- {next_state, connection, State, hibernate};
-
connection(Msg, State) ->
- handle_unexpected_message(Msg, connection, State).
+ ssl_connection:connection(Msg, State, tls_connection).
%%--------------------------------------------------------------------
%% Description: Whenever a gen_fsm receives an event sent using
@@ -878,195 +292,8 @@ handle_event(_Event, StateName, State) ->
%% 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) ->
- %% 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
- 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) ->
- Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
- 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) ->
- %% Run terminate before returning
- %% so that the reuseaddr inet-option will work
- %% as intended.
- (catch 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, _} =
- encode_alert(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, State0) ->
- Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
- 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,
- 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} = next_record_if_active(State1),
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case 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 read_application_data(<<>>, State1) of
- Stop = {stop,_,_} ->
- Stop;
- {Record, State2} ->
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case 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) ->
- 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_sync_event(Event, From, StateName, State) ->
+ ssl_connection:handle_sync_event(Event, From, StateName, State).
%%--------------------------------------------------------------------
%% Description: This function is called by a gen_fsm when it receives any
@@ -1074,7 +301,7 @@ handle_sync_event(peer_certificate, _, StateName,
%% (or a system message).
%%--------------------------------------------------------------------
-%% raw data from TCP, unpack records
+%% raw data from socket, unpack records
handle_info({Protocol, _, Data}, StateName,
#state{data_tag = Protocol} = State0) ->
case next_tls_record(Data, State0) of
@@ -1105,45 +332,8 @@ handle_info({CloseTag, Socket}, StateName,
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
{stop, {shutdown, transport_closed}, State};
-handle_info({ErrorTag, Socket, econnaborted}, StateName,
- #state{socket = Socket, transport_cb = Transport,
- start_or_recv_from = StartFrom, role = Role,
- error_tag = ErrorTag} = State) when StateName =/= connection ->
- alert_user(Transport, Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role),
- {stop, normal, State};
-
-handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
- error_tag = ErrorTag} = State) ->
- Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
- error_logger:info_report(Report),
- 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};
-
-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) ->
- Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [Msg]),
- error_logger:info_report(Report),
- {next_state, StateName, State, get_timeout(State)}.
+ ssl_connection:handle_info(Msg, StateName, State).
%%--------------------------------------------------------------------
%% Description:This function is called by a gen_fsm when it is about
@@ -1151,44 +341,9 @@ handle_info(Msg, StateName, State) ->
%% necessary cleaning up. When it returns, the gen_fsm terminates with
%% Reason. The return value is ignored.
%%--------------------------------------------------------------------
-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(Reason, StateName, State) ->
+ ssl_connection:terminate(Reason, StateName, State).
-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,
- 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),
- workaround_transport_delivery_problems(Socket, Transport);
-
-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).
%%--------------------------------------------------------------------
%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
@@ -1200,1093 +355,206 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts,
- User, {CbModule, _,_, _} = CbInfo,
- Timeout) ->
- try
- {ok, Pid} = ssl_connection_sup:start_child([Role, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = socket_control(Socket, Pid, CbModule),
- ok = handshake(SslSocket, Timeout),
- {ok, SslSocket}
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end;
-
-start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts,
- User, {CbModule, _,_, _} = CbInfo,
- Timeout) ->
- try
- {ok, Pid} = ssl_connection_sup:start_child_dist([Role, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = socket_control(Socket, Pid, CbModule),
- ok = handshake(SslSocket, Timeout),
- {ok, SslSocket}
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end.
-
-ssl_init(SslOpts, Role) ->
-
- init_manager_name(SslOpts#ssl_options.erl_dist),
-
- {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} = init_certificates(SslOpts, Role),
- PrivateKey =
- init_private_key(PemCacheHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile,
- SslOpts#ssl_options.password, Role),
- DHParams = init_diffie_hellman(PemCacheHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role),
- {ok, CertDbRef, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, PrivateKey, DHParams}.
-
-init_manager_name(false) ->
- put(ssl_manager, ssl_manager:manager_name(normal));
-init_manager_name(true) ->
- put(ssl_manager, ssl_manager:manager_name(dist)).
-
-init_certificates(#ssl_options{cacerts = CaCerts,
- cacertfile = CACertFile,
- certfile = CertFile,
- cert = Cert}, Role) ->
- {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle} =
- try
- Certs = case CaCerts of
- undefined ->
- CACertFile;
- _ ->
- {der, CaCerts}
- end,
- {ok, _, _, _, _, _} = ssl_manager:connection_init(Certs, Role)
- catch
- _:Reason ->
- file_error(CACertFile, {cacertfile, Reason})
- end,
- init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, Role).
-
-init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, <<>>, _) ->
- {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined};
-
-init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, client) ->
- try
- %% Ignoring potential proxy-certificates see:
- %% http://dev.globus.org/wiki/Security/ProxyFileFormat
- [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle),
- {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert}
- catch _Error:_Reason ->
- {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined}
- end;
-
-init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CertFile, server) ->
- try
- [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle),
- {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, OwnCert}
- catch
- _:Reason ->
- file_error(CertFile, {certfile, Reason})
- end;
-init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, _, _) ->
- {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, Cert}.
-
-init_private_key(_, undefined, <<>>, _Password, _Client) ->
- undefined;
-init_private_key(DbHandle, undefined, KeyFile, Password, _) ->
- try
- {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
- [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List,
- PKey =:= 'RSAPrivateKey' orelse
- PKey =:= 'DSAPrivateKey' orelse
- PKey =:= 'ECPrivateKey' orelse
- PKey =:= 'PrivateKeyInfo'
- ],
- private_key(public_key:pem_entry_decode(PemEntry, Password))
- catch
- _:Reason ->
- file_error(KeyFile, {keyfile, Reason})
- end;
-
-%% First two clauses are for backwards compatibility
-init_private_key(_,{rsa, PrivateKey}, _, _,_) ->
- init_private_key('RSAPrivateKey', PrivateKey);
-init_private_key(_,{dsa, PrivateKey},_,_,_) ->
- init_private_key('DSAPrivateKey', PrivateKey);
-init_private_key(_,{ec, PrivateKey},_,_,_) ->
- init_private_key('ECPrivateKey', PrivateKey);
-init_private_key(_,{Asn1Type, PrivateKey},_,_,_) ->
- private_key(init_private_key(Asn1Type, PrivateKey)).
-
-init_private_key(Asn1Type, PrivateKey) ->
- public_key:der_decode(Asn1Type, PrivateKey).
-
-private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
- #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'},
- privateKey = Key}) ->
- public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key));
-
-private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
- #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'},
- privateKey = Key}) ->
- public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key));
-
-private_key(Key) ->
- Key.
-
--spec(file_error(_,_) -> no_return()).
-file_error(File, Throw) ->
- case Throw of
- {Opt,{badmatch, {error, {badmatch, Error}}}} ->
- throw({options, {Opt, binary_to_list(File), Error}});
- _ ->
- throw(Throw)
- end.
-
-init_diffie_hellman(_,Params, _,_) when is_binary(Params)->
- public_key:der_decode('DHParameter', Params);
-init_diffie_hellman(_,_,_, client) ->
- undefined;
-init_diffie_hellman(_,_,undefined, _) ->
- ?DEFAULT_DIFFIE_HELLMAN_PARAMS;
-init_diffie_hellman(DbHandle,_, DHParamFile, server) ->
- try
- {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle),
- case [Entry || Entry = {'DHParameter', _ , _} <- List] of
- [Entry] ->
- public_key:pem_entry_decode(Entry);
- [] ->
- ?DEFAULT_DIFFIE_HELLMAN_PARAMS
- end
- catch
- _:Reason ->
- file_error(DHParamFile, {dhfile, Reason})
- end.
-
-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.
-
-handle_peer_cert(Role, PeerCert, PublicKeyInfo,
- #state{session = #session{cipher_suite = CipherSuite} = Session} = State0) ->
- 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} = next_record(State2),
- 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),
- ec_dh_master_secret(ECDHKey, PublicKey, 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,
- connection_states = ConnectionStates0,
- transport_cb = Transport,
- negotiated_version = Version,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- session = #session{own_certificate = OwnCert},
- socket = Socket,
- tls_handshake_history = Handshake0} = State) ->
- Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
- {BinCert, ConnectionStates, Handshake} =
- encode_handshake(Certificate, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinCert),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
-certify_client(#state{client_certificate_requested = false} = State) ->
- State.
-
-verify_client_cert(#state{client_certificate_requested = true, role = client,
- connection_states = ConnectionStates0,
- transport_cb = Transport,
- negotiated_version = Version,
- socket = Socket,
- private_key = PrivateKey,
- session = #session{master_secret = MasterSecret,
- own_certificate = OwnCert},
- cert_hashsign_algorithm = HashSign,
- tls_handshake_history = Handshake0} = State) ->
-
- case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
- Version, HashSign, PrivateKey, Handshake0) of
- #certificate_verify{} = Verified ->
- {BinVerified, ConnectionStates, Handshake} =
- encode_handshake(Verified, Version,
- ConnectionStates0, Handshake0),
- Transport:send(Socket, BinVerified),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
- ignore ->
- State;
- #alert{} = Alert ->
- throw(Alert)
- end;
-verify_client_cert(#state{client_certificate_requested = false} = State) ->
- State.
-
-do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt,
- #state{negotiated_version = Version,
- session = #session{session_id = SessId},
- connection_states = ConnectionStates0}
- = State0) when is_atom(Type) ->
-
- ServerHello =
- tls_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt),
- State = server_hello(ServerHello,
- State0#state{expecting_next_protocol_negotiation =
- NextProtocols =/= undefined}),
- case Type of
- new ->
- new_server_hello(ServerHello, State);
- resumed ->
- resumed_server_hello(State)
- end.
+encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
+ Frag = tls_handshake:encode_handshake(Handshake, Version),
+ Hist = ssl_handshake:update_handshake_history(Hist0, Frag),
+ {Encoded, ConnectionStates} =
+ ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
+ {Encoded, ConnectionStates, Hist}.
-new_server_hello(#server_hello{cipher_suite = CipherSuite,
- compression_method = Compression,
- session_id = SessionId},
- #state{session = Session0,
- negotiated_version = Version} = State0) ->
- try server_certify_and_key_exchange(State0) of
- #state{} = State1 ->
- State2 = server_hello_done(State1),
- Session =
- Session0#session{session_id = SessionId,
- cipher_suite = CipherSuite,
- compression_method = Compression},
- {Record, State} = next_record(State2#state{session = Session}),
- next_state(hello, certify, Record, State)
- catch
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
- end.
-resumed_server_hello(#state{session = Session,
- connection_states = ConnectionStates0,
- negotiated_version = Version} = State0) ->
-
- case ssl_handshake:master_secret(tls_record, Version, Session,
- ConnectionStates0, server) of
- {_, ConnectionStates1} ->
- State1 = State0#state{connection_states = ConnectionStates1,
- session = Session},
- {ConnectionStates, Handshake} =
- finalize_handshake(State1, abbreviated),
- State2 = State1#state{connection_states =
- ConnectionStates,
- tls_handshake_history = Handshake},
- {Record, State} = next_record(State2),
- next_state(hello, abbreviated, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
- end.
+encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
+ ssl_record:encode_change_cipher_spec(Version, ConnectionStates).
-handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0} = State0) ->
- Session = Session0#session{session_id = NewId,
- cipher_suite = CipherSuite,
- compression_method = Compression},
- {Record, State} = next_record(State0#state{session = Session}),
- next_state(hello, certify, Record, State).
-
-handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
- negotiated_version = Version,
- host = Host, port = Port,
- 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} =
- next_record(State0#state{
- connection_states = ConnectionStates,
- session = Session}),
- next_state(hello, abbreviated, Record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
- end.
-client_certify_and_key_exchange(#state{negotiated_version = Version} =
- State0) ->
- try do_client_certify_and_key_exchange(State0) of
- State1 = #state{} ->
- {ConnectionStates, Handshake} = finalize_handshake(State1, certify),
- State2 = State1#state{connection_states = ConnectionStates,
- %% Reinitialize
- client_certificate_requested = false,
- tls_handshake_history = Handshake},
- {Record, State} = next_record(State2),
- next_state(certify, cipher, Record, State)
- catch
- throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end.
+decode_alerts(Bin) ->
+ decode_alerts(Bin, []).
-do_client_certify_and_key_exchange(State0) ->
- State1 = certify_client(State0),
- State2 = key_exchange(State1),
- verify_client_cert(State2).
+decode_alerts(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc) ->
+ A = ?ALERT_REC(Level, Description),
+ decode_alerts(Rest, [A | Acc]);
+decode_alerts(<<>>, Acc) ->
+ lists:reverse(Acc, []).
-server_certify_and_key_exchange(State0) ->
- State1 = certify_server(State0),
- State2 = key_exchange(State1),
- request_client_cert(State2).
-
-server_hello(ServerHello, #state{transport_cb = Transport,
- socket = Socket,
- negotiated_version = Version,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State) ->
- CipherSuite = ServerHello#server_hello.cipher_suite,
- {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
- {BinMsg, ConnectionStates1, Handshake1} =
- encode_handshake(ServerHello, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates1,
- tls_handshake_history = Handshake1,
- key_algorithm = KeyAlgorithm}.
-
-server_hello_done(#state{transport_cb = Transport,
- socket = Socket,
- negotiated_version = Version,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State) ->
+initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
+ {CbModule, DataTag, CloseTag, ErrorTag}) ->
+ ConnectionStates = ssl_record:init_connection_states(Role),
- HelloDone = ssl_handshake:server_hello_done(),
+ SessionCacheCb = case application:get_env(ssl, session_cb) of
+ {ok, Cb} when is_atom(Cb) ->
+ Cb;
+ _ ->
+ ssl_session_cache
+ end,
- {BinHelloDone, ConnectionStates, Handshake} =
- encode_handshake(HelloDone, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinHelloDone),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake}.
-
-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{transport_cb = Transport,
- socket = Socket,
- negotiated_version = Version,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- session = #session{own_certificate = OwnCert}} = State) ->
- case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
- CertMsg = #certificate{} ->
- {BinCertMsg, ConnectionStates, Handshake} =
- encode_handshake(CertMsg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinCertMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake
- };
- 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,
- tls_handshake_history = Handshake0,
- socket = Socket,
- transport_cb = Transport
- } = State)
- 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}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- diffie_hellman_keys = DHKeys,
- tls_handshake_history = Handshake};
-
-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,
- tls_handshake_history = Handshake0,
- socket = Socket,
- transport_cb = Transport
- } = State)
- when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa;
- Algo == ecdh_anon ->
-
- ECDHKeys = public_key:generate_key(select_curve(State)),
- 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}),
- {BinMsg, ConnectionStates, Handshake1} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- diffie_hellman_keys = ECDHKeys,
- tls_handshake_history = Handshake1};
-
-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,
- tls_handshake_history = Handshake0,
- socket = Socket,
- transport_cb = Transport
- } = State) ->
- 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}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
-
-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,
- tls_handshake_history = Handshake0,
- socket = Socket,
- transport_cb = Transport
- } = State) ->
- 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}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- diffie_hellman_keys = DHKeys,
- tls_handshake_history = Handshake};
-
-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,
- tls_handshake_history = Handshake0,
- socket = Socket,
- transport_cb = Transport
- } = State) ->
- 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}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
-
-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,
- tls_handshake_history = Handshake0,
- socket = Socket,
- transport_cb = Transport
- } = State)
- 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}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- srp_params = SrpParams,
- srp_keys = Keys,
- tls_handshake_history = Handshake};
-
-key_exchange(#state{role = client,
- connection_states = ConnectionStates0,
- key_algorithm = rsa,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- premaster_secret = PremasterSecret,
- socket = Socket, transport_cb = Transport,
- tls_handshake_history = Handshake0} = State) ->
- Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
-key_exchange(#state{role = client,
- connection_states = ConnectionStates0,
- key_algorithm = Algorithm,
- negotiated_version = Version,
- diffie_hellman_keys = {DhPubKey, _},
- socket = Socket, transport_cb = Transport,
- tls_handshake_history = Handshake0} = State)
- when Algorithm == dhe_dss;
- Algorithm == dhe_rsa;
- Algorithm == dh_anon ->
- Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
-
-key_exchange(#state{role = client,
- connection_states = ConnectionStates0,
- key_algorithm = Algorithm,
- negotiated_version = Version,
- diffie_hellman_keys = Keys,
- socket = Socket, transport_cb = Transport,
- tls_handshake_history = Handshake0} = State)
- 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}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
-
-key_exchange(#state{role = client,
- ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- key_algorithm = psk,
- negotiated_version = Version,
- socket = Socket, transport_cb = Transport,
- tls_handshake_history = Handshake0} = State) ->
- Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
+ Monitor = erlang:monitor(process, User),
-key_exchange(#state{role = client,
- ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- key_algorithm = dhe_psk,
- negotiated_version = Version,
- diffie_hellman_keys = {DhPubKey, _},
- socket = Socket, transport_cb = Transport,
- tls_handshake_history = Handshake0} = State) ->
- Msg = ssl_handshake:key_exchange(client, Version, {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
+ #state{socket_options = SocketOptions,
+ %% We do not want to save the password in the state so that
+ %% could be written in the clear into error logs.
+ ssl_options = SSLOptions#ssl_options{password = undefined},
+ session = #session{is_resumable = new},
+ transport_cb = CbModule,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ connection_states = ConnectionStates,
+ protocol_buffers = #protocol_buffers{},
+ user_application = {Monitor, User},
+ user_data_buffer = <<>>,
+ session_cache_cb = SessionCacheCb,
+ renegotiation = {false, first},
+ start_or_recv_from = undefined,
+ send_queue = queue:new(),
+ protocol_cb = ?MODULE
+ }.
-key_exchange(#state{role = client,
- ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- key_algorithm = rsa_psk,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- premaster_secret = PremasterSecret,
- socket = Socket, transport_cb = Transport,
- tls_handshake_history = Handshake0} = State) ->
- Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
+next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) ->
+ handle_own_alert(Alert, Version, Current, State);
-key_exchange(#state{role = client,
- connection_states = ConnectionStates0,
- key_algorithm = Algorithm,
- negotiated_version = Version,
- srp_keys = {ClientPubKey, _},
- socket = Socket, transport_cb = Transport,
- tls_handshake_history = Handshake0} = State)
- when Algorithm == srp_dss;
- Algorithm == srp_rsa;
- Algorithm == srp_anon ->
- Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}),
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake}.
-
-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,
- tls_handshake_history = Handshake0,
- negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport} = State) ->
- #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),
+next_state(_,Next, no_record, State) ->
+ {next_state, Next, State, get_timeout(State)};
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{client_certificate_requested = true,
- connection_states = ConnectionStates,
- tls_handshake_history = Handshake};
-request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
- State) ->
- State.
-
-finalize_handshake(State, StateName) ->
- ConnectionStates0 = cipher_protocol(State),
-
- ConnectionStates =
- ssl_record:activate_pending_connection_state(ConnectionStates0,
- write),
-
- State1 = State#state{connection_states = ConnectionStates},
- State2 = next_protocol(State1),
- finished(State2, StateName).
-
-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{transport_cb = Transport, socket = Socket,
- negotiated_version = Version,
- next_protocol = NextProtocol,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State) ->
- NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
- {BinMsg, ConnectionStates, Handshake} = encode_handshake(NextProtocolMessage, Version, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates,
- tls_handshake_history = Handshake}.
+next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) ->
+ Alerts = decode_alerts(EncAlerts),
+ handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)});
-cipher_protocol(#state{connection_states = ConnectionStates0,
- socket = Socket,
- negotiated_version = Version,
- transport_cb = Transport}) ->
- {BinChangeCipher, ConnectionStates} =
- encode_change_cipher(#change_cipher_spec{},
- Version, ConnectionStates0),
- Transport:send(Socket, BinChangeCipher),
- ConnectionStates.
-
-finished(#state{role = Role, socket = Socket, negotiated_version = Version,
- transport_cb = Transport,
- session = Session,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0}, StateName) ->
- MasterSecret = Session#session.master_secret,
- Finished = ssl_handshake:finished(Version, Role,
- get_current_connection_state_prf(ConnectionStates0, write),
- MasterSecret, Handshake0),
- ConnectionStates1 = save_verify_data(Role, Finished, ConnectionStates0, StateName),
- {BinFinished, ConnectionStates, Handshake} =
- encode_handshake(Finished, Version, ConnectionStates1, Handshake0),
- Transport:send(Socket, BinFinished),
- {ConnectionStates, Handshake}.
-
-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).
-
-handle_server_key(#server_key_exchange{exchange_keys = Keys},
- #state{key_algorithm = KeyAlg,
- negotiated_version = Version} = State) ->
-
- Params = ssl_handshake:decode_server_key(Keys, KeyAlg, Version),
- HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KeyAlg, Version),
- case is_anonymous(KeyAlg) of
- true ->
- server_master_secret(Params#server_key_params.params,
- State#state{hashsign_algorithm = HashSign});
- false ->
- verify_server_key(Params, HashSign, State#state{hashsign_algorithm = HashSign})
- end.
+next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
+ State0 = #state{protocol_buffers =
+ #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
+ negotiated_version = Version}) ->
+ Handle =
+ fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) ->
+ %% This message should not be included in handshake
+ %% message hashes. Starts new handshake (renegotiation)
+ Hs0 = ssl_handshake:init_handshake_history(),
+ ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs0,
+ renegotiation = {true, peer}});
+ ({#hello_request{} = Packet, _}, {next_state, SName, State}) ->
+ %% This message should not be included in handshake
+ %% message hashes. Already in negotiation so it will be ignored!
+ ?MODULE:SName(Packet, State);
+ ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) ->
+ Version = Packet#client_hello.client_version,
+ Hs0 = ssl_handshake:init_handshake_history(),
+ Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw),
+ ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1,
+ renegotiation = {true, peer}});
+ ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_history=Hs0}}) ->
+ Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw),
+ ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1});
+ (_, StopState) -> StopState
+ end,
+ try
+ {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0),
+ State = State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_packets = Packets,
+ tls_handshake_buffer = Buf}},
+ handle_tls_handshake(Handle, Next, State)
+ catch throw:#alert{} = Alert ->
+ handle_own_alert(Alert, Version, Current, State0)
+ end;
-verify_server_key(#server_key_params{params = Params,
- params_bin = EncParams,
- signature = Signature},
- HashSign = {HashAlgo, _},
- #state{negotiated_version = Version,
- public_key_info = PubKeyInfo,
- connection_states = ConnectionStates} = State) ->
- ConnectionState =
- ssl_record:pending_connection_state(ConnectionStates, read),
- SecParams = ConnectionState#connection_state.security_parameters,
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Hash = ssl_handshake:server_key_exchange_hash(HashAlgo,
- <<ClientRandom/binary,
- ServerRandom/binary,
- EncParams/binary>>),
- case ssl_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of
- true ->
- server_master_secret(Params, State);
- false ->
- ?ALERT_REC(?FATAL, ?DECRYPT_ERROR)
- end.
+next_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) ->
+ case read_application_data(Data, State0) of
+ Stop = {stop,_,_} ->
+ Stop;
+ {Record, State} ->
+ next_state(StateName, StateName, Record, State)
+ end;
+next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} =
+ _ChangeCipher,
+ #state{connection_states = ConnectionStates0} = State0) ->
+ ConnectionStates1 =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read),
+ {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}),
+ next_state(Current, Next, Record, State);
+next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) ->
+ %% Ignore unknown type
+ {Record, State} = next_record(State0),
+ next_state(Current, Next, Record, State).
-server_master_secret(#server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey},
- State) ->
- dh_master_secret(P, G, ServerPublicDhKey, undefined, State);
-
-server_master_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
- State) ->
- ECDHKeys = public_key:generate_key(ECCurve),
- ec_dh_master_secret(ECDHKeys, #'ECPoint'{point = ECServerPubKey}, State#state{diffie_hellman_keys = ECDHKeys});
-
-server_master_secret(#server_psk_params{
- hint = IdentityHint},
- State) ->
- %% store for later use
- State#state{psk_identity = IdentityHint};
-
-server_master_secret(#server_dhe_psk_params{
- hint = IdentityHint,
- dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}},
- State) ->
- dhe_psk_master_secret(IdentityHint, P, G, ServerPublicDhKey, undefined, State);
-
-server_master_secret(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B},
- State) ->
- client_srp_master_secret(G, N, S, B, undefined, State).
-
-master_from_premaster_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};
+next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0,
+ tls_cipher_texts = CT0} = Buffers} = State0) ->
+ case tls_record:get_tls_records(Data, Buf0) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ next_record(State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_record_buffer = Buf1,
+ tls_cipher_texts = CT1}});
#alert{} = Alert ->
Alert
end.
-dh_master_secret(#'DHParameter'{} = Params, OtherPublicDhKey, MyPrivateKey, State) ->
- PremasterSecret =
- public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params),
- master_from_premaster_secret(PremasterSecret, State).
-
-dh_master_secret(Prime, Base, PublicDhKey, undefined, State) ->
- Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
- dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State#state{diffie_hellman_keys = Keys});
-
-dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State) ->
- PremasterSecret =
- crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]),
- master_from_premaster_secret(PremasterSecret, State).
-
-ec_dh_master_secret(ECDHKeys, ECPoint, State) ->
- PremasterSecret =
- public_key:compute_key(ECPoint, ECDHKeys),
- master_from_premaster_secret(PremasterSecret, State).
-
-handle_psk_identity(_PSKIdentity, LookupFun)
- when LookupFun == undefined ->
- error;
-handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
- Fun(psk, PSKIdentity, UserState).
-
-server_psk_master_secret(ClientPSKIdentity,
- #state{ssl_options = SslOpts} = State) ->
- case handle_psk_identity(ClientPSKIdentity, SslOpts#ssl_options.user_lookup_fun) of
- {ok, PSK} when is_binary(PSK) ->
- Len = byte_size(PSK),
- PremasterSecret = <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>,
- master_from_premaster_secret(PremasterSecret, State);
- #alert{} = Alert ->
- Alert;
- _ ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
- end.
-
-dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, undefined, State) ->
- Keys = {_, PrivateDhKey} =
- crypto:generate_key(dh, [Prime, Base]),
- dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey,
- State#state{diffie_hellman_keys = Keys});
-
-dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey,
- #state{ssl_options = SslOpts} = State) ->
- case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of
- {ok, PSK} when is_binary(PSK) ->
- DHSecret =
- crypto:compute_key(dh, PublicDhKey, PrivateDhKey,
- [Prime, Base]),
- DHLen = erlang:byte_size(DHSecret),
- Len = erlang:byte_size(PSK),
- PremasterSecret = <<?UINT16(DHLen), DHSecret/binary, ?UINT16(Len), PSK/binary>>,
- master_from_premaster_secret(PremasterSecret, State);
- #alert{} = Alert ->
- Alert;
- _ ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
- end.
-
-server_rsa_psk_master_secret(PskIdentity, PremasterSecret,
- #state{ssl_options = SslOpts} = State) ->
- case handle_psk_identity(PskIdentity, SslOpts#ssl_options.user_lookup_fun) of
- {ok, PSK} when is_binary(PSK) ->
- Len = byte_size(PSK),
- RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>,
- master_from_premaster_secret(RealPMS, State);
- #alert{} = Alert ->
- Alert;
- _ ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
- 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};
+next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []},
+ socket = Socket,
+ transport_cb = Transport} = State) ->
+ ssl_socket:setopts(Transport, Socket, [{active,once}]),
+ {no_record, State};
+next_record(#state{protocol_buffers =
+ #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]}
+ = Buffers,
+ connection_states = ConnStates0} = State) ->
+ case tls_record:decode_cipher_text(CT, ConnStates0) of
+ {Plain, ConnStates} ->
+ {Plain, State#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_cipher_texts = Rest},
+ connection_states = ConnStates}};
#alert{} = Alert ->
- throw(Alert);
- _ ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
- end.
-
-server_srp_master_secret(Verifier, Prime, ClientPub, State = #state{srp_keys = ServerKeys}) ->
- case crypto:compute_key(srp, ClientPub, ServerKeys, {host, [Verifier, Prime, '6a']}) of
- error ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
- PremasterSecret ->
- master_from_premaster_secret(PremasterSecret, State)
- end.
-
-client_srp_master_secret(_Generator, _Prime, _Salt, _ServerPub, #alert{} = Alert, _State) ->
- Alert;
-client_srp_master_secret(Generator, Prime, Salt, ServerPub, undefined, State) ->
- Keys = generate_srp_client_keys(Generator, Prime, 0),
- client_srp_master_secret(Generator, Prime, Salt, ServerPub, Keys, State#state{srp_keys = Keys});
-
-client_srp_master_secret(Generator, Prime, Salt, ServerPub, ClientKeys,
- #state{ssl_options = SslOpts} = State) ->
- case ssl_srp_primes:check_srp_params(Generator, Prime) of
- ok ->
- {Username, Password} = SslOpts#ssl_options.srp_identity,
- DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]),
- case crypto:compute_key(srp, ServerPub, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of
- error ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
- PremasterSecret ->
- master_from_premaster_secret(PremasterSecret, State)
- end;
- _ ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
- end.
-
-cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) ->
- ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0),
- next_state_connection(cipher, ack_connection(State#state{session = Session,
- connection_states = ConnectionStates}));
-
-cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State) ->
- ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0),
- {ConnectionStates, Handshake} =
- finalize_handshake(State#state{connection_states = ConnectionStates1,
- session = Session}, cipher),
- next_state_connection(cipher, ack_connection(State#state{connection_states =
- ConnectionStates,
- session = Session,
- tls_handshake_history =
- Handshake})).
-encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
- ssl_record:encode_alert_record(Alert, Version, ConnectionStates).
-
-encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
- ssl_record:encode_change_cipher_spec(Version, ConnectionStates).
+ {Alert, State}
+ end;
+next_record(State) ->
+ {no_record, State}.
-encode_handshake(HandshakeRec, Version, ConnectionStates0, Handshake0) ->
- Frag = tls_handshake:encode_handshake(HandshakeRec, Version),
- Handshake1 = tls_handshake:update_handshake_history(Handshake0, Frag),
- {E, ConnectionStates1} =
- ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
- {E, ConnectionStates1, Handshake1}.
+next_record_if_active(State =
+ #state{socket_options =
+ #socket_options{active = false}}) ->
+ {no_record ,State};
-encode_packet(Data, #socket_options{packet=Packet}) ->
- case Packet of
- 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1);
- 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1);
- 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1);
- _ -> Data
- end.
+next_record_if_active(State) ->
+ next_record(State).
-encode_size_packet(Bin, Size, Max) ->
- Len = erlang:byte_size(Bin),
- case Len > Max of
- true -> throw({error, {badarg, {packet_to_large, Len, Max}}});
- false -> <<Len:Size, Bin/binary>>
+next_state_connection(StateName, #state{send_queue = Queue0,
+ negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0
+ } = State) ->
+ %% Send queued up data that was queued while renegotiating
+ case queue:out(Queue0) of
+ {{value, {From, Data}}, Queue} ->
+ {Msgs, ConnectionStates} =
+ ssl_record:encode_data(Data, Version, ConnectionStates0),
+ Result = Transport:send(Socket, Msgs),
+ gen_fsm:reply(From, Result),
+ next_state_connection(StateName,
+ State#state{connection_states = ConnectionStates,
+ send_queue = Queue});
+ {empty, Queue0} ->
+ next_state_is_connection(StateName, State)
end.
-decode_alerts(Bin) ->
- decode_alerts(Bin, []).
+%% In next_state_is_connection/1: clear tls_handshake,
+%% premaster_secret and public_key_info (only needed during handshake)
+%% to reduce memory foot print of a connection.
+next_state_is_connection(_, State =
+ #state{start_or_recv_from = RecvFrom,
+ socket_options =
+ #socket_options{active = false}}) when RecvFrom =/= undefined ->
+ passive_receive(State#state{premaster_secret = undefined,
+ public_key_info = undefined,
+ tls_handshake_history = ssl_handshake:init_handshake_history()}, connection);
-decode_alerts(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc) ->
- A = ?ALERT_REC(Level, Description),
- decode_alerts(Rest, [A | Acc]);
-decode_alerts(<<>>, Acc) ->
- lists:reverse(Acc, []).
+next_state_is_connection(StateName, State0) ->
+ {Record, State} = next_record_if_active(State0),
+ next_state(StateName, connection, Record, State#state{premaster_secret = undefined,
+ public_key_info = undefined,
+ tls_handshake_history = ssl_handshake:init_handshake_history()}).
passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
case Buffer of
@@ -2342,38 +610,10 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
{stop, normal, State0}
end.
-write_application_data(Data0, From, #state{socket = Socket,
- negotiated_version = Version,
- transport_cb = Transport,
- connection_states = ConnectionStates0,
- send_queue = SendQueue,
- socket_options = SockOpts,
- ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) ->
- Data = encode_packet(Data0, SockOpts),
-
- case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
- true ->
- renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue),
- renegotiation = {true, internal}});
- false ->
- {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0),
- Result = Transport:send(Socket, Msgs),
- {reply, Result,
- connection, State#state{connection_states = ConnectionStates}, get_timeout(State)}
- end.
-
-time_to_renegotiate(_Data, #connection_states{current_write =
- #connection_state{sequence_number = Num}}, RenegotiateAt) ->
-
- %% We could do test:
- %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt),
- %% but we chose to have a some what lower renegotiateAt and a much cheaper test
- is_time_to_renegotiate(Num, RenegotiateAt).
-
-is_time_to_renegotiate(N, M) when N < M->
- false;
-is_time_to_renegotiate(_,_) ->
- true.
+get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) ->
+ infinity;
+get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) ->
+ HibernateAfter.
%% Picks ClientData
get_data(_, _, <<>>) ->
@@ -2446,7 +686,8 @@ format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet,
{ok, do_format_reply(Mode, Packet, Header, Data)};
format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
header = Header}, Data) ->
- {ssl, ssl_socket:socket(self(), Transport, Socket), do_format_reply(Mode, Packet, Header, Data)}.
+ {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE),
+ do_format_reply(Mode, Packet, Header, Data)}.
deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From) ->
send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data)).
@@ -2454,7 +695,8 @@ deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Da
format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data) ->
{error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data) ->
- {ssl_error, ssl_socket:socket(self(), Transport, Socket), {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
+ {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE),
+ {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
header(N, Data);
@@ -2487,297 +729,99 @@ send_or_reply(false, no_pid, _, _) ->
send_or_reply(_, Pid, _From, Data) ->
send_user(Pid, Data).
-opposite_role(client) ->
- server;
-opposite_role(server) ->
- client.
-
send_user(Pid, Msg) ->
Pid ! Msg.
-handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet]} = State) ->
- FsmReturn = {next_state, StateName, State#state{tls_packets = []}},
+handle_tls_handshake(Handle, StateName,
+ #state{protocol_buffers =
+ #protocol_buffers{tls_packets = [Packet]} = Buffers} = State) ->
+ FsmReturn = {next_state, StateName, State#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_packets = []}}},
Handle(Packet, FsmReturn);
-handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet | Packets]} = State0) ->
- FsmReturn = {next_state, StateName, State0#state{tls_packets = Packets}},
+handle_tls_handshake(Handle, StateName,
+ #state{protocol_buffers =
+ #protocol_buffers{tls_packets = [Packet | Packets]} = Buffers} =
+ State0) ->
+ FsmReturn = {next_state, StateName, State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_packets =
+ Packets}}},
case Handle(Packet, FsmReturn) of
{next_state, NextStateName, State, _Timeout} ->
handle_tls_handshake(Handle, NextStateName, State);
{stop, _,_} = Stop ->
Stop
end.
-
-next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) ->
- handle_own_alert(Alert, Version, Current, State);
-
-next_state(_,Next, no_record, State) ->
- {next_state, Next, State, get_timeout(State)};
-
-next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) ->
- Alerts = decode_alerts(EncAlerts),
- handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)});
-
-next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
- State0 = #state{tls_handshake_buffer = Buf0, negotiated_version = Version}) ->
- Handle =
- fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) ->
- %% This message should not be included in handshake
- %% message hashes. Starts new handshake (renegotiation)
- Hs0 = tls_handshake:init_handshake_history(),
- ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs0,
- renegotiation = {true, peer}});
- ({#hello_request{} = Packet, _}, {next_state, SName, State}) ->
- %% This message should not be included in handshake
- %% message hashes. Already in negotiation so it will be ignored!
- ?MODULE:SName(Packet, State);
- ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) ->
- Version = Packet#client_hello.client_version,
- Hs0 = tls_handshake:init_handshake_history(),
- Hs1 = tls_handshake:update_handshake_history(Hs0, Raw),
- ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1,
- renegotiation = {true, peer}});
- ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_history=Hs0}}) ->
- Hs1 = tls_handshake:update_handshake_history(Hs0, Raw),
- ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1});
- (_, StopState) -> StopState
- end,
- try
- {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0),
- State = State0#state{tls_packets = Packets, tls_handshake_buffer = Buf},
- handle_tls_handshake(Handle, Next, State)
- catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, Current, State0)
- end;
-
-next_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) ->
- case read_application_data(Data, State0) of
- Stop = {stop,_,_} ->
- Stop;
- {Record, State} ->
- next_state(StateName, StateName, Record, State)
- end;
-next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} =
- _ChangeCipher,
- #state{connection_states = ConnectionStates0} = State0) ->
- ConnectionStates1 =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read),
- {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}),
- next_state(Current, Next, Record, State);
-next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) ->
- %% Ignore unknown type
- {Record, State} = next_record(State0),
- next_state(Current, Next, Record, State).
-
-next_tls_record(Data, #state{tls_record_buffer = Buf0,
- tls_cipher_texts = CT0} = State0) ->
- case tls_record:get_tls_records(Data, Buf0) of
- {Records, Buf1} ->
- CT1 = CT0 ++ Records,
- next_record(State0#state{tls_record_buffer = Buf1,
- tls_cipher_texts = CT1});
- #alert{} = Alert ->
- Alert
- end.
-
-next_record(#state{tls_packets = [], tls_cipher_texts = [], socket = Socket,
- transport_cb = Transport} = State) ->
- ssl_socket:setopts(Transport, Socket, [{active,once}]),
- {no_record, State};
-next_record(#state{tls_packets = [], tls_cipher_texts = [CT | Rest],
- connection_states = ConnStates0} = State) ->
- case tls_record:decode_cipher_text(CT, ConnStates0) of
- {Plain, ConnStates} ->
- {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end;
-next_record(State) ->
- {no_record, State}.
-
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
- {no_record ,State};
-
-next_record_if_active(State) ->
- next_record(State).
-
-next_state_connection(StateName, #state{send_queue = Queue0,
- negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
- connection_states = ConnectionStates0
- } = State) ->
- %% Send queued up data that was queued while renegotiating
- case queue:out(Queue0) of
- {{value, {From, Data}}, Queue} ->
- {Msgs, ConnectionStates} =
- ssl_record:encode_data(Data, Version, ConnectionStates0),
+write_application_data(Data0, From,
+ #state{socket = Socket,
+ negotiated_version = Version,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0,
+ send_queue = SendQueue,
+ socket_options = SockOpts,
+ ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) ->
+ Data = encode_packet(Data0, SockOpts),
+
+ case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
+ true ->
+ renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue),
+ renegotiation = {true, internal}});
+ false ->
+ {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0),
Result = Transport:send(Socket, Msgs),
- gen_fsm:reply(From, Result),
- next_state_connection(StateName,
- State#state{connection_states = ConnectionStates,
- send_queue = Queue});
- {empty, Queue0} ->
- next_state_is_connection(StateName, State)
+ {reply, Result,
+ connection, State#state{connection_states = ConnectionStates}, get_timeout(State)}
end.
-%% In next_state_is_connection/1: clear tls_handshake,
-%% premaster_secret and public_key_info (only needed during handshake)
-%% to reduce memory foot print of a connection.
-next_state_is_connection(_, State =
- #state{start_or_recv_from = RecvFrom,
- socket_options =
- #socket_options{active = false}}) when RecvFrom =/= undefined ->
- passive_receive(State#state{premaster_secret = undefined,
- public_key_info = undefined,
- tls_handshake_history = tls_handshake:init_handshake_history()}, connection);
-
-next_state_is_connection(StateName, State0) ->
- {Record, State} = next_record_if_active(State0),
- next_state(StateName, connection, Record, State#state{premaster_secret = undefined,
- public_key_info = undefined,
- tls_handshake_history = tls_handshake:init_handshake_history()}).
-
-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
+encode_packet(Data, #socket_options{packet=Packet}) ->
+ case Packet of
+ 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1);
+ 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1);
+ 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1);
+ _ -> Data
+ end.
-invalidate_session(client, Host, Port, Session) ->
- ssl_manager:invalidate_session(Host, Port, Session);
-invalidate_session(server, _, Port, Session) ->
- ssl_manager:invalidate_session(Port, Session).
+encode_size_packet(Bin, Size, Max) ->
+ Len = erlang:byte_size(Bin),
+ case Len > Max of
+ true -> throw({error, {badarg, {packet_to_large, Len, Max}}});
+ false -> <<Len:Size, Bin/binary>>
+ end.
-initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
- {CbModule, DataTag, CloseTag, ErrorTag}) ->
- ConnectionStates = ssl_record:init_connection_states(Role),
+time_to_renegotiate(_Data,
+ #connection_states{current_write =
+ #connection_state{sequence_number = Num}},
+ RenegotiateAt) ->
- SessionCacheCb = case application:get_env(ssl, session_cb) of
- {ok, Cb} when is_atom(Cb) ->
- Cb;
- _ ->
- ssl_session_cache
- end,
-
- Monitor = erlang:monitor(process, User),
-
- #state{socket_options = SocketOptions,
- %% We do not want to save the password in the state so that
- %% could be written in the clear into error logs.
- ssl_options = SSLOptions#ssl_options{password = undefined},
- session = #session{is_resumable = new},
- transport_cb = CbModule,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- connection_states = ConnectionStates,
- tls_handshake_buffer = <<>>,
- tls_record_buffer = <<>>,
- tls_cipher_texts = [],
- user_application = {Monitor, User},
- user_data_buffer = <<>>,
- session_cache_cb = SessionCacheCb,
- renegotiation = {false, first},
- start_or_recv_from = undefined,
- send_queue = queue:new()
- }.
-
-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;
+ %% We could do test:
+ %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt),
+ %% but we chose to have a some what lower renegotiateAt and a much cheaper test
+ is_time_to_renegotiate(Num, RenegotiateAt).
-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]).
+is_time_to_renegotiate(N, M) when N < M->
+ false;
+is_time_to_renegotiate(_,_) ->
+ true.
+renegotiate(#state{role = client} = State) ->
+ %% Handle same way as if server requested
+ %% the renegotiation
+ Hs0 = ssl_handshake:init_handshake_history(),
+ connection(#hello_request{}, State#state{tls_handshake_history = Hs0});
+renegotiate(#state{role = server,
+ socket = Socket,
+ transport_cb = Transport,
+ negotiated_version = Version,
+ connection_states = ConnectionStates0} = State0) ->
+ HelloRequest = ssl_handshake:hello_request(),
+ Frag = tls_handshake:encode_handshake(HelloRequest, Version),
+ Hs0 = ssl_handshake:init_handshake_history(),
+ {BinMsg, ConnectionStates} =
+ ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
+ Transport:send(Socket, BinMsg),
+ {Record, State} = next_record(State0#state{connection_states =
+ ConnectionStates,
+ tls_handshake_history = Hs0}),
+ next_state(connection, hello, Record, State#state{allow_renegotiate = true}).
handle_alerts([], Result) ->
Result;
@@ -2788,7 +832,8 @@ handle_alerts([Alert | Alerts], {next_state, StateName, State, _Timeout}) ->
handle_alerts(Alerts, handle_alert(Alert, StateName, State)).
handle_alert(#alert{level = ?FATAL} = Alert, StateName,
- #state{socket = Socket, transport_cb = Transport, ssl_options = SslOpts, start_or_recv_from = From, host = Host,
+ #state{socket = Socket, transport_cb = Transport,
+ ssl_options = SslOpts, start_or_recv_from = From, host = Host,
port = Port, session = Session, user_application = {_Mon, Pid},
role = Role, socket_options = Opts} = State) ->
invalidate_session(Role, Host, Port, Session),
@@ -2838,10 +883,12 @@ alert_user(Transport, Socket, Active, Pid, From, Alert, Role) ->
case ssl_alert:reason_code(Alert, Role) of
closed ->
send_or_reply(Active, Pid, From,
- {ssl_closed, ssl_socket:socket(self(), Transport, Socket)});
+ {ssl_closed, ssl_socket:socket(self(),
+ Transport, Socket, ?MODULE)});
ReasonCode ->
send_or_reply(Active, Pid, From,
- {ssl_error, ssl_socket:socket(self(), Transport, Socket), ReasonCode})
+ {ssl_error, ssl_socket:socket(self(),
+ Transport, Socket, ?MODULE), ReasonCode})
end.
log_alert(true, Info, Alert) ->
@@ -2857,7 +904,7 @@ handle_own_alert(Alert, Version, StateName,
ssl_options = SslOpts} = State) ->
try %% Try to tell the other side
{BinMsg, _} =
- encode_alert(Alert, Version, ConnectionStates),
+ ssl_alert:encode(Alert, Version, ConnectionStates),
Transport:send(Socket, BinMsg),
workaround_transport_delivery_problems(Socket, Transport)
catch _:_ -> %% Can crash if we are in a uninitialized state
@@ -2888,73 +935,26 @@ handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = Stat
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE),
handle_own_alert(Alert, Version, {Info, Msg}, State).
-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.
-
-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.
-renegotiate(#state{role = client} = State) ->
- %% Handle same way as if server requested
- %% the renegotiation
- Hs0 = tls_handshake:init_handshake_history(),
- connection(#hello_request{}, State#state{tls_handshake_history = Hs0});
-renegotiate(#state{role = server,
- socket = Socket,
- transport_cb = Transport,
- negotiated_version = Version,
- connection_states = ConnectionStates0} = State0) ->
- HelloRequest = ssl_handshake:hello_request(),
- Frag = tls_handshake:encode_handshake(HelloRequest, Version),
- Hs0 = tls_handshake:init_handshake_history(),
- {BinMsg, ConnectionStates} =
- ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
- Transport:send(Socket, BinMsg),
- {Record, State} = next_record(State0#state{connection_states =
- ConnectionStates,
- tls_handshake_history = Hs0}),
- next_state(connection, hello, Record, State#state{allow_renegotiate = true}).
-
-notify_senders(SendQueue) ->
- lists:foreach(fun({From, _}) ->
- gen_fsm:reply(From, {error, closed})
- end, queue:to_list(SendQueue)).
+handle_close_alert(Data, StateName, State0) ->
+ case next_tls_record(Data, State0) of
+ {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} ->
+ [Alert|_] = decode_alerts(EncAlerts),
+ handle_normal_shutdown(Alert, StateName, State);
+ _ ->
+ ok
+ end.
-notify_renegotiater({true, From}) when not is_atom(From) ->
- gen_fsm:reply(From, {error, closed});
-notify_renegotiater(_) ->
+cancel_timer(undefined) ->
+ ok;
+cancel_timer(Timer) ->
+ erlang:cancel_timer(Timer),
ok.
-terminate_alert(Reason, Version, ConnectionStates) when Reason == normal;
- Reason == user_close ->
- {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- Version, ConnectionStates),
- BinAlert;
-terminate_alert({shutdown, _}, Version, ConnectionStates) ->
- {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- Version, ConnectionStates),
- BinAlert;
-
-terminate_alert(_, Version, ConnectionStates) ->
- {BinAlert, _} = encode_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR),
- Version, ConnectionStates),
- BinAlert.
+invalidate_session(client, Host, Port, Session) ->
+ ssl_manager:invalidate_session(Host, Port, Session);
+invalidate_session(server, _, Port, Session) ->
+ ssl_manager:invalidate_session(Port, Session).
workaround_transport_delivery_problems(Socket, gen_tcp = Transport) ->
%% Standard trick to try to make sure all
@@ -2970,134 +970,3 @@ workaround_transport_delivery_problems(Socket, gen_tcp = Transport) ->
Transport:recv(Socket, 0, 30000);
workaround_transport_delivery_problems(Socket, Transport) ->
Transport:close(Socket).
-
-get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) ->
- infinity;
-get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) ->
- HibernateAfter.
-
-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.
-
-get_current_connection_state_prf(CStates, Direction) ->
- CS = ssl_record:current_connection_state(CStates, Direction),
- CS#connection_state.security_parameters#security_parameters.prf_algorithm.
-get_pending_connection_state_prf(CStates, Direction) ->
- CS = ssl_record:pending_connection_state(CStates, Direction),
- CS#connection_state.security_parameters#security_parameters.prf_algorithm.
-
-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}).
-
-cancel_timer(undefined) ->
- ok;
-cancel_timer(Timer) ->
- erlang:cancel_timer(Timer),
- ok.
-
-handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} = State) ->
- ssl_socket:setopts(Transport, Socket, [{active, false}]),
- case Transport:recv(Socket, 0, 0) of
- {error, closed} ->
- ok;
- {ok, Data} ->
- handle_close_alert(Data, StateName, State)
- end.
-
-handle_close_alert(Data, StateName, State0) ->
- case next_tls_record(Data, State0) of
- {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} ->
- [Alert|_] = decode_alerts(EncAlerts),
- handle_normal_shutdown(Alert, StateName, State);
- _ ->
- ok
- end.
-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.