aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/ssl_connection.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/ssl_connection.erl')
-rw-r--r--lib/ssl/src/ssl_connection.erl1704
1 files changed, 1704 insertions, 0 deletions
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
new file mode 100644
index 0000000000..178c055cdf
--- /dev/null
+++ b/lib/ssl/src/ssl_connection.erl
@@ -0,0 +1,1704 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles an ssl connection, e.i. both the setup
+%% e.i. SSL-Handshake, SSL-Alert and SSL-Cipher protocols and delivering
+%% data to the application. All data on the connectinon is received and
+%% sent according to the SSL-record protocol.
+%%----------------------------------------------------------------------
+
+-module(ssl_connection).
+
+-behaviour(gen_fsm).
+
+-include("ssl_debug.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_int.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Internal application API
+-export([send/2, send/3, recv/3, connect/7, accept/6, close/1, shutdown/2,
+ new_user/2, get_opts/2, set_opts/2, info/1, session_info/1,
+ peer_certificate/1,
+ sockname/1, peername/1]).
+
+%% Called by ssl_connection_sup
+-export([start_link/7]).
+
+%% gen_fsm callbacks
+-export([init/1, hello/2, certify/2, cipher/2, connection/2, connection/3, abbreviated/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
+ host, % string() | ipadress()
+ port, % integer()
+ socket, % socket()
+ ssl_options, % #ssl_options{}
+ socket_options, % #socket_options{}
+ connection_states, % #connection_states{} from ssl_record.hrl
+ tls_record_buffer, % binary() buffer of incomplete records
+ tls_handshake_buffer, % binary() buffer of incomplete handshakes
+ %% {{md5_hash, sha_hash}, {prev_md5, prev_sha}} (binary())
+ tls_handshake_hashes, % see above
+ tls_cipher_texts, % list() received but not deciphered yet
+ own_cert, % binary()
+ session, % #session{} from ssl_handshake.erl
+ session_cache, %
+ session_cache_cb, %
+ negotiated_version, % #protocol_version{}
+ supported_protocol_versions, % [atom()]
+ client_certificate_requested = false,
+ key_algorithm, % atom as defined by cipher_suite
+ public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams}
+ private_key, % PKIX: 'RSAPrivateKey'
+ diffie_hellman_params, %
+ premaster_secret, %
+ cert_db_ref, % ets_table()
+ from, % term(), where to reply
+ bytes_to_read, % integer(), # bytes to read in passive mode
+ user_data_buffer, % binary()
+%% tls_buffer, % Keeps a lookahead one packet if available
+ log_alert % boolan()
+ }).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+send(Pid, Data) ->
+ sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity).
+send(Pid, Data, Timeout) ->
+ sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+recv(Pid, Length, Timeout) -> % TODO: Prio with renegotiate?
+ sync_send_all_state_event(Pid, {recv, Length}, Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+connect(Host, Port, Socket, Options, User, CbInfo, Timeout) ->
+ start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+accept(Port, Socket, Opts, User, CbInfo, Timeout) ->
+ start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+close(ConnectionPid) ->
+ case sync_send_all_state_event(ConnectionPid, close) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+shutdown(ConnectionPid, How) ->
+ sync_send_all_state_event(ConnectionPid, {shutdown, How}).
+
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+new_user(ConnectionPid, User) ->
+ sync_send_all_state_event(ConnectionPid, {new_user, User}).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+sockname(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, sockname).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+peername(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, peername).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+get_opts({ListenSocket, {_SslOpts, SockOpts}, _}, OptTags) ->
+ get_socket_opts(ListenSocket, OptTags, SockOpts, []);
+get_opts(ConnectionPid, OptTags) ->
+ sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+set_opts(ConnectionPid, Options) ->
+ sync_send_all_state_event(ConnectionPid, {set_opts, Options}).
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, info).
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+session_info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, session_info).
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+peer_certificate(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, peer_certificate).
+
+%%====================================================================
+%% ssl_connection_sup API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%%
+%% Description: Creates a gen_fsm process which calls Module:init/1 to
+%% initialize. To ensure a synchronized start-up procedure, this function
+%% does not return until Module:init/1 has returned.
+%%--------------------------------------------------------------------
+start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
+ gen_fsm:start_link(?MODULE, [Role, Host, Port, Socket, Options,
+ User, CbInfo], []).
+
+
+%%====================================================================
+%% gen_fsm callbacks
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, StateName, State} |
+%% {ok, StateName, State, Timeout} |
+%% ignore |
+%% {stop, StopReason}
+%% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or
+%% gen_fsm:start_link/3,4, this function is called by the new process to
+%% initialize.
+%%--------------------------------------------------------------------
+init([Role, Host, Port, Socket, {SSLOpts, _} = Options,
+ User, CbInfo]) ->
+ State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
+ Hashes0 = ssl_handshake:init_hashes(),
+
+ try ssl_init(SSLOpts, Role) of
+ {ok, Ref, CacheRef, OwnCert, Key} ->
+ State = State0#state{tls_handshake_hashes = Hashes0,
+ own_cert = OwnCert,
+ cert_db_ref = Ref,
+ session_cache = CacheRef,
+ private_key = Key},
+ {ok, hello, State}
+ catch
+ throw:Error ->
+ {stop, Error}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% state_name(Event, State) -> {next_state, NextStateName, NextState}|
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}
+%% Description:There should be one instance of this function for each possible
+%% state name. Whenever a gen_fsm receives an event sent using
+%% gen_fsm:send_event/2, the instance of this function with the same name as
+%% the current state name StateName is called to handle the event. It is also
+%% called if a timeout occurs.
+%%--------------------------------------------------------------------
+hello(socket_control, #state{host = Host, port = Port, role = client,
+ ssl_options = SslOpts,
+ transport_cb = Transport, socket = Socket,
+ connection_states = ConnectionStates}
+ = State0) ->
+ Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, SslOpts),
+ Version = Hello#client_hello.client_version,
+ Hashes0 = ssl_handshake:init_hashes(),
+ {BinMsg, CS2, Hashes1} =
+ encode_handshake(Hello, Version, ConnectionStates, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State = State0#state{connection_states = CS2,
+ negotiated_version = Version, %% Requested version
+ session =
+ #session{session_id = Hello#client_hello.session_id,
+ is_resumable = false},
+ tls_handshake_hashes = Hashes1},
+ {next_state, hello, next_record(State)};
+
+hello(socket_control, #state{role = server} = State) ->
+ {next_state, hello, next_record(State)};
+
+hello(hello, #state{role = client} = State) ->
+ {next_state, hello, State};
+
+hello(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression} = Hello,
+ #state{session = Session0 = #session{session_id = OldId},
+ connection_states = ConnectionStates0,
+ role = client,
+ negotiated_version = ReqVersion,
+ host = Host, port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb} = State0) ->
+ {Version, NewId, ConnectionStates1} =
+ ssl_handshake:hello(Hello, ConnectionStates0),
+
+ {KeyAlgorithm, _, _, _} =
+ ssl_cipher:suite_definition(CipherSuite),
+
+ PremasterSecret = make_premaster_secret(ReqVersion),
+
+ State = State0#state{key_algorithm = KeyAlgorithm,
+ negotiated_version = Version,
+ connection_states = ConnectionStates1,
+ premaster_secret = PremasterSecret},
+
+ case ssl_session:is_new(OldId, NewId) of
+ true ->
+ Session = Session0#session{session_id = NewId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {next_state, certify,
+ next_record(State#state{session = Session})};
+ false ->
+ Session = CacheCb:lookup(Cache, {{Host, Port}, NewId}),
+ case ssl_handshake:master_secret(Version, Session,
+ ConnectionStates1, client) of
+ {_, ConnectionStates2} ->
+ {next_state, abbreviated,
+ next_record(State#state{
+ connection_states = ConnectionStates2,
+ session = Session})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, hello, State),
+ {stop, normal, State}
+ end
+ end;
+
+hello(Hello = #client_hello{client_version = ClientVersion},
+ State = #state{connection_states = ConnectionStates0,
+ port = Port, session = Session0,
+ session_cache = Cache,
+ session_cache_cb = CacheCb,
+ ssl_options = SslOpts}) ->
+
+ case ssl_handshake:hello(Hello, {Port, SslOpts,
+ Session0, Cache, CacheCb,
+ ConnectionStates0}) of
+ {Version, {Type, Session}, ConnectionStates} ->
+ do_server_hello(Type, State#state{connection_states =
+ ConnectionStates,
+ negotiated_version = Version,
+ session = Session});
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ClientVersion, hello, State),
+ {stop, normal, State}
+ end.
+
+abbreviated(socket_control, #state{role = server} = State) ->
+ {next_state, abbreviated, State};
+abbreviated(hello, State) ->
+ {next_state, certify, State};
+
+abbreviated(Finished = #finished{},
+ #state{role = server,
+ negotiated_version = Version,
+ tls_handshake_hashes = Hashes,
+ session = #session{master_secret = MasterSecret},
+ from = From} = State) ->
+ case ssl_handshake:verify_connection(Version, Finished, client,
+ MasterSecret, Hashes) of
+ verified ->
+ gen_fsm:reply(From, connected),
+ {next_state, connection, next_record_if_active(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, abbreviated, State),
+ {stop, normal, State}
+ end;
+
+abbreviated(Finished = #finished{},
+ #state{role = client, tls_handshake_hashes = Hashes0,
+ session = #session{master_secret = MasterSecret},
+ from = From,
+ negotiated_version = Version} = State) ->
+ case ssl_handshake:verify_connection(Version, Finished, server,
+ MasterSecret, Hashes0) of
+ verified ->
+ {ConnectionStates, Hashes} = finalize_client_handshake(State),
+ gen_fsm:reply(From, connected),
+ {next_state, connection,
+ next_record_if_active(State#state{tls_handshake_hashes = Hashes,
+ connection_states =
+ ConnectionStates})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, abbreviated, State),
+ {stop, normal, State}
+ end.
+
+certify(socket_control, #state{role = server} = State) ->
+ {next_state, certify, State};
+certify(hello, State) ->
+ {next_state, certify, 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_certificate, State),
+ {stop, normal, State};
+
+certify(#certificate{asn1_certificates = []},
+ #state{role = server,
+ ssl_options = #ssl_options{verify = verify_peer,
+ fail_if_no_peer_cert = false}} =
+ State) ->
+ {next_state, certify, next_record(State#state{client_certificate_requested = false})};
+
+certify(#certificate{} = Cert,
+ #state{session = Session,
+ negotiated_version = Version,
+ cert_db_ref = CertDbRef,
+ ssl_options = Opts} = State0) ->
+ case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth,
+ Opts#ssl_options.verify,
+ Opts#ssl_options.verify_fun) of
+ {PeerCert, PublicKeyInfo} ->
+ State = State0#state{session =
+ Session#session{peer_certificate = PeerCert},
+ public_key_info = PublicKeyInfo,
+ client_certificate_requested = false
+ },
+ {next_state, certify, next_record(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, certify_certificate, State0),
+ {stop, normal, State0}
+ end;
+
+certify(#server_key_exchange{} = KeyExchangeMsg,
+ #state{role = client,
+ key_algorithm = Alg} = State)
+ when Alg == dhe_dss; Alg == dhe_rsa; Alg == dh_anon; Alg == krb5 ->
+ NewState = handle_server_key(KeyExchangeMsg, State),
+ {next_state, certify, NewState};
+
+certify(#server_key_exchange{},
+ State = #state{role = client, negotiated_version = Version,
+ key_algorithm = Alg})
+ when Alg == rsa; Alg == dh_dss; Alg == dh_rsa ->
+ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE),
+ handle_own_alert(Alert, Version, certify_server_key_exchange, State),
+ {stop, normal, State};
+
+certify(KeyExchangeMsg = #server_key_exchange{}, State =
+ #state{role = server}) ->
+ NewState = handle_clinet_key(KeyExchangeMsg, State),
+ {next_state, cipher, NewState};
+
+certify(#certificate_request{}, State) ->
+ NewState = State#state{client_certificate_requested = true},
+ {next_state, certify, next_record(NewState)};
+
+certify(#server_hello_done{},
+ #state{session = Session0,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ premaster_secret = PremasterSecret,
+ role = client} = State0) ->
+ case ssl_handshake:master_secret(Version, PremasterSecret,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates1} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates1,
+ session = Session},
+ client_certify_and_key_exchange(State);
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version,
+ certify_server_hello_done, State0),
+ {stop, normal, State0}
+ end;
+
+certify(#client_key_exchange{},
+ State = #state{role = server,
+ client_certificate_requested = true,
+ ssl_options = #ssl_options{fail_if_no_peer_cert = true},
+ negotiated_version = Version}) ->
+ %% We expect a certificate here
+ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE),
+ handle_own_alert(Alert, Version, certify_server_waiting_certificate, State),
+ {stop, normal, State};
+
+
+certify(#client_key_exchange{exchange_keys
+ = #encrypted_premaster_secret{premaster_secret
+ = EncPMS}},
+ #state{negotiated_version = Version,
+ connection_states = ConnectionStates0,
+ session = Session0,
+ private_key = Key} = State0) ->
+ try ssl_handshake:decrypt_premaster_secret(EncPMS, Key) of
+ PremasterSecret ->
+ case ssl_handshake:master_secret(Version, PremasterSecret,
+ ConnectionStates0, server) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ {next_state, cipher, next_record(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version,
+ certify_client_key_exchange, State0),
+ {stop, normal, State0}
+ end
+ catch
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, certify_client_key_exchange,
+ State0),
+ {stop, normal, State0}
+ end.
+
+cipher(socket_control, #state{role = server} = State) ->
+ {next_state, cipher, State};
+cipher(hello, State) ->
+ {next_state, cipher, State};
+
+cipher(#certificate_verify{signature = Signature},
+ #state{role = server,
+ public_key_info = PublicKeyInfo,
+ negotiated_version = Version,
+ session = #session{master_secret = MasterSecret},
+ key_algorithm = Algorithm,
+ tls_handshake_hashes = Hashes
+ } = State) ->
+ case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
+ Version, MasterSecret,
+ Algorithm, Hashes) of
+ valid ->
+ {next_state, cipher, next_record(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, cipher, State),
+ {stop, normal, State}
+ end;
+
+cipher(#finished{} = Finished,
+ State = #state{from = From,
+ negotiated_version = Version,
+ host = Host,
+ port = Port,
+ role = Role,
+ session = #session{master_secret = MasterSecret}
+ = Session0,
+ tls_handshake_hashes = Hashes}) ->
+
+ case ssl_handshake:verify_connection(Version, Finished,
+ opposite_role(Role),
+ MasterSecret, Hashes) of
+ verified ->
+ gen_fsm:reply(From, connected),
+ Session = register_session(Role, Host, Port, Session0),
+ case Role of
+ client ->
+ {next_state, connection,
+ next_record_if_active(State#state{session = Session})};
+ server ->
+ {NewConnectionStates, NewHashes} =
+ finalize_server_handshake(State#state{
+ session = Session}),
+ NewState =
+ State#state{connection_states = NewConnectionStates,
+ session = Session,
+ tls_handshake_hashes = NewHashes},
+ {next_state, connection, next_record_if_active(NewState)}
+ end;
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, cipher, State),
+ {stop, normal, State}
+ end.
+
+connection(socket_control, #state{role = server} = State) ->
+ {next_state, connection, State};
+connection(hello, State = #state{host = Host, port = Port,
+ socket = Socket,
+ ssl_options = SslOpts,
+ negotiated_version = Version,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0,
+ tls_handshake_hashes = Hashes0}) ->
+
+ Hello = ssl_handshake:client_hello(Host, Port,
+ ConnectionStates0, SslOpts),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Hello, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ {next_state, hello, State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1}}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% state_name(Event, From, State) -> {next_state, NextStateName, NextState} |
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {reply, Reply, NextStateName, NextState}|
+%% {reply, Reply, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}|
+%% {stop, Reason, Reply, NewState}
+%% Description: There should be one instance of this function for each
+%% possible state name. Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_event/2,3, the instance of this function with the same
+%% name as the current state name StateName is called to handle the event.
+%%--------------------------------------------------------------------
+connection({application_data, Data}, _From,
+ State = #state{socket = Socket,
+ negotiated_version = Version,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0}) ->
+ %% 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.
+ {Msgs, ConnectionStates1} = encode_data(Data, Version, ConnectionStates0),
+ Result = Transport:send(Socket, Msgs),
+ {reply, Result,
+ connection, State#state{connection_states = ConnectionStates1}}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_event(Event, StateName, State) -> {next_state, NextStateName,
+%% NextState} |
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:send_all_state_event/2, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+handle_event(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
+ StateName,
+ State = #state{key_algorithm = KeyAlg,
+ tls_handshake_buffer = Buf0,
+ negotiated_version = Version}) ->
+ Handle =
+ fun({Packet, Raw}, {next_state, SName, AS=#state{tls_handshake_hashes=Hs0}}) ->
+ Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
+ ?MODULE:SName(Packet, AS#state{tls_handshake_hashes=Hs1});
+ (_, StopState) -> StopState
+ end,
+ try
+ {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version),
+ Start = {next_state, StateName, State#state{tls_handshake_buffer = Buf}},
+ lists:foldl(Handle, Start, Packets)
+ catch throw:#alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State),
+ {stop, normal, State}
+ end;
+
+handle_event(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data},
+ StateName, State0) ->
+ case application_data(Data, State0) of
+ Stop = {stop,_,_} ->
+ Stop;
+ State ->
+ {next_state, StateName, State}
+ end;
+
+handle_event(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} =
+ _ChangeCipher,
+ StateName,
+ State = #state{connection_states = ConnectionStates0}) ->
+ ?DBG_TERM(_ChangeCipher),
+ ConnectionStates1 =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read),
+ {next_state, StateName,
+ next_record(State#state{connection_states = ConnectionStates1})};
+
+handle_event(#ssl_tls{type = ?ALERT, fragment = Data}, StateName, State) ->
+ Alerts = decode_alerts(Data),
+ ?DBG_TERM(Alerts),
+ [alert_event(A) || A <- Alerts],
+ {next_state, StateName, State};
+
+handle_event(#alert{level = ?FATAL} = Alert, connection,
+ #state{from = From, user_application = {_Mon, Pid}, log_alert = Log,
+ host = Host, port = Port, session = Session,
+ role = Role, socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ log_alert(Log, connection, Alert),
+ alert_user(Opts#socket_options.active, Pid, From, Alert, Role),
+ {stop, normal, State};
+handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ connection, #state{from = From,
+ role = Role,
+ user_application = {_Mon, Pid},
+ socket_options = Opts} = State) ->
+ alert_user(Opts#socket_options.active, Pid, From, Alert, Role),
+ {stop, normal, State};
+
+handle_event(#alert{level = ?FATAL} = Alert, StateName,
+ #state{from = From, host = Host, port = Port, session = Session,
+ log_alert = Log, role = Role} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ log_alert(Log, StateName, Alert),
+ alert_user(From, Alert, Role),
+ {stop, normal, State};
+handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ _, #state{from = From, role = Role} = State) ->
+ alert_user(From, Alert, Role),
+ {stop, normal, State};
+handle_event(#alert{level = ?WARNING} = Alert, StateName,
+ #state{log_alert = Log} = State) ->
+ log_alert(Log, StateName, Alert),
+%%TODO: Could be user_canceled or no_negotiation should the latter be
+ %% treated as fatal?!
+ {next_state, StateName, next_record(State)}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_sync_event(Event, From, StateName,
+%% State) -> {next_state, NextStateName, NextState} |
+%% {next_state, NextStateName, NextState,
+%% Timeout} |
+%% {reply, Reply, NextStateName, NextState}|
+%% {reply, Reply, NextStateName, NextState,
+%% Timeout} |
+%% {stop, Reason, NewState} |
+%% {stop, Reason, Reply, NewState}
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+handle_sync_event(started, From, StateName, State) ->
+ {next_state, StateName, State#state{from = From}};
+
+handle_sync_event(close, From, _StateName, State) ->
+ {stop, normal, ok, State#state{from = From}};
+
+handle_sync_event({shutdown, How}, From, StateName,
+ #state{transport_cb = CbModule,
+ socket = Socket} = State) ->
+ case CbModule:shutdown(Socket, How) of
+ ok ->
+ {reply, ok, StateName, State};
+ Error ->
+ {stop, normal, Error, State#state{from = From}}
+ end;
+
+%% TODO: men vad g�r next_record om det �r t.ex. renegotiate? kanske
+%% inte bra... t�l att t�nkas p�!
+handle_sync_event({recv, N}, From, StateName,
+ State0 = #state{user_data_buffer = Buffer}) ->
+ State1 = State0#state{bytes_to_read = N, from = From},
+ case Buffer of
+ <<>> ->
+ State = next_record(State1),
+ {next_state, StateName, State};
+ _ ->
+ case application_data(<<>>, State1) of
+ Stop = {stop, _, _} ->
+ Stop;
+ State ->
+ {next_state, StateName, State}
+ end
+ end;
+
+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}}};
+
+handle_sync_event({get_opts, OptTags}, _From, StateName,
+ #state{socket = Socket,
+ socket_options = SockOpts} = State) ->
+ OptsReply = get_socket_opts(Socket, OptTags, SockOpts, []),
+ {reply, OptsReply, StateName, State};
+
+handle_sync_event(sockname, _From, StateName,
+ #state{socket = Socket} = State) ->
+ SockNameReply = inet:sockname(Socket),
+ {reply, SockNameReply, StateName, State};
+
+handle_sync_event(peername, _From, StateName,
+ #state{socket = Socket} = State) ->
+ PeerNameReply = inet:peername(Socket),
+ {reply, PeerNameReply, StateName, State};
+
+handle_sync_event({set_opts, Opts0}, _From, StateName,
+ #state{socket_options = Opts1,
+ socket = Socket,
+ user_data_buffer = Buffer} = State0) ->
+ Opts = set_socket_opts(Socket, Opts0, Opts1, []),
+ State1 = State0#state{socket_options = Opts},
+ if
+ Opts#socket_options.active =:= false ->
+ {reply, ok, StateName, State1};
+ Buffer =:= <<>>, Opts1#socket_options.active =:= false ->
+ %% Need data, set active once
+ {reply, ok, StateName, next_record_if_active(State1)};
+ Buffer =:= <<>> ->
+ %% Active once already set
+ {reply, ok, StateName, State1};
+ true ->
+ case application_data(<<>>, State1) of
+ Stop = {stop,_,_} ->
+ Stop;
+ State ->
+ {reply, ok, StateName, State}
+ end
+ end;
+
+handle_sync_event(info, _, StateName,
+ #state{negotiated_version = Version,
+ session = #session{cipher_suite = Suite}} = State) ->
+
+ AtomVersion = ssl_record:protocol_version(Version),
+ {reply, {ok, {AtomVersion, ssl_cipher:suite_definition(Suite)}},
+ StateName, State};
+
+handle_sync_event(session_info, _, StateName,
+ #state{session = #session{session_id = Id,
+ cipher_suite = Suite}} = State) ->
+ {reply, [{session_id, Id},
+ {cipher_suite, ssl_cipher:suite_definition(Suite)}],
+ StateName, State};
+
+handle_sync_event(peer_certificate, _, StateName,
+ #state{session = #session{peer_certificate = Cert}}
+ = State) ->
+ {reply, {ok, Cert}, StateName, State}.
+
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}|
+%% {next_state, NextStateName, NextState,
+%% Timeout} |
+%% {stop, Reason, NewState}
+%% Description: This function is called by a gen_fsm when it receives any
+%% other message than a synchronous or asynchronous event
+%% (or a system message).
+%%--------------------------------------------------------------------
+
+%% raw data from TCP, unpack records
+handle_info({Protocol, _, Data}, StateName, State =
+ #state{data_tag = Protocol,
+ negotiated_version = Version,
+ tls_record_buffer = Buf0,
+ tls_cipher_texts = CT0}) ->
+ case ssl_record:get_tls_records(Data, Buf0) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ {next_state, StateName,
+ next_record(State#state{tls_record_buffer = Buf1,
+ tls_cipher_texts = CT1})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State),
+ {stop, normal, State}
+ end;
+
+%% %% This is the code for {packet,ssl} removed because it was slower
+%% %% than handling it in erlang.
+%% handle_info(Data = #ssl_tls{}, StateName,
+%% State = #state{tls_buffer = Buffer,
+%% socket = Socket,
+%% connection_states = ConnectionStates0}) ->
+%% case Buffer of
+%% buffer ->
+%% {next_state, StateName, State#state{tls_buffer = [Data]}};
+%% continue ->
+%% inet:setopts(Socket, [{active,once}]),
+%% {Plain, ConnectionStates} =
+%% ssl_record:decode_cipher_text(Data, ConnectionStates0),
+%% gen_fsm:send_all_state_event(self(), Plain),
+%% {next_state, StateName,
+%% State#state{tls_buffer = buffer,
+%% connection_states = ConnectionStates}};
+%% List when is_list(List) ->
+%% {next_state, StateName,
+%% State#state{tls_buffer = Buffer ++ [Data]}}
+%% end;
+
+%% handle_info(CloseMsg = {_, Socket}, StateName0,
+%% #state{socket = Socket,tls_buffer = [Msg]} = State0) ->
+%% %% Hmm we have a ssl_tls msg buffered, handle that first
+%% %% and it proberbly is a close alert
+%% {next_state, StateName0, State0#state{tls_buffer=[Msg,{ssl_close,CloseMsg}]}};
+
+handle_info({CloseTag, Socket}, _StateName,
+ #state{socket = Socket, close_tag = CloseTag,
+ negotiated_version = Version, host = Host,
+ port = Port, socket_options = Opts,
+ user_application = {_Mon,Pid}, from = From,
+ role = Role, session = Session} = State) ->
+ %% Debug option maybe, the user do NOT want to see these in their logs
+ %% error_logger:info_report("SSL: Peer did not send close notify alert."),
+ case Version of
+ {1, N} when N >= 1 ->
+ ok;
+ _ ->
+ invalidate_session(Role, Host, Port, Session)
+ end,
+ alert_user(Opts#socket_options.active, Pid, From,
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), Role),
+ {stop, normal, State};
+
+handle_info({'DOWN', MonitorRef, _, _, _}, _,
+ State = #state{user_application={MonitorRef,_Pid}}) ->
+ {stop, normal, State};
+
+handle_info(A, StateName, State) ->
+ io:format("SSL: Bad info (state ~w): ~w\n", [StateName, A]),
+ {stop, bad_info, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, StateName, State) -> void()
+%% Description:This function is called by a gen_fsm when it is about
+%% to terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_fsm terminates with
+%% Reason. The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, connection, _S=#state{negotiated_version = Version,
+ connection_states = ConnectionStates,
+ transport_cb = Transport,
+ socket = Socket}) ->
+ {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING,?CLOSE_NOTIFY),
+ Version, ConnectionStates),
+ Transport:send(Socket, BinAlert),
+ Transport:close(Socket);
+terminate(_Reason, _StateName, _S=#state{transport_cb = Transport, socket = Socket}) ->
+ Transport:close(Socket),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_} = CbInfo,
+ Timeout) ->
+ case ssl_connection_sup:start_child([Role, Host, Port, Socket,
+ Opts, User, CbInfo]) of
+ {ok, Pid} ->
+ CbModule:controlling_process(Socket, Pid),
+ send_event(Pid, socket_control),
+ case sync_send_all_state_event(Pid, started, Timeout) of
+ connected ->
+ {ok, sslsocket(Pid)};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+ssl_init(SslOpts, Role) ->
+ {ok, CertDbRef, CacheRef, OwnCert} = init_certificates(SslOpts, Role),
+ PrivateKey =
+ init_private_key(SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile,
+ SslOpts#ssl_options.password, Role),
+ ?DBG_TERM(PrivateKey),
+ {ok, CertDbRef, CacheRef, OwnCert, PrivateKey}.
+
+init_certificates(#ssl_options{cacertfile = CACertFile,
+ certfile = CertFile}, Role) ->
+
+ case ssl_manager:connection_init(CACertFile, Role) of
+ {ok, CertDbRef, CacheRef} ->
+ init_certificates(CertDbRef, CacheRef, CertFile, Role);
+ {error, _Error} ->
+ Report = io_lib:format("SSL: Error ~p ~n",[_Error]),
+ error_logger:error_report(Report),
+ throw(ecacertfile)
+ end.
+
+init_certificates(CertDbRef, CacheRef, CertFile, client) ->
+ try
+ [OwnCert] = ssl_certificate:file_to_certificats(CertFile),
+ {ok, CertDbRef, CacheRef, OwnCert}
+ catch _E:_R ->
+ {ok, CertDbRef, CacheRef, undefined}
+ end;
+
+init_certificates(CertDbRef, CacheRef, CertFile, server) ->
+ try
+ [OwnCert] = ssl_certificate:file_to_certificats(CertFile),
+ {ok, CertDbRef, CacheRef, OwnCert}
+ catch _E:_R ->
+ Report = io_lib:format("SSL: ~p: ~p:~p ~p~n",
+ [?LINE, _E,_R, erlang:get_stacktrace()]),
+ error_logger:error_report(Report),
+ throw(ecertfile)
+ end.
+
+init_private_key(undefined, "", _Password, client) ->
+ undefined;
+init_private_key(undefined, KeyFile, Password, _) ->
+ try
+ {ok, List} = ssl_manager:cache_pem_file(KeyFile),
+ [Der] = [Der || Der = {PKey, _ , _} <- List,
+ PKey =:= rsa_private_key orelse PKey =:= dsa_private_key],
+ {ok, Decoded} = public_key:decode_private_key(Der,Password),
+ Decoded
+ catch _E:_R ->
+ Report = io_lib:format("SSL: ~p: ~p:~p ~p~n",
+ [?LINE, _E,_R, erlang:get_stacktrace()]),
+ error_logger:error_report(Report),
+ throw(ekeyfile)
+ end;
+init_private_key(PrivateKey, _, _,_) ->
+ PrivateKey.
+
+send_event(FsmPid, Event) ->
+ gen_fsm:send_event(FsmPid, Event).
+
+sync_send_event(FsmPid, Event, Timeout) ->
+ try gen_fsm:sync_send_event(FsmPid, Event, Timeout) of
+ Reply ->
+ Reply
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{timeout, _} ->
+ {error, timeout};
+ exit:{normal, _} ->
+ {error, closed}
+ end.
+
+
+
+send_all_state_event(FsmPid, Event) ->
+ gen_fsm:send_all_state_event(FsmPid, Event).
+
+sync_send_all_state_event(FsmPid, Event) ->
+ sync_send_all_state_event(FsmPid, Event, ?DEFAULT_TIMEOUT
+).
+
+sync_send_all_state_event(FsmPid, Event, Timeout) ->
+ try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{timeout, _} ->
+ {error, timeout};
+ exit:{normal, _} ->
+ {error, closed}
+ end.
+
+%% Events: #alert{}
+alert_event(Alert) ->
+ send_all_state_event(self(), Alert).
+
+certify_client(#state{client_certificate_requested = true, role = client,
+ connection_states = ConnectionStates0,
+ transport_cb = Transport,
+ negotiated_version = Version,
+ cert_db_ref = CertDbRef,
+ own_cert = OwnCert,
+ socket = Socket,
+ tls_handshake_hashes = Hashes0} = State) ->
+ Certificate = ssl_handshake:certificate(OwnCert, CertDbRef, client),
+ {BinCert, ConnectionStates1, Hashes1} =
+ encode_handshake(Certificate, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinCert),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+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,
+ own_cert = OwnCert,
+ socket = Socket,
+ key_algorithm = KeyAlg,
+ private_key = PrivateKey,
+ session = #session{master_secret = MasterSecret},
+ tls_handshake_hashes = Hashes0} = State) ->
+ case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
+ Version, KeyAlg,
+ PrivateKey, Hashes0) of
+ ignore -> %% No key or cert or fixed_diffie_hellman
+ State;
+ Verified ->
+ SigAlg = ssl_handshake:sig_alg(KeyAlg),
+ {BinVerified, ConnectionStates1, Hashes1} =
+ encode_handshake(Verified, SigAlg, Version,
+ ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinVerified),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1}
+ end;
+verify_client_cert(#state{client_certificate_requested = false} = State) ->
+ State.
+
+do_server_hello(Type, #state{negotiated_version = Version,
+ session = Session,
+ connection_states = ConnectionStates0}
+ = State0) when is_atom(Type) ->
+ ServerHello =
+ ssl_handshake:server_hello(Session#session.session_id, Version,
+ ConnectionStates0),
+ State = server_hello(ServerHello, State0),
+
+ case Type of
+ new ->
+ do_server_hello(ServerHello, State);
+ resumed ->
+ case ssl_handshake:master_secret(Version, Session,
+ ConnectionStates0, server) of
+ {_, ConnectionStates1} ->
+ {ConnectionStates, Hashes} =
+ finished(State#state{connection_states =
+ ConnectionStates1}),
+ {next_state, abbreviated,
+ next_record(State#state{connection_states =
+ ConnectionStates,
+ tls_handshake_hashes = Hashes})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, hello, State),
+ {stop, normal, State}
+ end
+ end;
+
+do_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 ->
+ State = server_hello_done(State1),
+ Session =
+ Session0#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {next_state, certify, State#state{session = Session}}
+ catch
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, hello, State0),
+ {stop, normal, State0}
+ end.
+
+client_certify_and_key_exchange(#state{negotiated_version = Version} =
+ State0) ->
+ try do_client_certify_and_key_exchange(State0) of
+ State1 = #state{} ->
+ {ConnectionStates, Hashes} = finalize_client_handshake(State1),
+ State = State1#state{connection_states = ConnectionStates,
+ %% Reinitialize
+ client_certificate_requested = false,
+ tls_handshake_hashes = Hashes},
+ {next_state, cipher, next_record(State)}
+
+ catch
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, certify_foo, State0),
+ {stop, normal, State0}
+ end.
+
+do_client_certify_and_key_exchange(State0) ->
+ State1 = certify_client(State0),
+ State2 = key_exchange(State1),
+ verify_client_cert(State2).
+
+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_hashes = Hashes0} = State) ->
+ CipherSuite = ServerHello#server_hello.cipher_suite,
+ {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
+ %% Version = ServerHello#server_hello.server_version, TODO ska kontrolleras
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(ServerHello, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1,
+ key_algorithm = KeyAlgorithm}.
+
+server_hello_done(#state{transport_cb = Transport,
+ socket = Socket,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ tls_handshake_hashes = Hashes} = State0) ->
+
+ HelloDone = ssl_handshake:server_hello_done(),
+
+ {BinHelloDone, NewConnectionStates, NewHashes} =
+ encode_handshake(HelloDone, Version, ConnectionStates, Hashes),
+ Transport:send(Socket, BinHelloDone),
+ State = State0#state{connection_states = NewConnectionStates,
+ tls_handshake_hashes = NewHashes},
+ next_record(State).
+
+certify_server(#state{transport_cb = Transport,
+ socket = Socket,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ tls_handshake_hashes = Hashes,
+ cert_db_ref = CertDbRef,
+ own_cert = OwnCert} = State) ->
+
+ case ssl_handshake:certificate(OwnCert, CertDbRef, server) of
+ CertMsg = #certificate{} ->
+ {BinCertMsg, NewConnectionStates, NewHashes} =
+ encode_handshake(CertMsg, Version, ConnectionStates, Hashes),
+ Transport:send(Socket, BinCertMsg),
+ State#state{connection_states = NewConnectionStates,
+ tls_handshake_hashes = NewHashes
+ };
+ Alert = #alert{} ->
+ throw(Alert)
+ end.
+
+key_exchange(#state{role = server, key_algorithm = Algo} = State)
+ when Algo == rsa;
+ Algo == dh_dss;
+ Algo == dh_rsa ->
+ State;
+
+key_exchange(#state{role = server, key_algorithm = rsa_export} = State) ->
+ %% TODO when the public key in the server certificate is
+ %% less than or equal to 512 bits in length dont send key_exchange
+ %% but do it otherwise
+ State;
+
+key_exchange(#state{role = server, key_algorithm = Algo,
+ diffie_hellman_params = Params,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ tls_handshake_hashes = Hashes0,
+ socket = Socket,
+ transport_cb = Transport
+ } = State)
+ when Algo == dhe_dss;
+ Algo == dhe_dss_export;
+ Algo == dhe_rsa;
+ Algo == dhe_rsa_export ->
+ Msg = ssl_handshake:key_exchange(server, Params),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+
+key_exchange(#state{role = server, key_algorithm = dh_anon,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ tls_handshake_hashes = Hashes0,
+ socket = Socket,
+ transport_cb = Transport
+ } = State) ->
+ Msg = ssl_handshake:key_exchange(server, anonymous),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+
+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_hashes = Hashes0} = State) ->
+ Msg = rsa_key_exchange(PremasterSecret, PublicKeyInfo),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+
+key_exchange(#state{role = client,
+ connection_states = ConnectionStates0,
+ key_algorithm = Algorithm,
+ public_key_info = PublicKeyInfo,
+ negotiated_version = Version,
+ diffie_hellman_params = Params,
+ own_cert = Cert,
+ socket = Socket, transport_cb = Transport,
+ tls_handshake_hashes = Hashes0} = State)
+ when Algorithm == dhe_dss;
+ Algorithm == dhe_dss_export;
+ Algorithm == dhe_rsa;
+ Algorithm == dhe_rsa_export ->
+ Msg = dh_key_exchange(Cert, Params, PublicKeyInfo),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1}.
+
+rsa_key_exchange(PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption ->
+ ssl_handshake:key_exchange(client,
+ {premaster_secret, PremasterSecret,
+ PublicKeyInfo});
+
+rsa_key_exchange(_, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
+
+dh_key_exchange(OwnCert, Params, PublicKeyInfo) ->
+ case public_key:pkix_is_fixed_dh_cert(OwnCert) of
+ true ->
+ ssl_handshake:key_exchange(client, fixed_diffie_hellman);
+ false ->
+ ssl_handshake:key_exchange(client, {dh, Params, PublicKeyInfo})
+ end.
+
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
+ connection_states = ConnectionStates0,
+ cert_db_ref = CertDbRef,
+ tls_handshake_hashes = Hashes0,
+ negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport} = State) ->
+ Msg = ssl_handshake:certificate_request(ConnectionStates0, CertDbRef),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{client_certificate_requested = true,
+ connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
+ State) ->
+ State.
+
+finalize_client_handshake(#state{connection_states = ConnectionStates0}
+ = State) ->
+ ConnectionStates1 =
+ cipher_protocol(State#state{connection_states =
+ ConnectionStates0}),
+ ConnectionStates2 =
+ ssl_record:activate_pending_connection_state(ConnectionStates1,
+ write),
+ finished(State#state{connection_states = ConnectionStates2}).
+
+
+finalize_server_handshake(State) ->
+ ConnectionStates0 = cipher_protocol(State),
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, write),
+ finished(State#state{connection_states = ConnectionStates}).
+
+cipher_protocol(#state{connection_states = ConnectionStates,
+ socket = Socket,
+ negotiated_version = Version,
+ transport_cb = Transport}) ->
+ {BinChangeCipher, NewConnectionStates} =
+ encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates),
+ Transport:send(Socket, BinChangeCipher),
+ NewConnectionStates.
+
+finished(#state{role = Role, socket = Socket, negotiated_version = Version,
+ transport_cb = Transport,
+ session = Session,
+ connection_states = ConnectionStates,
+ tls_handshake_hashes = Hashes}) ->
+ MasterSecret = Session#session.master_secret,
+ Finished = ssl_handshake:finished(Version, Role, MasterSecret, Hashes),
+ {BinFinished, NewConnectionStates, NewHashes} =
+ encode_handshake(Finished, Version, ConnectionStates, Hashes),
+ Transport:send(Socket, BinFinished),
+ {NewConnectionStates, NewHashes}.
+
+handle_server_key(_KeyExchangeMsg, State) ->
+ State.
+handle_clinet_key(_KeyExchangeMsg, State) ->
+ State.
+
+encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
+ ?DBG_TERM(Alert),
+ ssl_record:encode_alert_record(Alert, Version, ConnectionStates).
+
+encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
+ ?DBG_TERM(#change_cipher_spec{}),
+ ssl_record:encode_change_cipher_spec(Version, ConnectionStates).
+
+encode_handshake(HandshakeRec, Version, ConnectionStates, Hashes) ->
+ encode_handshake(HandshakeRec, undefined, Version,
+ ConnectionStates, Hashes).
+
+encode_handshake(HandshakeRec, SigAlg, Version, ConnectionStates0, Hashes0) ->
+ ?DBG_TERM(HandshakeRec),
+ Frag = ssl_handshake:encode_handshake(HandshakeRec, Version, SigAlg),
+ Hashes1 = ssl_handshake:update_hashes(Hashes0, Frag),
+ {E, ConnectionStates1} =
+ ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
+ {E, ConnectionStates1, Hashes1}.
+
+encode_data(Data, Version, ConnectionStates) ->
+ ssl_record:encode_data(Data, Version, ConnectionStates).
+
+decode_alerts(Bin) ->
+ decode_alerts(Bin, []).
+
+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, []).
+
+application_data(Data, #state{user_application = {_Mon, Pid},
+ socket_options = SOpts,
+ bytes_to_read = BytesToRead,
+ from = From,
+ user_data_buffer = Buffer0} = State0) ->
+ Buffer1 = if
+ Buffer0 =:= <<>> -> Data;
+ Data =:= <<>> -> Buffer0;
+ true -> <<Buffer0/binary, Data/binary>>
+ end,
+ case get_data(SOpts, BytesToRead, Buffer1) of
+ {ok, <<>>, Buffer} -> % no reply, we need more data
+ next_record(State0#state{user_data_buffer = Buffer});
+ {ok, ClientData, Buffer} -> % Send data
+ SocketOpt = deliver_app_data(SOpts, ClientData, Pid, From),
+ State = State0#state{user_data_buffer = Buffer,
+ from = undefined,
+ bytes_to_read = 0,
+ socket_options = SocketOpt
+ },
+ if
+ SocketOpt#socket_options.active =:= false ->
+ State; %% Passive mode, wait for active once or recv
+ Buffer =:= <<>> -> %% Active and empty, get more data
+ next_record(State);
+ true -> %% We have more data
+ application_data(<<>>, State)
+ end;
+ {error,_Reason} -> %% Invalid packet in packet mode
+ deliver_packet_error(SOpts, Buffer1, Pid, From),
+ {stop, normal, State0}
+ end.
+
+%% Picks ClientData
+get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
+ when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
+ if
+ Active =/= false orelse BytesToRead =:= 0 ->
+ %% Active true or once, or passive mode recv(0)
+ {ok, Buffer, <<>>};
+ byte_size(Buffer) >= BytesToRead ->
+ %% Passive Mode, recv(Bytes)
+ <<Data:BytesToRead/binary, Rest/binary>> = Buffer,
+ {ok, Data, Rest};
+ true ->
+ %% Passive Mode not enough data
+ {ok, <<>>, Buffer}
+ end;
+get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) ->
+ PacketOpts = [{packet_size, Size}],
+ case erlang:decode_packet(Type, Buffer, PacketOpts) of
+ {more, _} ->
+ {ok, <<>>, Buffer};
+ Decoded ->
+ Decoded
+ end.
+
+deliver_app_data(SO = #socket_options{active=once}, Data, Pid, From) ->
+ send_or_reply(once, Pid, From, format_reply(SO, Data)),
+ SO#socket_options{active=false};
+deliver_app_data(SO= #socket_options{active=Active}, Data, Pid, From) ->
+ send_or_reply(Active, Pid, From, format_reply(SO, Data)),
+ SO.
+
+format_reply(#socket_options{active=false, mode=Mode, header=Header}, Data) ->
+ {ok, format_reply(Mode, Header, Data)};
+format_reply(#socket_options{active=_, mode=Mode, header=Header}, Data) ->
+ {ssl, sslsocket(), format_reply(Mode, Header, Data)}.
+
+deliver_packet_error(SO= #socket_options{active=Active}, Data, Pid, From) ->
+ send_or_reply(Active, Pid, From, format_packet_error(SO, Data)).
+
+format_packet_error(#socket_options{active=false, mode=Mode}, Data) ->
+ {error, {invalid_packet, format_reply(Mode, raw, Data)}};
+format_packet_error(#socket_options{active=_, mode=Mode}, Data) ->
+ {ssl_error, sslsocket(), {invalid_packet, format_reply(Mode, raw, Data)}}.
+
+format_reply(list, _, Data) -> binary_to_list(Data);
+format_reply(binary, 0, Data) -> Data;
+format_reply(binary, raw, Data) -> Data;
+format_reply(binary, N, Data) -> % Header mode
+ <<Header:N/binary, Rest/binary>> = Data,
+ [binary_to_list(Header), Rest].
+
+%% tcp_closed
+send_or_reply(false, _Pid, undefined, _Data) ->
+ Report = io_lib:format("SSL(debug): Unexpected Data ~p ~n",[_Data]),
+ error_logger:error_report(Report),
+ erlang:error({badarg, _Pid, undefined, _Data}),
+ ok;
+send_or_reply(false, _Pid, From, Data) ->
+ gen_fsm:reply(From, Data);
+send_or_reply(_, Pid, _From, Data) ->
+ send_user(Pid, Data).
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+send_user(Pid, Msg) ->
+ Pid ! Msg.
+
+%% %% This is the code for {packet,ssl} removed because it was slower
+%% %% than handling it in erlang.
+%% next_record(#state{socket = Socket,
+%% tls_buffer = [Msg|Rest],
+%% connection_states = ConnectionStates0} = State) ->
+%% Buffer =
+%% case Rest of
+%% [] ->
+%% inet:setopts(Socket, [{active,once}]),
+%% buffer;
+%% _ -> Rest
+%% end,
+%% case Msg of
+%% #ssl_tls{} ->
+%% {Plain, ConnectionStates} =
+%% ssl_record:decode_cipher_text(Msg, ConnectionStates0),
+%% gen_fsm:send_all_state_event(self(), Plain),
+%% State#state{tls_buffer=Buffer, connection_states = ConnectionStates};
+%% {ssl_close, Msg} ->
+%% self() ! Msg,
+%% State#state{tls_buffer=Buffer}
+%% end;
+%% next_record(#state{socket = Socket, tls_buffer = undefined} = State) ->
+%% inet:setopts(Socket, [{active,once}]),
+%% State#state{tls_buffer=continue};
+%% next_record(State) ->
+%% State#state{tls_buffer=continue}.
+
+next_record(#state{tls_cipher_texts = [], socket = Socket} = State) ->
+ inet:setopts(Socket, [{active,once}]),
+ State;
+next_record(#state{tls_cipher_texts = [CT | Rest],
+ connection_states = ConnStates0} = State) ->
+ {Plain, ConnStates} = ssl_record:decode_cipher_text(CT, ConnStates0),
+ gen_fsm:send_all_state_event(self(), Plain),
+ State#state{tls_cipher_texts = Rest, connection_states = ConnStates}.
+
+next_record_if_active(State =
+ #state{socket_options =
+ #socket_options{active = false}}) ->
+ State;
+next_record_if_active(State) ->
+ next_record(State).
+
+register_session(_, _, _, #session{is_resumable = true} = Session) ->
+ Session; %% Already registered
+register_session(client, Host, Port, Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Host, Port, Session),
+ Session;
+register_session(server, _, Port, Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Port, Session),
+ Session.
+
+invalidate_session(client, Host, Port, Session) ->
+ ssl_manager:invalidate_session(Host, Port, Session);
+invalidate_session(server, _, Port, Session) ->
+ ssl_manager:invalidate_session(Port, Session).
+
+initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
+ {CbModule, DataTag, CloseTag}) ->
+ ConnectionStates = ssl_record:init_connection_states(Role),
+
+ 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 = false},
+ transport_cb = CbModule,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ connection_states = ConnectionStates,
+ tls_handshake_buffer = <<>>,
+ tls_record_buffer = <<>>,
+ tls_cipher_texts = [],
+ user_application = {Monitor, User},
+ bytes_to_read = 0,
+ user_data_buffer = <<>>,
+ log_alert = true,
+ session_cache_cb = SessionCacheCb
+ }.
+
+sslsocket(Pid) ->
+ #sslsocket{pid = Pid, fd = new_ssl}.
+
+sslsocket() ->
+ sslsocket(self()).
+
+get_socket_opts(_,[], _, Acc) ->
+ {ok, Acc};
+get_socket_opts(Socket, [mode | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{mode, SockOpts#socket_options.mode} | Acc]);
+get_socket_opts(Socket, [packet | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{packet, SockOpts#socket_options.packet} | Acc]);
+get_socket_opts(Socket, [header | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{header, SockOpts#socket_options.header} | Acc]);
+get_socket_opts(Socket, [active | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{active, SockOpts#socket_options.active} | Acc]);
+get_socket_opts(Socket, [Tag | Tags], SockOpts, Acc) ->
+ case inet:getopts(Socket, [Tag]) of
+ {ok, [Opt]} ->
+ get_socket_opts(Socket, Tags, SockOpts, [Opt | Acc]);
+ {error, Error} ->
+ {error, Error}
+ end.
+
+set_socket_opts(_, [], SockOpts, []) ->
+ SockOpts;
+set_socket_opts(Socket, [], SockOpts, Other) ->
+ %% Set non emulated options
+ inet:setopts(Socket, Other),
+ SockOpts;
+set_socket_opts(Socket, [{mode, Mode}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts, SockOpts#socket_options{mode = Mode}, Other);
+set_socket_opts(Socket, [{packet, Packet}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts,
+ SockOpts#socket_options{packet = Packet}, Other);
+set_socket_opts(Socket, [{header, Header}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts,
+ SockOpts#socket_options{header = Header}, Other);
+set_socket_opts(Socket, [{active, Active}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts, SockOpts, [Opt | Other]).
+
+alert_user(From, Alert, Role) ->
+ alert_user(false, no_pid, From, Alert, Role).
+
+alert_user(false = Active, Pid, From, Alert, Role) ->
+ ReasonCode = ssl_alert:reason_code(Alert, Role),
+ send_or_reply(Active, Pid, From, {error, ReasonCode});
+alert_user(Active, Pid, From, Alert, Role) ->
+ case ssl_alert:reason_code(Alert, Role) of
+ closed ->
+ send_or_reply(Active, Pid, From,
+ {ssl_closed, sslsocket()});
+ ReasonCode ->
+ send_or_reply(Active, Pid, From,
+ {ssl_error, sslsocket(), ReasonCode})
+ end.
+
+log_alert(true, StateName, Alert) ->
+ Txt = ssl_alert:alert_txt(Alert),
+ error_logger:format("SSL: ~p: ~s\n", [StateName, Txt]);
+log_alert(false, _, _) ->
+ ok.
+
+handle_own_alert(Alert, Version, StateName,
+ #state{transport_cb = Transport,
+ socket = Socket,
+ from = User,
+ role = Role,
+ connection_states = ConnectionStates,
+ log_alert = Log}) ->
+ {BinMsg, _} =
+ encode_alert(Alert, Version, ConnectionStates),
+ Transport:send(Socket, BinMsg),
+ log_alert(Log, StateName, Alert),
+ alert_user(User, Alert, Role).
+
+make_premaster_secret({MajVer, MinVer}) ->
+ Rand = crypto:rand_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>.