aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl')
-rw-r--r--lib/ssl/doc/src/notes.xml64
-rw-r--r--lib/ssl/doc/src/ssl.xml2
-rw-r--r--lib/ssl/src/Makefile15
-rw-r--r--lib/ssl/src/dtls.erl79
-rw-r--r--lib/ssl/src/dtls_connection.erl108
-rw-r--r--lib/ssl/src/dtls_connection.hrl51
-rw-r--r--lib/ssl/src/dtls_connection_sup.erl60
-rw-r--r--lib/ssl/src/dtls_handshake.erl28
-rw-r--r--lib/ssl/src/dtls_record.erl127
-rw-r--r--lib/ssl/src/dtls_v1.erl2
-rw-r--r--lib/ssl/src/ssl.app.src12
-rw-r--r--lib/ssl/src/ssl.erl1016
-rw-r--r--lib/ssl/src/ssl_alert.erl13
-rw-r--r--lib/ssl/src/ssl_api.hrl66
-rw-r--r--lib/ssl/src/ssl_cipher.erl2
-rw-r--r--lib/ssl/src/ssl_config.erl156
-rw-r--r--lib/ssl/src/ssl_connection.erl1856
-rw-r--r--lib/ssl/src/ssl_connection.hrl89
-rw-r--r--lib/ssl/src/ssl_dist_sup.erl4
-rw-r--r--lib/ssl/src/ssl_handshake.erl198
-rw-r--r--lib/ssl/src/ssl_internal.hrl58
-rw-r--r--lib/ssl/src/ssl_record.erl38
-rw-r--r--lib/ssl/src/ssl_socket.erl7
-rw-r--r--lib/ssl/src/ssl_srp.hrl7
-rw-r--r--lib/ssl/src/ssl_sup.erl36
-rw-r--r--lib/ssl/src/tls.erl1014
-rw-r--r--lib/ssl/src/tls_connection.erl2936
-rw-r--r--lib/ssl/src/tls_connection.hrl38
-rw-r--r--lib/ssl/src/tls_connection_sup.erl (renamed from lib/ssl/src/ssl_connection_sup.erl)4
-rw-r--r--lib/ssl/src/tls_handshake.erl60
-rw-r--r--lib/ssl/src/tls_record.erl82
-rw-r--r--lib/ssl/test/Makefile1
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl8
-rw-r--r--lib/ssl/test/ssl_certificate_verify_SUITE.erl24
-rw-r--r--lib/ssl/test/ssl_dist_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_to_openssl_SUITE.erl5
-rw-r--r--lib/ssl/vsn.mk2
38 files changed, 4257 insertions, 4017 deletions
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 692d7fd0e1..fb32ccec7b 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -25,7 +25,69 @@
<file>notes.xml</file>
</header>
<p>This document describes the changes made to the SSL application.</p>
- <section><title>SSL 5.3.1</title>
+ <section><title>SSL 5.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Honors the clients advertised support of elliptic curves
+ and no longer sends incorrect elliptic curve extension in
+ server hello.</p>
+ <p>
+ Own Id: OTP-11370</p>
+ </item>
+ <item>
+ <p>
+ Fix initialization of DTLS fragment reassembler, in
+ previously contributed code, for future support of DTLS .
+ Thanks to Andreas Schultz.</p>
+ <p>
+ Own Id: OTP-11376</p>
+ </item>
+ <item>
+ <p>
+ Corrected type error in client_preferred_next_protocols
+ documentation. Thanks to Julien Barbot.</p>
+ <p>
+ Own Id: OTP-11457</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ TLS code has been refactored to prepare for future DTLS
+ support. Also some DTLS code is in place but not yet
+ runnable, some of it contributed by Andreas Schultz and
+ some of it written by the OTP team. Thanks to to Andreas
+ for his participation.</p>
+ <p>
+ Own Id: OTP-11292</p>
+ </item>
+ <item>
+ <p>
+ Remove extraneous dev debug code left in the close
+ function. Thanks to Ken Key.</p>
+ <p>
+ Own Id: OTP-11447</p>
+ </item>
+ <item>
+ <p>
+ Add SSL Server Name Indication (SNI) client support.
+ Thanks to Julien Barbot.</p>
+ <p>
+ Own Id: OTP-11460</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 5.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index f487da3b14..80ef419fb7 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -76,7 +76,7 @@
<seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>.
</p>
- <p> <c>ssloption() = {verify, verify_type()} |
+ <p><marker id="type-ssloption"></marker><c>ssloption() = {verify, verify_type()} |
{verify_fun, {fun(), term()}} |
{fail_if_no_peer_cert, boolean()}
{depth, integer()} |
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 6744e2f256..131b615277 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -55,7 +55,10 @@ MODULES= \
ssl_srp_primes \
tls_connection \
dtls_connection \
- ssl_connection_sup \
+ ssl_config \
+ ssl_connection \
+ tls_connection_sup \
+ dtls_connection_sup \
tls_handshake \
dtls_handshake\
ssl_handshake\
@@ -73,8 +76,9 @@ MODULES= \
ssl_tls_dist_proxy
INTERNAL_HRL_FILES = \
- ssl_alert.hrl ssl_cipher.hrl ssl_handshake.hrl tls_handshake.hrl \
- dtls_handshake.hrl ssl_internal.hrl \
+ ssl_alert.hrl ssl_cipher.hrl \
+ tls_connection.hrl dtls_connection.hrl ssl_connection.hrl \
+ ssl_handshake.hrl tls_handshake.hrl dtls_handshake.hrl ssl_api.hrl ssl_internal.hrl \
ssl_record.hrl tls_record.hrl dtls_record.hrl ssl_srp.hrl
ERL_FILES= \
@@ -148,9 +152,10 @@ $(EBIN)/ssl_alert.$(EMULATOR): ssl_alert.hrl ssl_record.hrl
$(EBIN)/ssl_certificate.$(EMULATOR): ssl_internal.hrl ssl_alert.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl
$(EBIN)/ssl_certificate_db.$(EMULATOR): ssl_internal.hrl ../../public_key/include/public_key.hrl ../../kernel/include/file.hrl
$(EBIN)/ssl_cipher.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/tls_connection.$(EMULATOR): ssl_internal.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/dtls_connection.$(EMULATOR): ssl_internal.hrl dtls_record.hrl ssl_cipher.hrl dtls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/tls_connection.$(EMULATOR): ssl_internal.hrl tls_connection.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/dtls_connection.$(EMULATOR): ssl_internal.hrl dtls_connection.hrl dtls_record.hrl ssl_cipher.hrl dtls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl ssl_connection.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
$(EBIN)/ssl_manager.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl ../../kernel/include/file.hrl
$(EBIN)/ssl_record.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl
$(EBIN)/ssl_session.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
diff --git a/lib/ssl/src/dtls.erl b/lib/ssl/src/dtls.erl
index 013286c9bd..1cad9560b5 100644
--- a/lib/ssl/src/dtls.erl
+++ b/lib/ssl/src/dtls.erl
@@ -19,7 +19,84 @@
%%
-%%% Purpose : API for DTLS.
+%%% Purpose : Reflect DTLS specific API options (fairly simple wrapper at the moment)
+%% First implementation will support DTLS connections only in a "TLS/TCP like way"
-module(dtls).
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+
+-export([connect/2, connect/3, listen/2, accept/1, accept/2,
+ handshake/1, handshake/2, handshake/3]).
+
+%%--------------------------------------------------------------------
+-spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+-spec connect(host() | port(), [connect_option()] | inet:port_number(),
+ timeout() | list()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+-spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+
+%%
+%% Description: Connect to an DTLS server.
+%%--------------------------------------------------------------------
+
+connect(Socket, Options) when is_port(Socket) ->
+ connect(Socket, Options, infinity).
+connect(Socket, SslOptions, Timeout) when is_port(Socket) ->
+ DTLSOpts = [{protocol, dtls} | SslOptions],
+ ssl:connect(Socket, DTLSOpts, Timeout);
+connect(Host, Port, Options) ->
+ connect(Host, Port, Options, infinity).
+connect(Host, Port, Options, Timeout) ->
+ DTLSOpts = [{protocol, dtls} | Options],
+ ssl:connect(Host, Port, DTLSOpts, Timeout).
+
+%%--------------------------------------------------------------------
+-spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
+
+%%
+%% Description: Creates an ssl listen socket.
+%%--------------------------------------------------------------------
+listen(Port, Options) ->
+ DTLSOpts = [{protocol, dtls} | Options],
+ ssl:listen(Port, DTLSOpts).
+
+%%--------------------------------------------------------------------
+-spec accept(#sslsocket{}) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+-spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+%%
+%% Description: Performs transport accept on an ssl listen socket
+%%--------------------------------------------------------------------
+accept(ListenSocket) ->
+ accept(ListenSocket, infinity).
+accept(Socket, Timeout) ->
+ ssl:transport_accept(Socket, Timeout).
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}) -> ok | {error, reason()}.
+-spec handshake(#sslsocket{} | port(), timeout()| [ssl_option()
+ | transport_option()]) ->
+ ok | {ok, #sslsocket{}} | {error, reason()}.
+-spec handshake(port(), [ssl_option()| transport_option()], timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+
+handshake(ListenSocket) ->
+ handshake(ListenSocket, infinity).
+
+handshake(#sslsocket{} = Socket, Timeout) ->
+ ssl:ssl_accept(Socket, Timeout);
+
+handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+ handshake(ListenSocket, SslOptions, infinity).
+
+handshake(Socket, SslOptions, Timeout) when is_port(Socket) ->
+ ssl:ssl_accept(Socket, SslOptions, Timeout).
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index fda488501c..da2e076856 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -18,88 +18,13 @@
%%
-module(dtls_connection).
-%%-behaviour(gen_fsm).
-
-%% -include("dtls_handshake.hrl").
-%% -include("ssl_alert.hrl").
-%% -include("dtls_record.hrl").
-%% -include("ssl_cipher.hrl").
-%% -include("ssl_internal.hrl").
-%% -include("ssl_srp.hrl").
-%% -include_lib("public_key/include/public_key.hrl").
-
-
-%% %% Called by dtls_connection_sup
-%% %%-export([start_link/7]).
-
-%% %% gen_fsm callbacks
-%% -export([init/1, hello/2, certify/2, cipher/2,
-%% abbreviated/2, connection/2, handle_event/3,
-%% handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
-
-%% -record(message_sequences, {
-%% read = 0,
-%% write = 0
-%% }).
-
-%% -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
-%% message_sequences = #message_sequences{},
-%% dtls_packets = [], % Not yet handled decode ssl/tls packets.
-%% dtls_record_buffer, % binary() buffer of incomplete records
-%% dtls_handshake_buffer, % binary() buffer of incomplete handshakes
-%% dtls_handshake_history, % tls_handshake_history()
-%% dtls_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, % atom as defined by cipher_suite
-%% 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()
-%% log_alert, % boolean()
-%% 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}
-%% client_cookie = <<>>
-%% }).
+%% Internal application API
+%%====================================================================
+%% Internal application API
+%%====================================================================
-%% %%====================================================================
-%% %% Internal application API
-%% %%====================================================================
-
%% %%====================================================================
%% %% State functions
@@ -196,32 +121,7 @@
%% {Record, State} = next_record(State2),
%% next_state(hello, hello, Record, State);
-%% hello(Hello = #client_hello{client_version = ClientVersion},
-%% State = #state{connection_states = ConnectionStates0,
-%% port = Port, session = #session{own_certificate = Cert} = Session0,
-%% renegotiation = {Renegotiation, _},
-%% session_cache = Cache,
-%% session_cache_cb = CacheCb,
-%% ssl_options = SslOpts}) ->
-%% case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
-%% ConnectionStates0, Cert}, Renegotiation) of
-%% {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise,
-%% EcPointFormats, EllipticCurves} ->
-%% do_server_hello(Type, ProtocolsToAdvertise,
-%% EcPointFormats, EllipticCurves,
-%% State#state{connection_states = ConnectionStates,
-%% negotiated_version = Version,
-%% session = Session,
-%% client_ecc = {EllipticCurves, EcPointFormats}});
-%% #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().
diff --git a/lib/ssl/src/dtls_connection.hrl b/lib/ssl/src/dtls_connection.hrl
new file mode 100644
index 0000000000..b8dff479d5
--- /dev/null
+++ b/lib/ssl/src/dtls_connection.hrl
@@ -0,0 +1,51 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: SSL/TLS specific state
+%%----------------------------------------------------------------------
+
+-ifndef(dtls_connection).
+-define(dtls_connection, true).
+
+-include("ssl_connection.hrl").
+
+-record(protocol_buffers, {
+ dtls_packets = [] ::[binary()], % Not yet handled decode ssl/tls packets.
+ dtls_record_buffer :: binary(), % Buffer of incomplete records
+ dtls_handshake_buffer :: binary(), % Buffer of incomplete handshakes
+ dtls_cipher_texts :: [binary()],
+ dtls_cipher_texts_next :: [binary()] % Received for Epoch not yet active
+ }).
+
+-record(flight, {
+ last_retransmit,
+ last_read_seq,
+ msl_timer,
+ flight_state,
+ flight_buffer, % buffer of not yet ACKed TLS records
+ }).
+
+-record(message_sequences, {
+ read = 0,
+ write = 0
+ }).
+
+-endif. % -ifdef(dtls_connection).
diff --git a/lib/ssl/src/dtls_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl
new file mode 100644
index 0000000000..9fe545be18
--- /dev/null
+++ b/lib/ssl/src/dtls_connection_sup.erl
@@ -0,0 +1,60 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Supervisor of DTLS connection.
+%%----------------------------------------------------------------------
+-module(dtls_connection_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+-export([start_child/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+start_child(Args) ->
+ supervisor:start_child(?MODULE, Args).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_O) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 0,
+ MaxT = 3600,
+
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {dtls_connection, start_link, []},
+ Restart = temporary, % E.g. should not be restarted
+ Shutdown = 4000,
+ Modules = [dtls_connection],
+ Type = worker,
+
+ ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+ {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 40616a8717..5db2434753 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -21,13 +21,26 @@
-include("dtls_record.hrl").
-include("ssl_internal.hrl").
--export([client_hello/9, hello/3, get_dtls_handshake/2,
+-export([client_hello/8, client_hello/9, hello/3,
+ get_dtls_handshake/2,
dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1,
encode_handshake/4]).
%%====================================================================
%% Internal application API
%%====================================================================
+%%--------------------------------------------------------------------
+-spec client_hello(host(), inet:port_number(), #connection_states{},
+ #ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
+ #client_hello{}.
+%%
+%% Description: Creates a client hello message.
+%%--------------------------------------------------------------------
+client_hello(Host, Port, ConnectionStates, SslOpts,
+ Cache, CacheCb, Renegotiation, OwnCert) ->
+ %% First client hello (two sent in DTLS ) uses empty Cookie
+ client_hello(Host, Port, <<>>, ConnectionStates, SslOpts,
+ Cache, CacheCb, Renegotiation, OwnCert).
%%--------------------------------------------------------------------
-spec client_hello(host(), inet:port_number(), term(), #connection_states{},
@@ -87,11 +100,6 @@ hello(Address, Port,
{reply, HelloVerifyRequest}
end.
-address_to_bin({A,B,C,D}, Port) ->
- <<0:80,16#ffff:16,A,B,C,D,Port:16>>;
-address_to_bin({A,B,C,D,E,F,G,H}, Port) ->
- <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>.
-
%%--------------------------------------------------------------------
encode_handshake(Package, Version, MsgSeq, Mss) ->
{MsgType, Bin} = enc_hs(Package, Version),
@@ -102,7 +110,7 @@ encode_handshake(Package, Version, MsgSeq, Mss) ->
%--------------------------------------------------------------------
-spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | binary()) ->
- {[dtls_handshake()], #ssl_tls{}}.
+ {[dtls_handshake()], #dtls_hs_state{}} | {retransmit, #dtls_hs_state{}}.
%
% Description: Given a DTLS state and new data from ssl_record, collects
% and returns it as a list of handshake messages, also returns a new
@@ -182,7 +190,6 @@ get_dtls_handshake_aux(Version, SeqNo,
get_dtls_handshake_aux(_Version, _SeqNo, <<>>, HsState) ->
{lists:reverse(HsState#dtls_hs_state.completed),
- HsState#dtls_hs_state.highest_record_seq,
HsState#dtls_hs_state{completed = []}}.
dec_dtls_fragment(Version, SeqNo, Type, Length, MessageSeq, MsgBody,
@@ -426,3 +433,8 @@ decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor),
cookie = Cookie};
decode_handshake(Version, Tag, Msg) ->
ssl_handshake:decode_handshake(Version, Tag, Msg).
+
+address_to_bin({A,B,C,D}, Port) ->
+ <<0:80,16#ffff:16,A,B,C,D,Port:16>>;
+address_to_bin({A,B,C,D,E,F,G,H}, Port) ->
+ <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>.
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index f667458a10..b0a7976864 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -40,8 +40,9 @@
%% Protocol version handling
-export([protocol_version/1, lowest_protocol_version/2,
highest_protocol_version/1, supported_protocol_versions/0,
- is_acceptable_version/2, cipher/4, decipher/2]).
+ is_acceptable_version/2]).
+%% DTLS Epoch handling
-export([init_connection_state_seq/2, current_connection_state_epoch/2,
set_connection_state_by_epoch/3, connection_state_by_epoch/3]).
@@ -114,35 +115,44 @@ get_dtls_records_aux(Data, Acc) ->
end.
encode_plain_text(Type, Version, Data,
- #connection_state{
- compression_state = CompS0,
- epoch = Epoch,
- sequence_number = Seq,
- security_parameters=
- #security_parameters{compression_algorithm = CompAlg}
- }= CS0) ->
+ #connection_states{current_write=#connection_state{
+ epoch = Epoch,
+ sequence_number = Seq,
+ compression_state=CompS0,
+ security_parameters=
+ #security_parameters{compression_algorithm=CompAlg}
+ }= WriteState0} = ConnectionStates) ->
{Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
- CS1 = CS0#connection_state{compression_state = CompS1},
- {CipherText, CS2} = cipher(Type, Version, Comp, CS1),
- CTBin = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherText),
- {CTBin, CS2}.
-
-decode_cipher_text(CipherText, ConnnectionStates0) ->
- ReadState0 = ConnnectionStates0#connection_states.current_read,
- #connection_state{compression_state = CompressionS0,
- security_parameters = SecParams} = ReadState0,
+ WriteState1 = WriteState0#connection_state{compression_state = CompS1},
+ MacHash = calc_mac_hash(WriteState1, Type, Version, Epoch, Seq, Comp),
+ {CipherFragment, WriteState} = ssl_record:cipher(Version, Comp, WriteState1, MacHash),
+ CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment),
+ {CipherText, ConnectionStates#connection_states{current_write =
+ WriteState#connection_state{sequence_number = Seq +1}}}.
+
+decode_cipher_text(#ssl_tls{type = Type, version = Version,
+ epoch = Epoch,
+ record_seq = Seq,
+ fragment = CipherFragment} = CipherText,
+ #connection_states{current_read =
+ #connection_state{compression_state = CompressionS0,
+ security_parameters = SecParams} = ReadState0}
+ = ConnnectionStates0) ->
CompressAlg = SecParams#security_parameters.compression_algorithm,
- case decipher(CipherText, ReadState0) of
- {Compressed, ReadState1} ->
- {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg,
- Compressed, CompressionS0),
- ConnnectionStates = ConnnectionStates0#connection_states{
- current_read = ReadState1#connection_state{
- compression_state = CompressionS1}},
- {Plain, ConnnectionStates};
- #alert{} = Alert ->
- Alert
- end.
+ {PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version),
+ CipherFragment, ReadState0),
+ MacHash = calc_mac_hash(Type, Version, Epoch, Seq, PlainFragment, ReadState1),
+ case ssl_record:is_correct_mac(Mac, MacHash) of
+ true ->
+ {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg,
+ PlainFragment, CompressionS0),
+ ConnnectionStates = ConnnectionStates0#connection_states{
+ current_read = ReadState1#connection_state{
+ compression_state = CompressionS1}},
+ {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end.
%%--------------------------------------------------------------------
-spec protocol_version(tls_atom_version() | tls_version()) ->
@@ -286,7 +296,8 @@ connection_state_by_epoch(#connection_states{pending_write = CS}, Epoch, write)
CS.
%%--------------------------------------------------------------------
-spec set_connection_state_by_epoch(#connection_states{},
- #connection_state{}, read | write) -> ok.
+ #connection_state{}, read | write)
+ -> #connection_states{}.
%%
%% Description: Returns the instance of the connection_state record
%% that is defined by the Epoch.
@@ -323,62 +334,14 @@ encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) ->
[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch),
?UINT48(Seq), ?UINT16(Length)>>, Fragment].
-cipher(Type, Version, Fragment, CS0) ->
+calc_mac_hash(#connection_state{mac_secret = MacSecret,
+ security_parameters = #security_parameters{mac_algorithm = MacAlg}},
+ Type, Version, Epoch, SeqNo, Fragment) ->
Length = erlang:iolist_size(Fragment),
- {MacHash, CS1=#connection_state{cipher_state = CipherS0,
- security_parameters=
- #security_parameters{bulk_cipher_algorithm =
- BCA}
- }} =
- hash_and_bump_seqno(CS0, Type, Version, Length, Fragment),
- {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment, Version),
- CS2 = CS1#connection_state{cipher_state=CipherS1},
- {Ciphered, CS2}.
-
-decipher(TLS=#ssl_tls{type=Type, version=Version={254, _},
- epoch = Epoch, record_seq = SeqNo,
- fragment=Fragment}, CS0) ->
- SP = CS0#connection_state.security_parameters,
- BCA = SP#security_parameters.bulk_cipher_algorithm,
- HashSz = SP#security_parameters.hash_size,
- CipherS0 = CS0#connection_state.cipher_state,
- case ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment, Version) of
- {T, Mac, CipherS1} ->
- CS1 = CS0#connection_state{cipher_state = CipherS1},
- TLength = size(T),
- MacHash = hash_with_seqno(CS1, Type, Version, Epoch, SeqNo, TLength, T),
- case ssl_record:is_correct_mac(Mac, MacHash) of
- true ->
- {TLS#ssl_tls{fragment = T}, CS1};
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
- end;
- #alert{} = Alert ->
- Alert
- end.
-
-hash_with_seqno(#connection_state{mac_secret = MacSecret,
- security_parameters =
- SecPars},
- Type, Version = {254, _},
- Epoch, SeqNo, Length, Fragment) ->
- mac_hash(Version,
- SecPars#security_parameters.mac_algorithm,
- MacSecret, (Epoch bsl 48) + SeqNo, Type,
+ NewSeq = (Epoch bsl 48) + SeqNo,
+ mac_hash(Version, MacAlg, MacSecret, NewSeq, Type,
Length, Fragment).
-hash_and_bump_seqno(#connection_state{epoch = Epoch,
- sequence_number = SeqNo,
- mac_secret = MacSecret,
- security_parameters =
- SecPars} = CS0,
- Type, Version = {254, _}, Length, Fragment) ->
- Hash = mac_hash(Version,
- SecPars#security_parameters.mac_algorithm,
- MacSecret, (Epoch bsl 48) + SeqNo, Type,
- Length, Fragment),
- {Hash, CS0#connection_state{sequence_number = SeqNo+1}}.
-
mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
dtls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
Length, Fragment).
diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl
index c12e12e424..6e41641483 100644
--- a/lib/ssl/src/dtls_v1.erl
+++ b/lib/ssl/src/dtls_v1.erl
@@ -28,7 +28,7 @@ suites(Minor) ->
tls_v1:suites(corresponding_minor_tls_version(Minor)).
mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
- tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
+ tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, corresponding_tls_version(Version),
Length, Fragment).
ecc_curves({_Major, Minor}) ->
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 44798f8c12..68ebc49e4a 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -15,18 +15,19 @@
dtls_record,
dtls_v1,
%% API
- tls, %% Future API module
- dtls, %% Future API module
- ssl,
+ ssl, %% Main API
+ tls, %% TLS specific
+ dtls, %% DTLS specific
ssl_session_cache_api,
%% Both TLS/SSL and DTLS
+ ssl_config,
+ ssl_connection,
ssl_handshake,
ssl_record,
ssl_cipher,
ssl_srp_primes,
ssl_alert,
ssl_socket,
- %%ssl_connection,
%% Erlang Distribution over SSL/TLS
inet_tls_dist,
ssl_tls_dist_proxy,
@@ -40,7 +41,8 @@
%% App structure
ssl_app,
ssl_sup,
- ssl_connection_sup
+ tls_connection_sup,
+ dtls_connection_sup
]},
{registered, [ssl_sup, ssl_manager]},
{applications, [crypto, public_key, kernel, stdlib]},
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index dc6898d001..cff842cb2f 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -19,63 +19,38 @@
%%
-%%% Purpose : Backwards compatibility
+%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl
-module(ssl).
+-include("ssl_internal.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Application handling
+-export([start/0, start/1, stop/0, clear_pem_cache/0]).
--export([start/0, start/1, stop/0, transport_accept/1,
- transport_accept/2, ssl_accept/1, ssl_accept/2, ssl_accept/3,
- cipher_suites/0, cipher_suites/1, suite_definition/1,
- close/1, shutdown/2,
- connect/3, connect/2, connect/4, connection_info/1,
- controlling_process/2, listen/2, peername/1, peercert/1,
- recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1,
- versions/0, session_info/1, format_error/1,
- renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1, negotiated_next_protocol/1]).
+%% Socket handling
+-export([connect/3, connect/2, connect/4,
+ listen/2, transport_accept/1, transport_accept/2,
+ ssl_accept/1, ssl_accept/2, ssl_accept/3,
+ controlling_process/2, peername/1, peercert/1, sockname/1,
+ close/1, shutdown/2, recv/2, recv/3, send/2, getopts/2, setopts/2
+ ]).
+%% SSL/TLS protocol handling
+-export([cipher_suites/0, cipher_suites/1, suite_definition/1,
+ connection_info/1, versions/0, session_info/1, format_error/1,
+ renegotiate/1, prf/5, negotiated_next_protocol/1]).
+%% Misc
+-export([random_bytes/1]).
+-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
-include("ssl_cipher.hrl").
-include("ssl_handshake.hrl").
+-include("ssl_srp.hrl").
-include_lib("public_key/include/public_key.hrl").
-%% Visible in API
--export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
- erl_cipher_suite/0, %% From ssl_cipher.hrl
- tls_atom_version/0, %% From ssl_internal.hrl
- prf_random/0, sslsocket/0]).
-
--type sslsocket() :: #sslsocket{}.
--type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
--type socket_connect_option() :: gen_tcp:connect_option().
--type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
--type socket_listen_option() :: gen_tcp:listen_option().
-
--type ssl_option() :: {verify, verify_type()} |
- {verify_fun, {fun(), InitialUserState::term()}} |
- {fail_if_no_peer_cert, boolean()} | {depth, integer()} |
- {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} |
- {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
- {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
- {user_lookup_fun, {fun(), InitialUserState::term()}} |
- {psk_identity, string()} |
- {srp_identity, {string(), string()}} |
- {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
- {reuse_session, fun()} | {hibernate_after, integer()|undefined} |
- {next_protocols_advertised, list(binary())} |
- {client_preferred_next_protocols, binary(), client | server, list(binary())}.
-
--type verify_type() :: verify_none | verify_peer.
--type path() :: string().
--type ciphers() :: [erl_cipher_suite()] |
- string(). % (according to old API)
--type ssl_imp() :: new | old.
-
--type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
- ClosedTag::atom(), ErrTag::atom()}}.
--type prf_random() :: client_random | server_random.
-
%%--------------------------------------------------------------------
-spec start() -> ok | {error, reason()}.
-spec start(permanent | transient | temporary) -> ok | {error, reason()}.
@@ -85,65 +60,249 @@
%% is temporary. see application(3)
%%--------------------------------------------------------------------
start() ->
- tls:start().
+ application:start(crypto),
+ application:start(asn1),
+ application:start(public_key),
+ application:start(ssl).
+
start(Type) ->
- tls:start(Type).
+ application:start(crypto, Type),
+ application:start(asn1),
+ application:start(public_key, Type),
+ application:start(ssl, Type).
+%%--------------------------------------------------------------------
+-spec stop() -> ok.
+%%
+%% Description: Stops the ssl application.
+%%--------------------------------------------------------------------
stop() ->
- tls:stop().
+ application:stop(ssl).
-connect(Socket, SslOptions) ->
- tls:connect(Socket, SslOptions).
+%%--------------------------------------------------------------------
+-spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+-spec connect(host() | port(), [connect_option()] | inet:port_number(),
+ timeout() | list()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+-spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
-connect(Socket, SslOptions0, TimeoutOrOpts) ->
- tls:connect(Socket, SslOptions0, TimeoutOrOpts).
+%%
+%% Description: Connect to an ssl server.
+%%--------------------------------------------------------------------
+connect(Socket, SslOptions) when is_port(Socket) ->
+ connect(Socket, SslOptions, infinity).
+
+connect(Socket, SslOptions0, Timeout) when is_port(Socket) ->
+ {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0,
+ {gen_tcp, tcp, tcp_closed, tcp_error}),
+ EmulatedOptions = emulated_options(),
+ {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions),
+ try handle_options(SslOptions0 ++ SocketValues, client) of
+ {ok, #config{transport_info = CbInfo, ssl = SslOptions, emulated = EmOpts,
+ connection_cb = ConnectionCb}} ->
+
+ ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()),
+ case ssl_socket:peername(Transport, Socket) of
+ {ok, {Address, Port}} ->
+ ssl_connection:connect(ConnectionCb, Address, Port, Socket,
+ {SslOptions, EmOpts},
+ self(), CbInfo, Timeout);
+ {error, Error} ->
+ {error, Error}
+ end
+ catch
+ _:{error, Reason} ->
+ {error, Reason}
+ end;
+
+connect(Host, Port, Options) ->
+ connect(Host, Port, Options, infinity).
connect(Host, Port, Options, Timeout) ->
- tls:connect(Host, Port, Options, Timeout).
+ try handle_options(Options, client) of
+ {ok, Config} ->
+ do_connect(Host,Port,Config,Timeout)
+ catch
+ throw:Error ->
+ Error
+ end.
-listen(Port, Options) ->
- tls:listen(Port, Options).
+%%--------------------------------------------------------------------
+-spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Creates an ssl listen socket.
+%%--------------------------------------------------------------------
+listen(_Port, []) ->
+ {error, nooptions};
+listen(Port, Options0) ->
+ try
+ {ok, Config} = handle_options(Options0, server),
+ ConnectionCb = connection_cb(Options0),
+ #config{transport_info = {Transport, _, _, _}, inet_user = Options, connection_cb = ConnectionCb} = Config,
+ case Transport:listen(Port, Options) of
+ {ok, ListenSocket} ->
+ {ok, #sslsocket{pid = {ListenSocket, Config}}};
+ Err = {error, _} ->
+ Err
+ end
+ catch
+ Error = {error, _} ->
+ Error
+ end.
+%%--------------------------------------------------------------------
+-spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+-spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+%%
+%% Description: Performs transport accept on an ssl listen socket
+%%--------------------------------------------------------------------
transport_accept(ListenSocket) ->
- tls:transport_accept(ListenSocket).
+ transport_accept(ListenSocket, infinity).
+
+transport_accept(#sslsocket{pid = {ListenSocket,
+ #config{transport_info = CbInfo,
+ connection_cb = ConnectionCb,
+ ssl = SslOpts}}}, Timeout) ->
+ %% The setopt could have been invoked on the listen socket
+ %% and options should be inherited.
+ EmOptions = emulated_options(),
+ {Transport,_,_, _} = CbInfo,
+ {ok, SocketValues} = ssl_socket:getopts(Transport, ListenSocket, EmOptions),
+ ok = ssl_socket:setopts(Transport, ListenSocket, internal_inet_values()),
+ case Transport:accept(ListenSocket, Timeout) of
+ {ok, Socket} ->
+ ok = ssl_socket:setopts(Transport, ListenSocket, SocketValues),
+ {ok, Port} = ssl_socket:port(Transport, Socket),
+ ConnArgs = [server, "localhost", Port, Socket,
+ {SslOpts, socket_options(SocketValues)}, self(), CbInfo],
+ ConnectionSup = connection_sup(ConnectionCb),
+ case ConnectionSup:start_child(ConnArgs) of
+ {ok, Pid} ->
+ ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport);
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
-transport_accept(ListenSocket, Timeout) ->
- tls:transport_accept(ListenSocket, Timeout).
-
+%%--------------------------------------------------------------------
+-spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}.
+-spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option()
+ | transport_option()]) ->
+ ok | {ok, #sslsocket{}} | {error, reason()}.
+-spec ssl_accept(port(), [ssl_option()| transport_option()], timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
ssl_accept(ListenSocket) ->
- tls:ssl_accept(ListenSocket, infinity).
+ ssl_accept(ListenSocket, infinity).
ssl_accept(#sslsocket{} = Socket, Timeout) ->
- tls:ssl_accept(Socket, Timeout);
+ ssl_connection:handshake(Socket, Timeout);
ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
- tls:ssl_accept(ListenSocket, SslOptions, infinity).
+ ssl_accept(ListenSocket, SslOptions, infinity).
ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
- tls:ssl_accept(Socket, SslOptions, Timeout).
+ {Transport,_,_,_} =
+ proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}),
+ EmulatedOptions = emulated_options(),
+ {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions),
+ ConnetionCb = connection_cb(SslOptions),
+ try handle_options(SslOptions ++ SocketValues, server) of
+ {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
+ ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()),
+ {ok, Port} = ssl_socket:port(Transport, Socket),
+ ssl_connection:ssl_accept(ConnetionCb, Port, Socket,
+ {SslOpts, EmOpts},
+ self(), CbInfo, Timeout)
+ catch
+ Error = {error, _Reason} -> Error
+ end.
-close(Socket) ->
- tls:close(Socket).
+%%--------------------------------------------------------------------
+-spec close(#sslsocket{}) -> term().
+%%
+%% Description: Close an ssl connection
+%%--------------------------------------------------------------------
+close(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+ ssl_connection:close(Pid);
+close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}) ->
+ Transport:close(ListenSocket).
-send(Socket, Data) ->
- tls:send(Socket, Data).
+%%--------------------------------------------------------------------
+-spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}.
+%%
+%% Description: Sends data over the ssl connection
+%%--------------------------------------------------------------------
+send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) ->
+ ssl_connection:send(Pid, Data);
+send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}}}}, Data) ->
+ Transport:send(ListenSocket, Data). %% {error,enotconn}
+%%--------------------------------------------------------------------
+-spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
+-spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
+%%
+%% Description: Receives data when active = false
+%%--------------------------------------------------------------------
recv(Socket, Length) ->
- tls:recv(Socket, Length, infinity).
-recv(Socket, Length, Timeout) ->
- tls:recv(Socket, Length, Timeout).
+ recv(Socket, Length, infinity).
+recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid) ->
+ ssl_connection:recv(Pid, Length, Timeout);
+recv(#sslsocket{pid = {Listen,
+ #config{transport_info = {Transport, _, _, _}}}}, _,_) when is_port(Listen)->
+ Transport:recv(Listen, 0). %% {error,enotconn}
-controlling_process(Socket, NewOwner) ->
- tls:controlling_process(Socket, NewOwner).
-
-connection_info(Socket) ->
- tls:connection_info(Socket).
+%%--------------------------------------------------------------------
+-spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}.
+%%
+%% Description: Changes process that receives the messages when active = true
+%% or once.
+%%--------------------------------------------------------------------
+controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
+ ssl_connection:new_user(Pid, NewOwner);
+controlling_process(#sslsocket{pid = {Listen,
+ #config{transport_info = {Transport, _, _, _}}}},
+ NewOwner) when is_port(Listen),
+ is_pid(NewOwner) ->
+ Transport:controlling_process(Listen, NewOwner).
+
+%%--------------------------------------------------------------------
+-spec connection_info(#sslsocket{}) -> {ok, {tls_atom_version(), erl_cipher_suite()}} |
+ {error, reason()}.
+%%
+%% Description: Returns ssl protocol and cipher used for the connection
+%%--------------------------------------------------------------------
+connection_info(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+ ssl_connection:info(Pid);
+connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
+ {error, enotconn}.
-peername(Socket) ->
- tls:peername(Socket).
+%%--------------------------------------------------------------------
+-spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
+%%
+%% Description: same as inet:peername/1.
+%%--------------------------------------------------------------------
+peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid)->
+ ssl_socket:peername(Transport, Socket);
+peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}) ->
+ ssl_socket:peername(Transport, ListenSocket). %% Will return {error, enotconn}
+%%--------------------------------------------------------------------
+-spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}.
+%%
+%% Description: Returns the peercert.
+%%--------------------------------------------------------------------
peercert(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- case tls_connection:peer_certificate(Pid) of
+ case ssl_connection:peer_certificate(Pid) of
{ok, undefined} ->
{error, no_peercert};
Result ->
@@ -152,20 +311,30 @@ peercert(#sslsocket{pid = Pid}) when is_pid(Pid) ->
peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
{error, enotconn}.
+%%--------------------------------------------------------------------
+-spec suite_definition(cipher_suite()) -> erl_cipher_suite().
+%%
+%% Description: Return erlang cipher suite definition.
+%%--------------------------------------------------------------------
suite_definition(S) ->
{KeyExchange, Cipher, Hash, _} = ssl_cipher:suite_definition(S),
{KeyExchange, Cipher, Hash}.
+%%--------------------------------------------------------------------
+-spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}.
+%%
+%% Description: Returns the next protocol that has been negotiated. If no
+%% protocol has been negotiated will return {error, next_protocol_not_negotiated}
+%%--------------------------------------------------------------------
negotiated_next_protocol(#sslsocket{pid = Pid}) ->
- tls_connection:negotiated_next_protocol(Pid).
+ ssl_connection:negotiated_next_protocol(Pid).
-%%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
-spec cipher_suites() -> [erl_cipher_suite()].
--spec cipher_suites(erlang | openssl | all ) -> [erl_cipher_suite()] | [string()].
+-spec cipher_suites(erlang | openssl | all) -> [erl_cipher_suite()] | [string()].
%% Description: Returns all supported cipher suites.
%%--------------------------------------------------------------------
-
cipher_suites() ->
cipher_suites(erlang).
@@ -176,7 +345,6 @@ cipher_suites(erlang) ->
cipher_suites(openssl) ->
Version = tls_record:highest_protocol_version([]),
[ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)];
-
cipher_suites(all) ->
Version = tls_record:highest_protocol_version([]),
Supported = ssl_cipher:suites(Version)
@@ -185,37 +353,681 @@ cipher_suites(all) ->
++ ssl_cipher:srp_suites(),
[suite_definition(S) || S <- Supported].
-getopts(Socket, OptionTags) ->
- tls:getopts(Socket, OptionTags).
+%%--------------------------------------------------------------------
+-spec getopts(#sslsocket{}, [gen_tcp:option_name()]) ->
+ {ok, [gen_tcp:option()]} | {error, reason()}.
+%%
+%% Description: Gets options
+%%--------------------------------------------------------------------
+getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) ->
+ ssl_connection:get_opts(Pid, OptionTags);
+getopts(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}},
+ OptionTags) when is_list(OptionTags) ->
+ try ssl_socket:getopts(Transport, ListenSocket, OptionTags) of
+ {ok, _} = Result ->
+ Result;
+ {error, InetError} ->
+ {error, {options, {socket_options, OptionTags, InetError}}}
+ catch
+ _:_ ->
+ {error, {options, {socket_options, OptionTags}}}
+ end;
+getopts(#sslsocket{}, OptionTags) ->
+ {error, {options, {socket_options, OptionTags}}}.
-setopts(Socket, Options) ->
- tls:setopts(Socket, Options).
+%%--------------------------------------------------------------------
+-spec setopts(#sslsocket{}, [gen_tcp:option()]) -> ok | {error, reason()}.
+%%
+%% Description: Sets options
+%%--------------------------------------------------------------------
+setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) ->
+ try proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Options0) of
+ Options ->
+ ssl_connection:set_opts(Pid, Options)
+ catch
+ _:_ ->
+ {error, {options, {not_a_proplist, Options0}}}
+ end;
-shutdown(Socket, How) ->
- tls:shutdown(Socket, How).
+setopts(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}, Options) when is_list(Options) ->
+ try ssl_socket:setopts(Transport, ListenSocket, Options) of
+ ok ->
+ ok;
+ {error, InetError} ->
+ {error, {options, {socket_options, Options, InetError}}}
+ catch
+ _:Error ->
+ {error, {options, {socket_options, Options, Error}}}
+ end;
+setopts(#sslsocket{}, Options) ->
+ {error, {options,{not_a_proplist, Options}}}.
-sockname(Socket) ->
- tls:sockname(Socket).
+%%---------------------------------------------------------------
+-spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}.
+%%
+%% Description: Same as gen_tcp:shutdown/2
+%%--------------------------------------------------------------------
+shutdown(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}},
+ How) when is_port(Listen) ->
+ Transport:shutdown(Listen, How);
+shutdown(#sslsocket{pid = Pid}, How) ->
+ ssl_connection:shutdown(Pid, How).
+%%--------------------------------------------------------------------
+-spec sockname(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
+%%
+%% Description: Same as inet:sockname/1
+%%--------------------------------------------------------------------
+sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}}) when is_port(Listen) ->
+ ssl_socket:sockname(Transport, Listen);
+
+sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid) ->
+ ssl_socket:sockname(Transport, Socket).
+
+%%---------------------------------------------------------------
+-spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Returns list of session info currently [{session_id, session_id(),
+%% {cipher_suite, cipher_suite()}]
+%%--------------------------------------------------------------------
session_info(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- tls_connection:session_info(Pid);
+ ssl_connection:session_info(Pid);
session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
{error, enotconn}.
+%%---------------------------------------------------------------
+-spec versions() -> [{ssl_app, string()} | {supported, [tls_atom_version()]} |
+ {available, [tls_atom_version()]}].
+%%
+%% Description: Returns a list of relevant versions.
+%%--------------------------------------------------------------------
versions() ->
- tls:versions().
+ Vsns = tls_record:supported_protocol_versions(),
+ SupportedVsns = [tls_record:protocol_version(Vsn) || Vsn <- Vsns],
+ AvailableVsns = ?ALL_SUPPORTED_VERSIONS,
+ %% TODO Add DTLS versions when supported
+ [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}].
+
-renegotiate(Socket) ->
- tls:renegotiate(Socket).
+%%---------------------------------------------------------------
+-spec renegotiate(#sslsocket{}) -> ok | {error, reason()}.
+%%
+%% Description: Initiates a renegotiation.
+%%--------------------------------------------------------------------
+renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+ ssl_connection:renegotiation(Pid);
+renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
+ {error, enotconn}.
-prf(Socket, Secret, Label, Seed, WantedLength) ->
- tls:prf(Socket, Secret, Label, Seed, WantedLength).
+%%--------------------------------------------------------------------
+-spec prf(#sslsocket{}, binary() | 'master_secret', binary(),
+ binary() | prf_random(), non_neg_integer()) ->
+ {ok, binary()} | {error, reason()}.
+%%
+%% Description: use a ssl sessions TLS PRF to generate key material
+%%--------------------------------------------------------------------
+prf(#sslsocket{pid = Pid},
+ Secret, Label, Seed, WantedLength) when is_pid(Pid) ->
+ ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength);
+prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) ->
+ {error, enotconn}.
+%%--------------------------------------------------------------------
+-spec clear_pem_cache() -> ok.
+%%
+%% Description: Clear the PEM cache
+%%--------------------------------------------------------------------
clear_pem_cache() ->
- tls:clear_pem_cache().
+ ssl_manager:clear_pem_cache().
-format_error(Error) ->
- tls:format_error(Error).
+%%---------------------------------------------------------------
+-spec format_error({error, term()}) -> list().
+%%
+%% Description: Creates error string.
+%%--------------------------------------------------------------------
+format_error({error, Reason}) ->
+ format_error(Reason);
+format_error(Reason) when is_list(Reason) ->
+ Reason;
+format_error(closed) ->
+ "TLS connection is closed";
+format_error({tls_alert, Description}) ->
+ "TLS Alert: " ++ Description;
+format_error({options,{FileType, File, Reason}}) when FileType == cacertfile;
+ FileType == certfile;
+ FileType == keyfile;
+ FileType == dhfile ->
+ Error = file_error_format(Reason),
+ file_desc(FileType) ++ File ++ ": " ++ Error;
+format_error({options, {socket_options, Option, Error}}) ->
+ lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, format_error(Error)]));
+format_error({options, {socket_options, Option}}) ->
+ lists:flatten(io_lib:format("Invalid socket option: ~p", [Option]));
+format_error({options, Options}) ->
+ lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options]));
+format_error(Error) ->
+ case inet:format_error(Error) of
+ "unknown POSIX" ++ _ ->
+ unexpected_format(Error);
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+-spec random_bytes(integer()) -> binary().
+
+%%
+%% Description: Generates cryptographically secure random sequence if possible
+%% fallbacks on pseudo random function
+%%--------------------------------------------------------------------
random_bytes(N) ->
- tls:random_bytes(N).
+ try crypto:strong_rand_bytes(N) of
+ RandBytes ->
+ RandBytes
+ catch
+ error:low_entropy ->
+ crypto:rand_bytes(N)
+ end.
+
+%%%--------------------------------------------------------------
+%%% Internal functions
+%%%--------------------------------------------------------------------
+do_connect(Address, Port,
+ #config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts,
+ emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb},
+ Timeout) ->
+ {Transport, _, _, _} = CbInfo,
+ try Transport:connect(Address, Port, SocketOpts, Timeout) of
+ {ok, Socket} ->
+ ssl_connection:connect(ConnetionCb, Address, Port, Socket, {SslOpts,EmOpts},
+ self(), CbInfo, Timeout);
+ {error, Reason} ->
+ {error, Reason}
+ catch
+ exit:{function_clause, _} ->
+ {error, {options, {cb_info, CbInfo}}};
+ exit:badarg ->
+ {error, {options, {socket_options, UserOpts}}};
+ exit:{badarg, _} ->
+ {error, {options, {socket_options, UserOpts}}}
+ end.
+
+handle_options(Opts0, _Role) ->
+ Opts = proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Opts0),
+ ReuseSessionFun = fun(_, _, _, _) -> true end,
+
+ DefaultVerifyNoneFun =
+ {fun(_,{bad_cert, _}, UserState) ->
+ {valid, UserState};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, []},
+
+ VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun),
+
+ UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false),
+ UserVerifyFun = handle_option(verify_fun, Opts, undefined),
+ CaCerts = handle_option(cacerts, Opts, undefined),
+
+ {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} =
+ %% Handle 0, 1, 2 for backwards compatibility
+ case proplists:get_value(verify, Opts, verify_none) of
+ 0 ->
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
+ 1 ->
+ {verify_peer, false,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
+ 2 ->
+ {verify_peer, true,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
+ verify_none ->
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
+ verify_peer ->
+ {verify_peer, UserFailIfNoPeerCert,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
+ Value ->
+ throw({error, {options, {verify, Value}}})
+ end,
+
+ CertFile = handle_option(certfile, Opts, <<>>),
+
+ Versions = case handle_option(versions, Opts, []) of
+ [] ->
+ tls_record:supported_protocol_versions();
+ Vsns ->
+ [tls_record:protocol_version(Vsn) || Vsn <- Vsns]
+ end,
+
+ SSLOptions = #ssl_options{
+ versions = Versions,
+ verify = validate_option(verify, Verify),
+ verify_fun = VerifyFun,
+ fail_if_no_peer_cert = FailIfNoPeerCert,
+ verify_client_once = handle_option(verify_client_once, Opts, false),
+ depth = handle_option(depth, Opts, 1),
+ cert = handle_option(cert, Opts, undefined),
+ certfile = CertFile,
+ key = handle_option(key, Opts, undefined),
+ keyfile = handle_option(keyfile, Opts, CertFile),
+ password = handle_option(password, Opts, ""),
+ cacerts = CaCerts,
+ cacertfile = handle_option(cacertfile, Opts, CaCertDefault),
+ dh = handle_option(dh, Opts, undefined),
+ dhfile = handle_option(dhfile, Opts, undefined),
+ user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined),
+ psk_identity = handle_option(psk_identity, Opts, undefined),
+ srp_identity = handle_option(srp_identity, Opts, undefined),
+ ciphers = handle_option(ciphers, Opts, []),
+ %% Server side option
+ reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
+ reuse_sessions = handle_option(reuse_sessions, Opts, true),
+ secure_renegotiate = handle_option(secure_renegotiate, Opts, false),
+ renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT),
+ hibernate_after = handle_option(hibernate_after, Opts, undefined),
+ erl_dist = handle_option(erl_dist, Opts, false),
+ next_protocols_advertised =
+ handle_option(next_protocols_advertised, Opts, undefined),
+ next_protocol_selector =
+ make_next_protocol_selector(
+ handle_option(client_preferred_next_protocols, Opts, undefined)),
+ log_alert = handle_option(log_alert, Opts, true)
+ },
+
+ CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
+ SslOptions = [protocol, versions, verify, verify_fun,
+ fail_if_no_peer_cert, verify_client_once,
+ depth, cert, certfile, key, keyfile,
+ password, cacerts, cacertfile, dh, dhfile,
+ user_lookup_fun, psk_identity, srp_identity, ciphers,
+ reuse_session, reuse_sessions, ssl_imp,
+ cb_info, renegotiate_at, secure_renegotiate, hibernate_after,
+ erl_dist, next_protocols_advertised,
+ client_preferred_next_protocols, log_alert],
+
+ SockOpts = lists:foldl(fun(Key, PropList) ->
+ proplists:delete(Key, PropList)
+ end, Opts, SslOptions),
+
+ {SSLsock, Emulated} = emulated_options(SockOpts),
+ ConnetionCb = connection_cb(Opts),
+
+ {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = SSLsock,
+ inet_user = SockOpts, transport_info = CbInfo, connection_cb = ConnetionCb
+ }}.
+
+handle_option(OptionName, Opts, Default) ->
+ validate_option(OptionName,
+ proplists:get_value(OptionName, Opts, Default)).
+
+
+validate_option(versions, Versions) ->
+ validate_versions(Versions, Versions);
+validate_option(verify, Value)
+ when Value == verify_none; Value == verify_peer ->
+ Value;
+validate_option(verify_fun, undefined) ->
+ undefined;
+%% Backwards compatibility
+validate_option(verify_fun, Fun) when is_function(Fun) ->
+ {fun(_,{bad_cert, _} = Reason, OldFun) ->
+ case OldFun([Reason]) of
+ true ->
+ {valid, OldFun};
+ false ->
+ {fail, Reason}
+ end;
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, Fun};
+validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) ->
+ Value;
+validate_option(fail_if_no_peer_cert, Value)
+ when Value == true; Value == false ->
+ Value;
+validate_option(verify_client_once, Value)
+ when Value == true; Value == false ->
+ Value;
+validate_option(depth, Value) when is_integer(Value),
+ Value >= 0, Value =< 255->
+ Value;
+validate_option(cert, Value) when Value == undefined;
+ is_binary(Value) ->
+ Value;
+validate_option(certfile, undefined = Value) ->
+ Value;
+validate_option(certfile, Value) when is_binary(Value) ->
+ Value;
+validate_option(certfile, Value) when is_list(Value) ->
+ list_to_binary(Value);
+
+validate_option(key, undefined) ->
+ undefined;
+validate_option(key, {KeyType, Value}) when is_binary(Value),
+ KeyType == rsa; %% Backwards compatibility
+ KeyType == dsa; %% Backwards compatibility
+ KeyType == 'RSAPrivateKey';
+ KeyType == 'DSAPrivateKey';
+ KeyType == 'PrivateKeyInfo' ->
+ {KeyType, Value};
+
+validate_option(keyfile, undefined) ->
+ <<>>;
+validate_option(keyfile, Value) when is_binary(Value) ->
+ Value;
+validate_option(keyfile, Value) when is_list(Value), Value =/= "" ->
+ list_to_binary(Value);
+validate_option(password, Value) when is_list(Value) ->
+ Value;
+
+validate_option(cacerts, Value) when Value == undefined;
+ is_list(Value) ->
+ Value;
+%% certfile must be present in some cases otherwhise it can be set
+%% to the empty string.
+validate_option(cacertfile, undefined) ->
+ <<>>;
+validate_option(cacertfile, Value) when is_binary(Value) ->
+ Value;
+validate_option(cacertfile, Value) when is_list(Value), Value =/= ""->
+ list_to_binary(Value);
+validate_option(dh, Value) when Value == undefined;
+ is_binary(Value) ->
+ Value;
+validate_option(dhfile, undefined = Value) ->
+ Value;
+validate_option(dhfile, Value) when is_binary(Value) ->
+ Value;
+validate_option(dhfile, Value) when is_list(Value), Value =/= "" ->
+ list_to_binary(Value);
+validate_option(psk_identity, undefined) ->
+ undefined;
+validate_option(psk_identity, Identity)
+ when is_list(Identity), Identity =/= "", length(Identity) =< 65535 ->
+ list_to_binary(Identity);
+validate_option(user_lookup_fun, undefined) ->
+ undefined;
+validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) ->
+ Value;
+validate_option(srp_identity, undefined) ->
+ undefined;
+validate_option(srp_identity, {Username, Password})
+ when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 ->
+ {list_to_binary(Username), list_to_binary(Password)};
+
+validate_option(ciphers, Value) when is_list(Value) ->
+ Version = tls_record:highest_protocol_version([]),
+ try cipher_suites(Version, Value)
+ catch
+ exit:_ ->
+ throw({error, {options, {ciphers, Value}}});
+ error:_->
+ throw({error, {options, {ciphers, Value}}})
+ end;
+validate_option(reuse_session, Value) when is_function(Value) ->
+ Value;
+validate_option(reuse_sessions, Value) when Value == true;
+ Value == false ->
+ Value;
+
+validate_option(secure_renegotiate, Value) when Value == true;
+ Value == false ->
+ Value;
+validate_option(renegotiate_at, Value) when is_integer(Value) ->
+ erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
+
+validate_option(hibernate_after, undefined) ->
+ undefined;
+validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 ->
+ Value;
+validate_option(erl_dist,Value) when Value == true;
+ Value == false ->
+ Value;
+validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value)
+ when is_list(PreferredProtocols) ->
+ case tls_record:highest_protocol_version([]) of
+ {3,0} ->
+ throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
+ _ ->
+ validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
+ validate_npn_ordering(Precedence),
+ {Precedence, PreferredProtocols, ?NO_PROTOCOL}
+ end;
+validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value)
+ when is_list(PreferredProtocols), is_binary(Default),
+ byte_size(Default) > 0, byte_size(Default) < 256 ->
+ case tls_record:highest_protocol_version([]) of
+ {3,0} ->
+ throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
+ _ ->
+ validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
+ validate_npn_ordering(Precedence),
+ Value
+ end;
+
+validate_option(client_preferred_next_protocols, undefined) ->
+ undefined;
+validate_option(log_alert, Value) when Value == true;
+ Value == false ->
+ Value;
+validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) ->
+ case tls_record:highest_protocol_version([]) of
+ {3,0} ->
+ throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
+ _ ->
+ validate_binary_list(next_protocols_advertised, Value),
+ Value
+ end;
+
+validate_option(next_protocols_advertised, undefined) ->
+ undefined;
+validate_option(Opt, Value) ->
+ throw({error, {options, {Opt, Value}}}).
+
+validate_npn_ordering(client) ->
+ ok;
+validate_npn_ordering(server) ->
+ ok;
+validate_npn_ordering(Value) ->
+ throw({error, {options, {client_preferred_next_protocols, {invalid_precedence, Value}}}}).
+
+validate_binary_list(Opt, List) ->
+ lists:foreach(
+ fun(Bin) when is_binary(Bin),
+ byte_size(Bin) > 0,
+ byte_size(Bin) < 256 ->
+ ok;
+ (Bin) ->
+ throw({error, {options, {Opt, {invalid_protocol, Bin}}}})
+ end, List).
+
+validate_versions([], Versions) ->
+ Versions;
+validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == tlsv1;
+ Version == sslv3 ->
+ validate_versions(Rest, Versions);
+validate_versions([Ver| _], Versions) ->
+ throw({error, {options, {Ver, {versions, Versions}}}}).
+
+validate_inet_option(mode, Value)
+ when Value =/= list, Value =/= binary ->
+ throw({error, {options, {mode,Value}}});
+validate_inet_option(packet, Value)
+ when not (is_atom(Value) orelse is_integer(Value)) ->
+ throw({error, {options, {packet,Value}}});
+validate_inet_option(packet_size, Value)
+ when not is_integer(Value) ->
+ throw({error, {options, {packet_size,Value}}});
+validate_inet_option(header, Value)
+ when not is_integer(Value) ->
+ throw({error, {options, {header,Value}}});
+validate_inet_option(active, Value)
+ when Value =/= true, Value =/= false, Value =/= once ->
+ throw({error, {options, {active,Value}}});
+validate_inet_option(_, _) ->
+ ok.
+
+%% The option cacerts overrides cacertsfile
+ca_cert_default(_,_, [_|_]) ->
+ undefined;
+ca_cert_default(verify_none, _, _) ->
+ undefined;
+ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) ->
+ undefined;
+%% Server that wants to verify_peer and has no verify_fun must have
+%% some trusted certs.
+ca_cert_default(verify_peer, undefined, _) ->
+ "".
+
+emulated_options() ->
+ [mode, packet, active, header, packet_size].
+
+internal_inet_values() ->
+ [{packet_size,0},{packet, 0},{header, 0},{active, false},{mode,binary}].
+
+socket_options(InetValues) ->
+ #socket_options{
+ mode = proplists:get_value(mode, InetValues, lists),
+ header = proplists:get_value(header, InetValues, 0),
+ active = proplists:get_value(active, InetValues, active),
+ packet = proplists:get_value(packet, InetValues, 0),
+ packet_size = proplists:get_value(packet_size, InetValues)
+ }.
+
+emulated_options(Opts) ->
+ emulated_options(Opts, internal_inet_values(), #socket_options{}).
+
+emulated_options([{mode,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(mode,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{mode=Opt});
+emulated_options([{header,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(header,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{header=Opt});
+emulated_options([{active,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(active,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{active=Opt});
+emulated_options([{packet,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(packet,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{packet=Opt});
+emulated_options([{packet_size,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(packet_size,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{packet_size=Opt});
+emulated_options([Opt|Opts], Inet, Emulated) ->
+ emulated_options(Opts, [Opt|Inet], Emulated);
+emulated_options([], Inet,Emulated) ->
+ {Inet, Emulated}.
+
+cipher_suites(Version, []) ->
+ ssl_cipher:suites(Version);
+cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility
+ Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0],
+ cipher_suites(Version, Ciphers);
+cipher_suites(Version, [{_,_,_}| _] = Ciphers0) ->
+ Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0],
+ cipher_suites(Version, Ciphers);
+
+cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
+ Supported0 = ssl_cipher:suites(Version)
+ ++ ssl_cipher:anonymous_suites()
+ ++ ssl_cipher:psk_suites(Version)
+ ++ ssl_cipher:srp_suites(),
+ Supported = ssl_cipher:filter_suites(Supported0),
+ case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of
+ [] ->
+ Supported;
+ Ciphers ->
+ Ciphers
+ end;
+cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) ->
+ %% Format: ["RC4-SHA","RC4-MD5"]
+ Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0],
+ cipher_suites(Version, Ciphers);
+cipher_suites(Version, Ciphers0) ->
+ %% Format: "RC4-SHA:RC4-MD5"
+ Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")],
+ cipher_suites(Version, Ciphers).
+
+unexpected_format(Error) ->
+ lists:flatten(io_lib:format("Unexpected error: ~p", [Error])).
+
+file_error_format({error, Error})->
+ case file:format_error(Error) of
+ "unknown POSIX error" ->
+ "decoding error";
+ Str ->
+ Str
+ end;
+file_error_format(_) ->
+ "decoding error".
+
+file_desc(cacertfile) ->
+ "Invalid CA certificate file ";
+file_desc(certfile) ->
+ "Invalid certificate file ";
+file_desc(keyfile) ->
+ "Invalid key file ";
+file_desc(dhfile) ->
+ "Invalid DH params file ".
+
+detect(_Pred, []) ->
+ undefined;
+detect(Pred, [H|T]) ->
+ case Pred(H) of
+ true ->
+ H;
+ _ ->
+ detect(Pred, T)
+ end.
+
+make_next_protocol_selector(undefined) ->
+ undefined;
+make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) ->
+ fun(AdvertisedProtocols) ->
+ case detect(fun(PreferredProtocol) ->
+ lists:member(PreferredProtocol, AdvertisedProtocols)
+ end, AllProtocols) of
+ undefined ->
+ DefaultProtocol;
+ PreferredProtocol ->
+ PreferredProtocol
+ end
+ end;
+
+make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) ->
+ fun(AdvertisedProtocols) ->
+ case detect(fun(PreferredProtocol) ->
+ lists:member(PreferredProtocol, AllProtocols)
+ end,
+ AdvertisedProtocols) of
+ undefined ->
+ DefaultProtocol;
+ PreferredProtocol ->
+ PreferredProtocol
+ end
+ end.
+
+connection_cb(tls) ->
+ tls_connection;
+connection_cb(dtls) ->
+ dtls_connection;
+connection_cb(Opts) ->
+ connection_cb(proplists:get_value(protocol, Opts, tls)).
+
+connection_sup(tls_connection) ->
+ tls_connection_sup;
+connection_sup(dtls_connection) ->
+ dtls_connection_sup.
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 1810043dfb..5c842b4d19 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -29,12 +29,23 @@
-include("ssl_alert.hrl").
-include("ssl_record.hrl").
+-include("ssl_internal.hrl").
--export([alert_txt/1, reason_code/2]).
+-export([encode/3, alert_txt/1, reason_code/2]).
%%====================================================================
%% Internal application API
%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec encode(#alert{}, tls_version(), #connection_states{}) ->
+ {iolist(), #connection_states{}}.
+%%
+%% Description:
+%%--------------------------------------------------------------------
+encode(#alert{} = Alert, Version, ConnectionStates) ->
+ ssl_record:encode_alert_record(Alert, Version, ConnectionStates).
+
%%--------------------------------------------------------------------
-spec reason_code(#alert{}, client | server) -> closed | {essl, string()}.
%%
diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl
new file mode 100644
index 0000000000..607991750f
--- /dev/null
+++ b/lib/ssl/src/ssl_api.hrl
@@ -0,0 +1,66 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-ifndef(ssl_api).
+-define(ssl_api, true).
+
+-include("ssl_cipher.hrl").
+
+%% Visible in API
+-export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
+ erl_cipher_suite/0, %% From ssl_cipher.hrl
+ tls_atom_version/0, %% From ssl_internal.hrl
+ prf_random/0, sslsocket/0]).
+
+
+%% Looks like it does for backwards compatibility reasons
+-record(sslsocket, {fd = nil, pid = nil}).
+
+
+-type sslsocket() :: #sslsocket{}.
+-type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
+-type socket_connect_option() :: gen_tcp:connect_option().
+-type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
+-type socket_listen_option() :: gen_tcp:listen_option().
+
+-type ssl_option() :: {verify, verify_type()} |
+ {verify_fun, {fun(), InitialUserState::term()}} |
+ {fail_if_no_peer_cert, boolean()} | {depth, integer()} |
+ {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} |
+ {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
+ {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
+ {user_lookup_fun, {fun(), InitialUserState::term()}} |
+ {psk_identity, string()} |
+ {srp_identity, {string(), string()}} |
+ {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
+ {reuse_session, fun()} | {hibernate_after, integer()|undefined} |
+ {next_protocols_advertised, list(binary())} |
+ {client_preferred_next_protocols, binary(), client | server, list(binary())}.
+
+-type verify_type() :: verify_none | verify_peer.
+-type path() :: string().
+-type ciphers() :: [erl_cipher_suite()] |
+ string(). % (according to old API)
+-type ssl_imp() :: new | old.
+
+-type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
+ ClosedTag::atom(), ErrTag::atom()}}.
+-type prf_random() :: client_random | server_random.
+
+-endif. % -ifdef(ssl_api).
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index e6ed0d8626..b2077c662a 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -72,7 +72,7 @@ security_parameters(Version, CipherSuite, SecParams) ->
hash_size = hash_size(Hash)}.
%%--------------------------------------------------------------------
--spec cipher(cipher_enum(), #cipher_state{}, binary(), binary(), tls_version()) ->
+-spec cipher(cipher_enum(), #cipher_state{}, binary(), iolist(), tls_version()) ->
{binary(), #cipher_state{}}.
%%
%% Description: Encrypts the data and the MAC using chipher described
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
new file mode 100644
index 0000000000..545b8aa0f6
--- /dev/null
+++ b/lib/ssl/src/ssl_config.erl
@@ -0,0 +1,156 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssl_config).
+
+-include("ssl_internal.hrl").
+-include("ssl_connection.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-export([init/2]).
+
+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;
+
+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.
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
new file mode 100644
index 0000000000..b7c1b9e8d0
--- /dev/null
+++ b/lib/ssl/src/ssl_connection.erl
@@ -0,0 +1,1856 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also
+%% tls_connection.erl and dtls_connection.erl
+%%----------------------------------------------------------------------
+
+-module(ssl_connection).
+
+-include("ssl_api.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_srp.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Setup
+-export([connect/8, ssl_accept/7, handshake/2,
+ socket_control/4]).
+
+%% User Events
+-export([send/2, recv/3, close/1, shutdown/2,
+ new_user/2, get_opts/2, set_opts/2, info/1, session_info/1,
+ peer_certificate/1, renegotiation/1, negotiated_next_protocol/1, prf/5
+ ]).
+
+-export([handle_session/6]).
+
+%% SSL FSM state functions
+-export([hello/3, abbreviated/3, certify/3, cipher/3, connection/3]).
+%% SSL all state functions
+-export([handle_sync_event/4, handle_info/3, terminate/3]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec connect(tls_connection | dtls_connection,
+ host(), inet:port_number(), port(), {#ssl_options{}, #socket_options{}},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Connect to an ssl server.
+%%--------------------------------------------------------------------
+connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+%%--------------------------------------------------------------------
+-spec ssl_accept(tls_connection | dtls_connection,
+ inet:port_number(), port(), {#ssl_options{}, #socket_options{}},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}.
+%%
+%% Description: Starts ssl handshake.
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = Pid}, Timeout) ->
+ case sync_send_all_state_event(Pid, {start, Timeout}) of
+ connected ->
+ ok;
+ Error ->
+ Error
+ end.
+%--------------------------------------------------------------------
+-spec socket_control(tls_connection | dtls_connection, port(), pid(), atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Set the ssl process to own the accept socket
+%%--------------------------------------------------------------------
+socket_control(Connection, Socket, Pid, Transport) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, ssl_socket:socket(Pid, Transport, Socket, Connection)};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%%--------------------------------------------------------------------
+-spec send(pid(), iodata()) -> ok | {error, reason()}.
+%%
+%% Description: Sends data over the ssl connection
+%%--------------------------------------------------------------------
+send(Pid, Data) ->
+ sync_send_all_state_event(Pid, {application_data,
+ %% iolist_to_binary should really
+ %% be called iodata_to_binary()
+ erlang:iolist_to_binary(Data)}).
+
+%%--------------------------------------------------------------------
+-spec recv(pid(), integer(), timeout()) ->
+ {ok, binary() | list()} | {error, reason()}.
+%%
+%% Description: Receives data when active = false
+%%--------------------------------------------------------------------
+recv(Pid, Length, Timeout) ->
+ sync_send_all_state_event(Pid, {recv, Length, Timeout}).
+
+%%--------------------------------------------------------------------
+-spec close(pid()) -> ok | {error, reason()}.
+%%
+%% Description: Close an ssl connection
+%%--------------------------------------------------------------------
+close(ConnectionPid) ->
+ case sync_send_all_state_event(ConnectionPid, close) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+-spec shutdown(pid(), atom()) -> ok | {error, reason()}.
+%%
+%% Description: Same as gen_tcp:shutdown/2
+%%--------------------------------------------------------------------
+shutdown(ConnectionPid, How) ->
+ sync_send_all_state_event(ConnectionPid, {shutdown, How}).
+
+%%--------------------------------------------------------------------
+-spec new_user(pid(), pid()) -> ok | {error, reason()}.
+%%
+%% Description: Changes process that receives the messages when active = true
+%% or once.
+%%--------------------------------------------------------------------
+new_user(ConnectionPid, User) ->
+ sync_send_all_state_event(ConnectionPid, {new_user, User}).
+
+%%--------------------------------------------------------------------
+-spec negotiated_next_protocol(pid()) -> {ok, binary()} | {error, reason()}.
+%%
+%% Description: Returns the negotiated protocol
+%%--------------------------------------------------------------------
+negotiated_next_protocol(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, negotiated_next_protocol).
+
+%%--------------------------------------------------------------------
+-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Same as inet:getopts/2
+%%--------------------------------------------------------------------
+get_opts(ConnectionPid, OptTags) ->
+ sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}).
+%%--------------------------------------------------------------------
+-spec set_opts(pid(), list()) -> ok | {error, reason()}.
+%%
+%% Description: Same as inet:setopts/2
+%%--------------------------------------------------------------------
+set_opts(ConnectionPid, Options) ->
+ sync_send_all_state_event(ConnectionPid, {set_opts, Options}).
+
+%%--------------------------------------------------------------------
+-spec info(pid()) -> {ok, {atom(), tuple()}} | {error, reason()}.
+%%
+%% Description: Returns ssl protocol and cipher used for the connection
+%%--------------------------------------------------------------------
+info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, info).
+
+%%--------------------------------------------------------------------
+-spec session_info(pid()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Returns info about the ssl session
+%%--------------------------------------------------------------------
+session_info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, session_info).
+
+%%--------------------------------------------------------------------
+-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
+%%
+%% Description: Returns the peer cert
+%%--------------------------------------------------------------------
+peer_certificate(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, peer_certificate).
+
+%%--------------------------------------------------------------------
+-spec renegotiation(pid()) -> ok | {error, reason()}.
+%%
+%% Description: Starts a renegotiation of the ssl session.
+%%--------------------------------------------------------------------
+renegotiation(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, renegotiate).
+
+%%--------------------------------------------------------------------
+-spec prf(pid(), binary() | 'master_secret', binary(),
+ binary() | ssl:prf_random(), non_neg_integer()) ->
+ {ok, binary()} | {error, reason()} | {'EXIT', term()}.
+%%
+%% Description: use a ssl sessions TLS PRF to generate key material
+%%--------------------------------------------------------------------
+prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
+ sync_send_all_state_event(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
+
+
+handle_session(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Version, NewId, ConnectionStates, NextProtocol,
+ #state{session = #session{session_id = OldId},
+ negotiated_version = ReqVersion} = State0) ->
+ {KeyAlgorithm, _, _, _} =
+ ssl_cipher:suite_definition(CipherSuite),
+
+ PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
+
+ NewNextProtocol = case NextProtocol of
+ undefined ->
+ State0#state.next_protocol;
+ _ ->
+ NextProtocol
+ end,
+
+ State = State0#state{key_algorithm = KeyAlgorithm,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ premaster_secret = PremasterSecret,
+ expecting_next_protocol_negotiation = NextProtocol =/= undefined,
+ next_protocol = NewNextProtocol},
+
+ case ssl_session:is_new(OldId, NewId) of
+ true ->
+ handle_new_session(NewId, CipherSuite, Compression,
+ State#state{connection_states = ConnectionStates});
+ false ->
+ handle_resumed_session(NewId,
+ State#state{connection_states = ConnectionStates})
+ end.
+
+%%--------------------------------------------------------------------
+-spec hello(start | #hello_request{} | #server_hello{} | term(),
+ #state{}, tls_connection | dtls_connection) ->
+ gen_fsm_state_return().
+%%--------------------------------------------------------------------
+hello(start, #state{role = server} = State0, Connection) ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_state(hello, hello, Record, State);
+
+hello(#hello_request{}, #state{role = client} = State0, Connection) ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_state(hello, hello, Record, State);
+
+hello({common_client_hello, Type, ServerHelloExt, HashSign},
+ #state{session = #session{cipher_suite = CipherSuite},
+ negotiated_version = Version} = State, Connection) ->
+ {KeyAlg, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
+ NegotiatedHashSign = negotiated_hashsign(HashSign, KeyAlg, Version),
+ do_server_hello(Type, ServerHelloExt,
+ State#state{hashsign_algorithm = NegotiatedHashSign}, Connection);
+
+hello(timeout, State, _) ->
+ {next_state, hello, State, hibernate};
+
+hello(Msg, State, Connection) ->
+ Connection:handle_unexpected_message(Msg, hello, State).
+
+%%--------------------------------------------------------------------
+-spec abbreviated(#hello_request{} | #finished{} | term(),
+ #state{}, tls_connection | dtls_connection) ->
+ gen_fsm_state_return().
+%%--------------------------------------------------------------------
+abbreviated(#hello_request{}, State0, Connection) ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_state(abbreviated, hello, Record, State);
+
+abbreviated(#finished{verify_data = Data} = Finished,
+ #state{role = server,
+ negotiated_version = Version,
+ tls_handshake_history = Handshake,
+ session = #session{master_secret = MasterSecret},
+ connection_states = ConnectionStates0} =
+ State, Connection) ->
+ case ssl_handshake:verify_connection(Version, Finished, client,
+ get_current_prf(ConnectionStates0, write),
+ MasterSecret, Handshake) of
+ verified ->
+ ConnectionStates =
+ ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
+ Connection:next_state_connection(abbreviated,
+ ack_connection(
+ State#state{connection_states = ConnectionStates}));
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, abbreviated, State)
+ end;
+
+abbreviated(#finished{verify_data = Data} = Finished,
+ #state{role = client, tls_handshake_history = Handshake0,
+ session = #session{master_secret = MasterSecret},
+ negotiated_version = Version,
+ connection_states = ConnectionStates0} = State0, Connection) ->
+ case ssl_handshake:verify_connection(Version, Finished, server,
+ get_pending_prf(ConnectionStates0, write),
+ MasterSecret, Handshake0) of
+ verified ->
+ ConnectionStates1 =
+ ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
+ State =
+ finalize_handshake(State0#state{connection_states = ConnectionStates1},
+ abbreviated, Connection),
+ Connection:next_state_connection(abbreviated,
+ ack_connection(State));
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, abbreviated, State0)
+ end;
+
+%% only allowed to send next_protocol message after change cipher spec
+%% & before finished message and it is not allowed during renegotiation
+abbreviated(#next_protocol{selected_protocol = SelectedProtocol},
+ #state{role = server, expecting_next_protocol_negotiation = true} = State0,
+ Connection) ->
+ {Record, State} = Connection:next_record(State0#state{next_protocol = SelectedProtocol}),
+ Connection:next_state(abbreviated, abbreviated, Record, State);
+
+abbreviated(timeout, State, _) ->
+ {next_state, abbreviated, State, hibernate };
+
+abbreviated(Msg, State, Connection) ->
+ Connection:handle_unexpected_message(Msg, abbreviated, State).
+
+%%--------------------------------------------------------------------
+-spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} |
+ #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
+ #state{}, tls_connection | dtls_connection) ->
+ gen_fsm_state_return().
+%%--------------------------------------------------------------------
+certify(#hello_request{}, State0, Connection) ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_state(certify, hello, Record, State);
+
+certify(#certificate{asn1_certificates = []},
+ #state{role = server, negotiated_version = Version,
+ ssl_options = #ssl_options{verify = verify_peer,
+ fail_if_no_peer_cert = true}} =
+ State, Connection) ->
+ Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
+ Connection:handle_own_alert(Alert, Version, certify, State);
+
+certify(#certificate{asn1_certificates = []},
+ #state{role = server,
+ ssl_options = #ssl_options{verify = verify_peer,
+ fail_if_no_peer_cert = false}} =
+ State0, Connection) ->
+ {Record, State} = Connection:next_record(State0#state{client_certificate_requested = false}),
+ Connection:next_state(certify, certify, Record, State);
+
+certify(#certificate{} = Cert,
+ #state{negotiated_version = Version,
+ role = Role,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ ssl_options = Opts} = State, Connection) ->
+ case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth,
+ Opts#ssl_options.verify,
+ Opts#ssl_options.verify_fun, Role) of
+ {PeerCert, PublicKeyInfo} ->
+ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
+ State#state{client_certificate_requested = false}, Connection);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State)
+ end;
+
+certify(#server_key_exchange{exchange_keys = Keys},
+ #state{role = client, negotiated_version = Version,
+ key_algorithm = Alg,
+ public_key_info = PubKeyInfo,
+ connection_states = ConnectionStates} = State, Connection)
+ when Alg == dhe_dss; Alg == dhe_rsa;
+ Alg == ecdhe_rsa; Alg == ecdhe_ecdsa;
+ Alg == dh_anon; Alg == ecdh_anon;
+ Alg == psk; Alg == dhe_psk; Alg == rsa_psk;
+ Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
+
+ Params = ssl_handshake:decode_server_key(Keys, Alg, Version),
+ HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, Version),
+ case is_anonymous(Alg) of
+ true ->
+ calculate_secret(Params#server_key_params.params,
+ State#state{hashsign_algorithm = HashSign}, Connection);
+ false ->
+ case ssl_handshake:verify_server_key(Params, HashSign, ConnectionStates, Version, PubKeyInfo) of
+ true ->
+ calculate_secret(Params#server_key_params.params,
+ State#state{hashsign_algorithm = HashSign}, Connection);
+ false ->
+ ?ALERT_REC(?FATAL, ?DECRYPT_ERROR)
+ end
+ end;
+
+certify(#server_key_exchange{} = Msg,
+ #state{role = client, key_algorithm = rsa} = State, Connection) ->
+ Connection:handle_unexpected_message(Msg, certify_server_keyexchange, State);
+
+certify(#certificate_request{hashsign_algorithms = HashSigns},
+ #state{session = #session{own_certificate = Cert}} = State0, Connection) ->
+ HashSign = ssl_handshake:select_hashsign(HashSigns, Cert),
+ {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
+ Connection:next_state(certify, certify, Record,
+ State#state{cert_hashsign_algorithm = HashSign});
+
+%% PSK and RSA_PSK might bypass the Server-Key-Exchange
+certify(#server_hello_done{},
+ #state{session = #session{master_secret = undefined},
+ negotiated_version = Version,
+ psk_identity = PSKIdentity,
+ ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
+ premaster_secret = undefined,
+ role = client,
+ key_algorithm = Alg} = State0, Connection)
+ when Alg == psk ->
+ case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State0);
+ PremasterSecret ->
+ State = master_secret(PremasterSecret,
+ State0#state{premaster_secret = PremasterSecret}),
+ client_certify_and_key_exchange(State, Connection)
+ end;
+
+certify(#server_hello_done{},
+ #state{session = #session{master_secret = undefined},
+ ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
+ negotiated_version = {Major, Minor},
+ psk_identity = PSKIdentity,
+ premaster_secret = undefined,
+ role = client,
+ key_algorithm = Alg} = State0, Connection)
+ when Alg == rsa_psk ->
+ Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
+ case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of
+ #alert{} = Alert ->
+ Alert;
+ PremasterSecret ->
+ State = master_secret(PremasterSecret, State0#state{premaster_secret = RSAPremasterSecret}),
+ client_certify_and_key_exchange(State, Connection)
+ end;
+
+%% Master secret was determined with help of server-key exchange msg
+certify(#server_hello_done{},
+ #state{session = #session{master_secret = MasterSecret} = Session,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ premaster_secret = undefined,
+ role = client} = State0, Connection) ->
+ case ssl_handshake:master_secret(record_cb(Connection), Version, Session,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates} ->
+ State = State0#state{connection_states = ConnectionStates},
+ client_certify_and_key_exchange(State, Connection);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State0)
+ end;
+
+%% Master secret is calculated from premaster_secret
+certify(#server_hello_done{},
+ #state{session = Session0,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ premaster_secret = PremasterSecret,
+ role = client} = State0, Connection) ->
+ case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ client_certify_and_key_exchange(State, Connection);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State0)
+ end;
+
+certify(#client_key_exchange{} = Msg,
+ #state{role = server,
+ client_certificate_requested = true,
+ ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, Connection) ->
+ %% We expect a certificate here
+ Connection:handle_unexpected_message(Msg, certify_client_key_exchange, State);
+
+certify(#client_key_exchange{exchange_keys = Keys},
+ State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) ->
+ try
+ certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version),
+ State, Connection)
+ catch
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State)
+ end;
+
+certify(timeout, State, _) ->
+ {next_state, certify, State, hibernate};
+
+certify(Msg, State, Connection) ->
+ Connection:handle_unexpected_message(Msg, certify, State).
+
+%%--------------------------------------------------------------------
+-spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(),
+ #state{}, tls_connection | dtls_connection) ->
+ gen_fsm_state_return().
+%%--------------------------------------------------------------------
+cipher(#hello_request{}, State0, Connection) ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_state(cipher, hello, Record, State);
+
+cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign},
+ #state{role = server,
+ public_key_info = {Algo, _, _} =PublicKeyInfo,
+ negotiated_version = Version,
+ session = #session{master_secret = MasterSecret},
+ tls_handshake_history = Handshake
+ } = State0, Connection) ->
+
+ HashSign = ssl_handshake:select_cert_hashsign(CertHashSign, Algo, Version),
+ case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
+ Version, HashSign, MasterSecret, Handshake) of
+ valid ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_state(cipher, cipher, Record,
+ State#state{cert_hashsign_algorithm = HashSign});
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, cipher, State0)
+ end;
+
+%% client must send a next protocol message if we are expecting it
+cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true,
+ next_protocol = undefined, negotiated_version = Version} = State0,
+ Connection) ->
+ Connection:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0);
+
+cipher(#finished{verify_data = Data} = Finished,
+ #state{negotiated_version = Version,
+ host = Host,
+ port = Port,
+ role = Role,
+ session = #session{master_secret = MasterSecret}
+ = Session0,
+ connection_states = ConnectionStates0,
+ tls_handshake_history = Handshake0} = State, Connection) ->
+ case ssl_handshake:verify_connection(Version, Finished,
+ opposite_role(Role),
+ get_current_prf(ConnectionStates0, read),
+ MasterSecret, Handshake0) of
+ verified ->
+ Session = register_session(Role, Host, Port, Session0),
+ cipher_role(Role, Data, Session, State, Connection);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, cipher, State)
+ end;
+
+%% only allowed to send next_protocol message after change cipher spec
+%% & before finished message and it is not allowed during renegotiation
+cipher(#next_protocol{selected_protocol = SelectedProtocol},
+ #state{role = server, expecting_next_protocol_negotiation = true} = State0, Connection) ->
+ {Record, State} = Connection:next_record(State0#state{next_protocol = SelectedProtocol}),
+ Connection:next_state(cipher, cipher, Record, State);
+
+cipher(timeout, State, _) ->
+ {next_state, cipher, State, hibernate};
+
+cipher(Msg, State, Connection) ->
+ Connection:handle_unexpected_message(Msg, cipher, State).
+
+%%--------------------------------------------------------------------
+-spec connection(term(), #state{}, tls_connection | dtls_connection) ->
+ gen_fsm_state_return().
+%%--------------------------------------------------------------------
+connection(timeout, State, _) ->
+ {next_state, connection, State, hibernate};
+
+connection(Msg, State, Connection) ->
+ Connection:handle_unexpected_message(Msg, connection, State).
+
+%%--------------------------------------------------------------------
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+handle_sync_event({application_data, Data}, From, connection,
+ #state{protocol_cb = Connection} = State) ->
+ %% We should look into having a worker process to do this to
+ %% parallize send and receive decoding and not block the receiver
+ %% if sending is overloading the socket.
+ try
+ Connection:write_application_data(Data, From, State)
+ catch throw:Error ->
+ {reply, Error, connection, State, get_timeout(State)}
+ end;
+handle_sync_event({application_data, Data}, From, StateName,
+ #state{send_queue = Queue} = State) ->
+ %% In renegotiation priorities handshake, send data when handshake is finished
+ {next_state, StateName,
+ State#state{send_queue = queue:in({From, Data}, Queue)},
+ get_timeout(State)};
+
+handle_sync_event({start, Timeout}, StartFrom, hello, #state{protocol_cb = Connection} = State) ->
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ Connection:hello(start, State#state{start_or_recv_from = StartFrom,
+ timer = Timer});
+
+%% The two clauses below could happen if a server upgrades a socket in
+%% active mode. Note that in this case we are lucky that
+%% controlling_process has been evalueated before receiving handshake
+%% messages from client. The server should put the socket in passive
+%% mode before telling the client that it is willing to upgrade
+%% and before calling ssl:ssl_accept/2. These clauses are
+%% here to make sure it is the users problem and not owers if
+%% they upgrade an active socket.
+handle_sync_event({start,_}, _, connection, State) ->
+ {reply, connected, connection, State, get_timeout(State)};
+handle_sync_event({start,_}, _From, error, {Error, State = #state{}}) ->
+ {stop, {shutdown, Error}, {error, Error}, State};
+
+handle_sync_event({start, Timeout}, StartFrom, StateName, State) ->
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ {next_state, StateName, State#state{start_or_recv_from = StartFrom,
+ timer = Timer}, get_timeout(State)};
+
+handle_sync_event(close, _, StateName, #state{protocol_cb = Connection} = State) ->
+ %% Run terminate before returning
+ %% so that the reuseaddr inet-option will work
+ %% as intended.
+ (catch Connection:terminate(user_close, StateName, State)),
+ {stop, normal, ok, State#state{terminated = true}};
+
+handle_sync_event({shutdown, How0}, _, StateName,
+ #state{transport_cb = Transport,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ socket = Socket} = State) ->
+ case How0 of
+ How when How == write; How == both ->
+ Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ {BinMsg, _} =
+ ssl_alert:encode(Alert, Version, ConnectionStates),
+ Transport:send(Socket, BinMsg);
+ _ ->
+ ok
+ end,
+
+ case Transport:shutdown(Socket, How0) of
+ ok ->
+ {reply, ok, StateName, State, get_timeout(State)};
+ Error ->
+ {stop, normal, Error, State}
+ end;
+
+handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName,
+ #state{protocol_cb = Connection} = State0) ->
+ Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
+ Connection:passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom, timer = Timer}, StateName);
+
+%% Doing renegotiate wait with handling request until renegotiate is
+%% finished. Will be handled by next_state_is_connection/2.
+handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) ->
+ Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
+ {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom,
+ timer = Timer},
+ get_timeout(State)};
+
+handle_sync_event({new_user, User}, _From, StateName,
+ State =#state{user_application = {OldMon, _}}) ->
+ NewMon = erlang:monitor(process, User),
+ erlang:demonitor(OldMon, [flush]),
+ {reply, ok, StateName, State#state{user_application = {NewMon,User}},
+ get_timeout(State)};
+
+handle_sync_event({get_opts, OptTags}, _From, StateName,
+ #state{socket = Socket,
+ transport_cb = Transport,
+ socket_options = SockOpts} = State) ->
+ OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []),
+ {reply, OptsReply, StateName, State, get_timeout(State)};
+
+handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) ->
+ {reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)};
+handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) ->
+ {reply, {ok, NextProtocol}, StateName, State, get_timeout(State)};
+
+handle_sync_event({set_opts, Opts0}, _From, StateName0,
+ #state{socket_options = Opts1,
+ protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport,
+ user_data_buffer = Buffer} = State0) ->
+ {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []),
+ State1 = State0#state{socket_options = Opts},
+ if
+ Opts#socket_options.active =:= false ->
+ {reply, Reply, StateName0, State1, get_timeout(State1)};
+ Buffer =:= <<>>, Opts1#socket_options.active =:= false ->
+ %% Need data, set active once
+ {Record, State2} = Connection:next_record_if_active(State1),
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_state(StateName0, StateName0, Record, State2) of
+ {next_state, StateName, State, Timeout} ->
+ {reply, Reply, StateName, State, Timeout};
+ {stop, Reason, State} ->
+ {stop, Reason, State}
+ end;
+ Buffer =:= <<>> ->
+ %% Active once already set
+ {reply, Reply, StateName0, State1, get_timeout(State1)};
+ true ->
+ case Connection:read_application_data(<<>>, State1) of
+ Stop = {stop,_,_} ->
+ Stop;
+ {Record, State2} ->
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_state(StateName0, StateName0, Record, State2) of
+ {next_state, StateName, State, Timeout} ->
+ {reply, Reply, StateName, State, Timeout};
+ {stop, Reason, State} ->
+ {stop, Reason, State}
+ end
+ end
+ end;
+
+handle_sync_event(renegotiate, From, connection, #state{protocol_cb = Connection} = State) ->
+ Connection:renegotiate(State#state{renegotiation = {true, From}});
+
+handle_sync_event(renegotiate, _, StateName, State) ->
+ {reply, {error, already_renegotiating}, StateName, State, get_timeout(State)};
+
+handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName,
+ #state{connection_states = ConnectionStates,
+ negotiated_version = Version} = State) ->
+ ConnectionState =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{master_secret = MasterSecret,
+ client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Reply = try
+ SecretToUse = case Secret of
+ _ when is_binary(Secret) -> Secret;
+ master_secret -> MasterSecret
+ end,
+ SeedToUse = lists:reverse(
+ lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
+ (client_random, Acc) -> [ClientRandom|Acc];
+ (server_random, Acc) -> [ServerRandom|Acc]
+ end, [], Seed)),
+ ssl_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength)
+ catch
+ exit:_ -> {error, badarg};
+ error:Reason -> {error, Reason}
+ end,
+ {reply, Reply, StateName, State, get_timeout(State)};
+
+handle_sync_event(info, _, StateName,
+ #state{negotiated_version = Version,
+ session = #session{cipher_suite = Suite}} = State) ->
+
+ AtomVersion = tls_record:protocol_version(Version),
+ {reply, {ok, {AtomVersion, ssl:suite_definition(Suite)}},
+ StateName, State, get_timeout(State)};
+
+handle_sync_event(session_info, _, StateName,
+ #state{session = #session{session_id = Id,
+ cipher_suite = Suite}} = State) ->
+ {reply, [{session_id, Id},
+ {cipher_suite, ssl:suite_definition(Suite)}],
+ StateName, State, get_timeout(State)};
+
+handle_sync_event(peer_certificate, _, StateName,
+ #state{session = #session{peer_certificate = Cert}}
+ = State) ->
+ {reply, {ok, Cert}, StateName, State, get_timeout(State)}.
+
+handle_info({ErrorTag, Socket, econnaborted}, StateName,
+ #state{socket = Socket, transport_cb = Transport,
+ start_or_recv_from = StartFrom, role = Role,
+ protocol_cb = Connection,
+ error_tag = ErrorTag} = State) when StateName =/= connection ->
+ Connection:alert_user(Transport, Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role),
+ {stop, normal, State};
+
+handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
+ protocol_cb = Connection,
+ error_tag = ErrorTag} = State) ->
+ Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
+ error_logger:info_report(Report),
+ Connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
+ {stop, normal, State};
+
+handle_info({'DOWN', MonitorRef, _, _, _}, _,
+ State = #state{user_application={MonitorRef,_Pid}}) ->
+ {stop, normal, State};
+
+%%% So that terminate will be run when supervisor issues shutdown
+handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
+ {stop, shutdown, State};
+handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) ->
+ %% Handle as transport close"
+ {stop, {shutdown, transport_closed}, State};
+
+handle_info(allow_renegotiate, StateName, State) ->
+ {next_state, StateName, State#state{allow_renegotiate = true}, get_timeout(State)};
+
+handle_info({cancel_start_or_recv, StartFrom}, StateName,
+ #state{renegotiation = {false, first}} = State) when StateName =/= connection ->
+ gen_fsm:reply(StartFrom, {error, timeout}),
+ {stop, {shutdown, user_timeout}, State#state{timer = undefined}};
+
+handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) ->
+ gen_fsm:reply(RecvFrom, {error, timeout}),
+ {next_state, StateName, State#state{start_or_recv_from = undefined,
+ bytes_to_read = undefined,
+ timer = undefined}, get_timeout(State)};
+
+handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
+ {next_state, StateName, State#state{timer = undefined}, get_timeout(State)};
+
+handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) ->
+ Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]),
+ error_logger:info_report(Report),
+ {next_state, StateName, State, get_timeout(State)}.
+
+
+terminate(_, _, #state{terminated = true}) ->
+ %% Happens when user closes the connection using ssl:close/1
+ %% we want to guarantee that Transport:close has been called
+ %% when ssl:close/1 returns.
+ ok;
+
+terminate({shutdown, transport_closed}, StateName, #state{send_queue = SendQueue,
+ renegotiation = Renegotiate} = State) ->
+ handle_unrecv_data(StateName, State),
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate);
+
+terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue,
+ renegotiation = Renegotiate} = State) ->
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate);
+
+terminate(Reason, connection, #state{negotiated_version = Version,
+ protocol_cb = Connection,
+ connection_states = ConnectionStates,
+ transport_cb = Transport, socket = Socket,
+ send_queue = SendQueue, renegotiation = Renegotiate} = State) ->
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
+ BinAlert = terminate_alert(Reason, Version, ConnectionStates),
+ Transport:send(Socket, BinAlert),
+ case Connection of
+ tls_connection ->
+ tls_connection:workaround_transport_delivery_problems(Socket, Transport);
+ _ ->
+ ok
+ end;
+
+terminate(_Reason, _StateName, #state{transport_cb = Transport,
+ socket = Socket, send_queue = SendQueue,
+ renegotiation = Renegotiate} = State) ->
+ handle_trusted_certs_db(State),
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
+ Transport:close(Socket).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} =
+ ServerHelloExt,
+ #state{negotiated_version = Version,
+ session = #session{session_id = SessId},
+ connection_states = ConnectionStates0}
+ = State0, Connection) when is_atom(Type) ->
+
+ ServerHello =
+ ssl_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt),
+ State = server_hello(ServerHello,
+ State0#state{expecting_next_protocol_negotiation =
+ NextProtocols =/= undefined}, Connection),
+ case Type of
+ new ->
+ new_server_hello(ServerHello, State, Connection);
+ resumed ->
+ resumed_server_hello(State, Connection)
+ end.
+
+new_server_hello(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression,
+ session_id = SessionId},
+ #state{session = Session0,
+ negotiated_version = Version} = State0, Connection) ->
+ try server_certify_and_key_exchange(State0, Connection) of
+ #state{} = State1 ->
+ State2 = server_hello_done(State1, Connection),
+ Session =
+ Session0#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {Record, State} = Connection:next_record(State2#state{session = Session}),
+ Connection:next_state(hello, certify, Record, State)
+ catch
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+resumed_server_hello(#state{session = Session,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version} = State0, Connection) ->
+
+ case ssl_handshake:master_secret(record_cb(Connection), Version, Session,
+ ConnectionStates0, server) of
+ {_, ConnectionStates1} ->
+ State1 = State0#state{connection_states = ConnectionStates1,
+ session = Session},
+ State2 =
+ finalize_handshake(State1, abbreviated, Connection),
+ {Record, State} = Connection:next_record(State2),
+ Connection:next_state(hello, abbreviated, Record, State);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+server_hello(ServerHello, State0, Connection) ->
+ CipherSuite = ServerHello#server_hello.cipher_suite,
+ {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
+ State = Connection:send_handshake(ServerHello, State0),
+ State#state{key_algorithm = KeyAlgorithm}.
+
+server_hello_done(State, Connection) ->
+ HelloDone = ssl_handshake:server_hello_done(),
+ Connection:send_handshake(HelloDone, State).
+
+
+
+
+handle_peer_cert(Role, PeerCert, PublicKeyInfo,
+ #state{session = #session{cipher_suite = CipherSuite} = Session} = State0,
+ Connection) ->
+ State1 = State0#state{session =
+ Session#session{peer_certificate = PeerCert},
+ public_key_info = PublicKeyInfo},
+ {KeyAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
+ State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1),
+
+ {Record, State} = Connection:next_record(State2),
+ Connection:next_state(certify, certify, Record, State).
+
+handle_peer_cert_key(client, _,
+ {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
+ PublicKeyParams},
+ KeyAlg, State) when KeyAlg == ecdh_rsa;
+ KeyAlg == ecdh_ecdsa ->
+ ECDHKey = public_key:generate_key(PublicKeyParams),
+ PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
+ master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey});
+
+%% We do currently not support cipher suites that use fixed DH.
+%% If we want to implement that the following clause can be used
+%% to extract DH parameters form cert.
+%% handle_peer_cert_key(client, _PeerCert, {?dhpublicnumber, PublicKey, PublicKeyParams},
+%% {_,SignAlg},
+%% #state{diffie_hellman_keys = {_, MyPrivatKey}} = State) when
+%% SignAlg == dh_rsa;
+%% SignAlg == dh_dss ->
+%% dh_master_secret(PublicKeyParams, PublicKey, MyPrivatKey, State);
+handle_peer_cert_key(_, _, _, _, State) ->
+ State.
+
+certify_client(#state{client_certificate_requested = true, role = client,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ session = #session{own_certificate = OwnCert}}
+ = State, Connection) ->
+ Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
+ Connection:send_handshake(Certificate, State);
+
+certify_client(#state{client_certificate_requested = false} = State, _) ->
+ State.
+
+verify_client_cert(#state{client_certificate_requested = true, role = client,
+ negotiated_version = Version,
+ private_key = PrivateKey,
+ session = #session{master_secret = MasterSecret,
+ own_certificate = OwnCert},
+ cert_hashsign_algorithm = HashSign,
+ tls_handshake_history = Handshake0} = State, Connection) ->
+
+ case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
+ Version, HashSign, PrivateKey, Handshake0) of
+ #certificate_verify{} = Verified ->
+ Connection:send_handshake(Verified, State);
+ ignore ->
+ State;
+ #alert{} = Alert ->
+ throw(Alert)
+ end;
+verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
+ State.
+
+client_certify_and_key_exchange(#state{negotiated_version = Version} =
+ State0, Connection) ->
+ try do_client_certify_and_key_exchange(State0, Connection) of
+ State1 = #state{} ->
+ State2 = finalize_handshake(State1, certify, Connection),
+ State3 = State2#state{
+ %% Reinitialize
+ client_certificate_requested = false},
+ {Record, State} = Connection:next_record(State3),
+ Connection:next_state(certify, cipher, Record, State)
+ catch
+ throw:#alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State0)
+ end.
+
+do_client_certify_and_key_exchange(State0, Connection) ->
+ State1 = certify_client(State0, Connection),
+ State2 = key_exchange(State1, Connection),
+ verify_client_cert(State2, Connection).
+
+server_certify_and_key_exchange(State0, Connection) ->
+ State1 = certify_server(State0, Connection),
+ State2 = key_exchange(State1, Connection),
+ request_client_cert(State2, Connection).
+
+certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
+ #state{private_key = Key} = State, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(EncPMS, Key),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+
+certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
+ #state{diffie_hellman_params = #'DHParameter'{} = Params,
+ diffie_hellman_keys = {_, ServerDhPrivateKey}} = State,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+
+certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
+ #state{diffie_hellman_keys = ECDHKey} = State, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+
+certify_client_key_exchange(#client_psk_identity{} = ClientKey,
+ #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+
+certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
+ #state{diffie_hellman_params = #'DHParameter'{} = Params,
+ diffie_hellman_keys = {_, ServerDhPrivateKey},
+ ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
+ #state{private_key = Key,
+ ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+
+certify_client_key_exchange(#client_srp_public{} = ClientKey,
+ #state{srp_params = Params,
+ srp_keys = Key
+ } = State0, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
+
+certify_server(#state{key_algorithm = Algo} = State, _)
+ when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; Algo == srp_anon ->
+ State;
+
+certify_server(#state{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ session = #session{own_certificate = OwnCert}} = State, Connection) ->
+ case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
+ Cert = #certificate{} ->
+ Connection:send_handshake(Cert, State);
+ Alert = #alert{} ->
+ throw(Alert)
+ end.
+
+key_exchange(#state{role = server, key_algorithm = rsa} = State,_) ->
+ State;
+key_exchange(#state{role = server, key_algorithm = Algo,
+ hashsign_algorithm = HashSignAlgo,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ private_key = PrivateKey,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State0, Connection)
+ when Algo == dhe_dss;
+ Algo == dhe_rsa;
+ Algo == dh_anon ->
+ DHKeys = public_key:generate_key(Params),
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ State = Connection:send_handshake(Msg, State0),
+ State#state{diffie_hellman_keys = DHKeys};
+
+key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State, _)
+ when Algo == ecdh_ecdsa; Algo == ecdh_rsa ->
+ State#state{diffie_hellman_keys = Key};
+key_exchange(#state{role = server, key_algorithm = Algo,
+ hashsign_algorithm = HashSignAlgo,
+ private_key = PrivateKey,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State0, Connection)
+ when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa;
+ Algo == ecdh_anon ->
+
+ ECDHKeys = public_key:generate_key(select_curve(State0)),
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ State = Connection:send_handshake(Msg, State0),
+ State#state{diffie_hellman_keys = ECDHKeys};
+
+key_exchange(#state{role = server, key_algorithm = psk,
+ ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
+ State;
+key_exchange(#state{role = server, key_algorithm = psk,
+ ssl_options = #ssl_options{psk_identity = PskIdentityHint},
+ hashsign_algorithm = HashSignAlgo,
+ private_key = PrivateKey,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State0, Connection) ->
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ Connection:send_handshake(Msg, State0);
+
+key_exchange(#state{role = server, key_algorithm = dhe_psk,
+ ssl_options = #ssl_options{psk_identity = PskIdentityHint},
+ hashsign_algorithm = HashSignAlgo,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ private_key = PrivateKey,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State0, Connection) ->
+ DHKeys = public_key:generate_key(Params),
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ State = Connection:send_handshake(Msg, State0),
+ State#state{diffie_hellman_keys = DHKeys};
+
+key_exchange(#state{role = server, key_algorithm = rsa_psk,
+ ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
+ State;
+key_exchange(#state{role = server, key_algorithm = rsa_psk,
+ ssl_options = #ssl_options{psk_identity = PskIdentityHint},
+ hashsign_algorithm = HashSignAlgo,
+ private_key = PrivateKey,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State0, Connection) ->
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ Connection:send_handshake(Msg, State0);
+
+key_exchange(#state{role = server, key_algorithm = Algo,
+ ssl_options = #ssl_options{user_lookup_fun = LookupFun},
+ hashsign_algorithm = HashSignAlgo,
+ session = #session{srp_username = Username},
+ private_key = PrivateKey,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State0, Connection)
+ when Algo == srp_dss;
+ Algo == srp_rsa;
+ Algo == srp_anon ->
+ SrpParams = handle_srp_identity(Username, LookupFun),
+ Keys = case generate_srp_server_keys(SrpParams, 0) of
+ Alert = #alert{} ->
+ throw(Alert);
+ Keys0 = {_,_} ->
+ Keys0
+ end,
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ State = Connection:send_handshake(Msg, State0),
+ State#state{srp_params = SrpParams,
+ srp_keys = Keys};
+
+key_exchange(#state{role = client,
+ key_algorithm = rsa,
+ public_key_info = PublicKeyInfo,
+ negotiated_version = Version,
+ premaster_secret = PremasterSecret} = State0, Connection) ->
+ Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo),
+ Connection:send_handshake(Msg, State0);
+
+key_exchange(#state{role = client,
+ key_algorithm = Algorithm,
+ negotiated_version = Version,
+ diffie_hellman_keys = {DhPubKey, _}
+ } = State0, Connection)
+ when Algorithm == dhe_dss;
+ Algorithm == dhe_rsa;
+ Algorithm == dh_anon ->
+ Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}),
+ Connection:send_handshake(Msg, State0);
+
+key_exchange(#state{role = client,
+ key_algorithm = Algorithm,
+ negotiated_version = Version,
+ diffie_hellman_keys = Keys} = State0, Connection)
+ when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa;
+ Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa;
+ Algorithm == ecdh_anon ->
+ Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}),
+ Connection:send_handshake(Msg, State0);
+
+key_exchange(#state{role = client,
+ ssl_options = SslOpts,
+ key_algorithm = psk,
+ negotiated_version = Version} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}),
+ Connection:send_handshake(Msg, State0);
+
+key_exchange(#state{role = client,
+ ssl_options = SslOpts,
+ key_algorithm = dhe_psk,
+ negotiated_version = Version,
+ diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, Version,
+ {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}),
+ Connection:send_handshake(Msg, State0);
+key_exchange(#state{role = client,
+ ssl_options = SslOpts,
+ key_algorithm = rsa_psk,
+ public_key_info = PublicKeyInfo,
+ negotiated_version = Version,
+ premaster_secret = PremasterSecret}
+ = State0, Connection) ->
+ Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity,
+ PremasterSecret, PublicKeyInfo),
+ Connection:send_handshake(Msg, State0);
+
+key_exchange(#state{role = client,
+ key_algorithm = Algorithm,
+ negotiated_version = Version,
+ srp_keys = {ClientPubKey, _}}
+ = State0, Connection)
+ when Algorithm == srp_dss;
+ Algorithm == srp_rsa;
+ Algorithm == srp_anon ->
+ Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}),
+ Connection:send_handshake(Msg, State0).
+
+rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption;
+ Algorithm == ?sha224WithRSAEncryption;
+ Algorithm == ?sha256WithRSAEncryption;
+ Algorithm == ?sha384WithRSAEncryption;
+ Algorithm == ?sha512WithRSAEncryption
+ ->
+ ssl_handshake:key_exchange(client, Version,
+ {premaster_secret, PremasterSecret,
+ PublicKeyInfo});
+rsa_key_exchange(_, _, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
+
+rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption;
+ Algorithm == ?sha224WithRSAEncryption;
+ Algorithm == ?sha256WithRSAEncryption;
+ Algorithm == ?sha384WithRSAEncryption;
+ Algorithm == ?sha512WithRSAEncryption
+ ->
+ ssl_handshake:key_exchange(client, Version,
+ {psk_premaster_secret, PskIdentity, PremasterSecret,
+ PublicKeyInfo});
+rsa_psk_key_exchange(_, _, _, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
+
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
+ connection_states = ConnectionStates0,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ negotiated_version = Version} = State0, Connection) ->
+ #connection_state{security_parameters =
+ #security_parameters{cipher_suite = CipherSuite}} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version),
+ State = Connection:send_handshake(Msg, State0),
+ State#state{client_certificate_requested = true};
+
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
+ State, _) ->
+ State.
+
+calculate_master_secret(PremasterSecret, #state{negotiated_version = Version,
+ connection_states = ConnectionStates0,
+ session = Session0} = State0, Connection,
+ Current, Next) ->
+ case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret,
+ ConnectionStates0, server) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State1 = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ {Record, State} = Connection:next_record(State1),
+ Connection:next_state(Current, Next, Record, State);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State0)
+ end.
+
+finalize_handshake(State0, StateName, Connection) ->
+ #state{connection_states = ConnectionStates0} =
+ State1 = cipher_protocol(State0, Connection),
+
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0,
+ write),
+
+ State2 = State1#state{connection_states = ConnectionStates},
+ State = next_protocol(State2, Connection),
+ finished(State, StateName, Connection).
+
+next_protocol(#state{role = server} = State, _) ->
+ State;
+next_protocol(#state{next_protocol = undefined} = State, _) ->
+ State;
+next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) ->
+ State;
+next_protocol(#state{next_protocol = NextProtocol} = State0, Connection) ->
+ NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
+ Connection:send_handshake(NextProtocolMessage, State0).
+
+cipher_protocol(State, Connection) ->
+ Connection:send_change_cipher(#change_cipher_spec{}, State).
+
+finished(#state{role = Role, negotiated_version = Version,
+ session = Session,
+ connection_states = ConnectionStates0,
+ tls_handshake_history = Handshake0} = State0, StateName, Connection) ->
+ MasterSecret = Session#session.master_secret,
+ Finished = ssl_handshake:finished(Version, Role,
+ get_current_prf(ConnectionStates0, write),
+ MasterSecret, Handshake0),
+ ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
+ Connection:send_handshake(Finished, State0#state{connection_states =
+ ConnectionStates}).
+
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
+ ssl_record:set_client_verify_data(current_write, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) ->
+ ssl_record:set_server_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_client_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_server_verify_data(current_write, Data, ConnectionStates).
+
+calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, dh_y = ServerPublicDhKey} = Params,
+ State, Connection) ->
+ Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
+ PremasterSecret =
+ ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
+ calculate_master_secret(PremasterSecret,
+ State#state{diffie_hellman_keys = Keys}, Connection, certify, certify);
+
+calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
+ State, Connection) ->
+ ECDHKeys = public_key:generate_key(ECCurve),
+ PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
+ calculate_master_secret(PremasterSecret,
+ State#state{diffie_hellman_keys = ECDHKeys}, Connection, certify, certify);
+
+calculate_secret(#server_psk_params{
+ hint = IdentityHint},
+ State0, Connection) ->
+ %% store for later use
+ {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}),
+ Connection:next_state(certify, certify, Record, State);
+
+calculate_secret(#server_dhe_psk_params{
+ dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
+ #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State, Connection) ->
+ Keys = {_, PrivateDhKey} =
+ crypto:generate_key(dh, [Prime, Base]),
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State#state{diffie_hellman_keys = Keys},
+ Connection, certify, certify);
+
+calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
+ #state{ssl_options = #ssl_options{srp_identity = SRPId}} = State, Connection) ->
+ Keys = generate_srp_client_keys(Generator, Prime, 0),
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId),
+ calculate_master_secret(PremasterSecret, State#state{srp_keys = Keys}, Connection, certify, certify).
+
+master_secret(#alert{} = Alert, _) ->
+ Alert;
+master_secret(PremasterSecret, #state{session = Session,
+ negotiated_version = Version, role = Role,
+ connection_states = ConnectionStates0} = State) ->
+ case ssl_handshake:master_secret(tls_record, Version, PremasterSecret,
+ ConnectionStates0, Role) of
+ {MasterSecret, ConnectionStates} ->
+ State#state{
+ session =
+ Session#session{master_secret = MasterSecret},
+ connection_states = ConnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end.
+
+generate_srp_server_keys(_SrpParams, 10) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+generate_srp_server_keys(SrpParams =
+ #srp_user{generator = Generator, prime = Prime,
+ verifier = Verifier}, N) ->
+ case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
+ error ->
+ generate_srp_server_keys(SrpParams, N+1);
+ Keys ->
+ Keys
+ end.
+
+generate_srp_client_keys(_Generator, _Prime, 10) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+generate_srp_client_keys(Generator, Prime, N) ->
+
+ case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
+ error ->
+ generate_srp_client_keys(Generator, Prime, N+1);
+ Keys ->
+ Keys
+ end.
+
+handle_srp_identity(Username, {Fun, UserState}) ->
+ case Fun(srp, Username, UserState) of
+ {ok, {SRPParams, Salt, DerivedKey}}
+ when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) ->
+ {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams),
+ Verifier = crypto:mod_pow(Generator, DerivedKey, Prime),
+ #srp_user{generator = Generator, prime = Prime,
+ salt = Salt, verifier = Verifier};
+ #alert{} = Alert ->
+ throw(Alert);
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end.
+
+
+cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State,
+ Connection) ->
+ ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0),
+ Connection:next_state_connection(cipher,
+ ack_connection(
+ State#state{session = Session,
+ connection_states = ConnectionStates}));
+
+cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0,
+ Connection) ->
+ ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0),
+ State =
+ finalize_handshake(State0#state{connection_states = ConnectionStates1,
+ session = Session}, cipher, Connection),
+ Connection:next_state_connection(cipher, ack_connection(State#state{session = Session})).
+
+negotiated_hashsign(undefined, Algo, Version) ->
+ default_hashsign(Version, Algo);
+negotiated_hashsign(HashSign = {_, _}, _, _) ->
+ HashSign.
+
+%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms
+%% If the client does not send the signature_algorithms extension, the
+%% server MUST do the following:
+%%
+%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA,
+%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had
+%% sent the value {sha1,rsa}.
+%%
+%% - If the negotiated key exchange algorithm is one of (DHE_DSS,
+%% DH_DSS), behave as if the client had sent the value {sha1,dsa}.
+%%
+%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA,
+%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
+
+default_hashsign(_Version = {Major, Minor}, KeyExchange)
+ when Major >= 3 andalso Minor >= 3 andalso
+ (KeyExchange == rsa orelse
+ KeyExchange == dhe_rsa orelse
+ KeyExchange == dh_rsa orelse
+ KeyExchange == ecdhe_rsa orelse
+ KeyExchange == ecdh_rsa orelse
+ KeyExchange == srp_rsa) ->
+ {sha, rsa};
+default_hashsign(_Version, KeyExchange)
+ when KeyExchange == rsa;
+ KeyExchange == dhe_rsa;
+ KeyExchange == dh_rsa;
+ KeyExchange == ecdhe_rsa;
+ KeyExchange == ecdh_rsa;
+ KeyExchange == srp_rsa ->
+ {md5sha, rsa};
+default_hashsign(_Version, KeyExchange)
+ when KeyExchange == ecdhe_ecdsa;
+ KeyExchange == ecdh_ecdsa ->
+ {sha, ecdsa};
+default_hashsign(_Version, KeyExchange)
+ when KeyExchange == dhe_dss;
+ KeyExchange == dh_dss;
+ KeyExchange == srp_dss ->
+ {sha, dsa};
+default_hashsign(_Version, KeyExchange)
+ when KeyExchange == dh_anon;
+ KeyExchange == ecdh_anon;
+ KeyExchange == psk;
+ KeyExchange == dhe_psk;
+ KeyExchange == rsa_psk;
+ KeyExchange == srp_anon ->
+ {null, anon}.
+
+select_curve(#state{client_ecc = {[Curve|_], _}}) ->
+ {namedCurve, Curve};
+select_curve(_) ->
+ {namedCurve, ?secp256k1}.
+
+is_anonymous(Algo) when Algo == dh_anon;
+ Algo == ecdh_anon;
+ Algo == psk;
+ Algo == dhe_psk;
+ Algo == rsa_psk;
+ Algo == srp_anon ->
+ true;
+is_anonymous(_) ->
+ false.
+
+get_current_prf(CStates, Direction) ->
+ CS = ssl_record:current_connection_state(CStates, Direction),
+ CS#connection_state.security_parameters#security_parameters.prf_algorithm.
+get_pending_prf(CStates, Direction) ->
+ CS = ssl_record:pending_connection_state(CStates, Direction),
+ CS#connection_state.security_parameters#security_parameters.prf_algorithm.
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+record_cb(tls_connection) ->
+ tls_record;
+record_cb(dtls_connection) ->
+ dtls_record.
+
+sync_send_all_state_event(FsmPid, Event) ->
+ try gen_fsm:sync_send_all_state_event(FsmPid, Event, infinity)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{{shutdown, _},_} ->
+ {error, closed}
+ end.
+
+get_socket_opts(_,_,[], _, Acc) ->
+ {ok, Acc};
+get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts,
+ [{mode, SockOpts#socket_options.mode} | Acc]);
+get_socket_opts(Transport, Socket, [packet | Tags], SockOpts, Acc) ->
+ case SockOpts#socket_options.packet of
+ {Type, headers} ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
+ Type ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
+ end;
+get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts,
+ [{header, SockOpts#socket_options.header} | Acc]);
+get_socket_opts(Transport, Socket, [active | Tags], SockOpts, Acc) ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts,
+ [{active, SockOpts#socket_options.active} | Acc]);
+get_socket_opts(Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
+ try ssl_socket:getopts(Transport, Socket, [Tag]) of
+ {ok, [Opt]} ->
+ get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]);
+ {error, Error} ->
+ {error, {options, {socket_options, Tag, Error}}}
+ catch
+ %% So that inet behavior does not crash our process
+ _:Error -> {error, {options, {socket_options, Tag, Error}}}
+ end;
+get_socket_opts(_, _,Opts, _,_) ->
+ {error, {options, {socket_options, Opts, function_clause}}}.
+
+set_socket_opts(_,_, [], SockOpts, []) ->
+ {ok, SockOpts};
+set_socket_opts(Transport, Socket, [], SockOpts, Other) ->
+ %% Set non emulated options
+ try ssl_socket:setopts(Transport, Socket, Other) of
+ ok ->
+ {ok, SockOpts};
+ {error, InetError} ->
+ {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
+ catch
+ _:Error ->
+ %% So that inet behavior does not crash our process
+ {{error, {options, {socket_options, Other, Error}}}, SockOpts}
+ end;
+
+set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) when Mode == list; Mode == binary ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{mode = Mode}, Other);
+set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) when Packet == raw;
+ Packet == 0;
+ Packet == 1;
+ Packet == 2;
+ Packet == 4;
+ Packet == asn1;
+ Packet == cdr;
+ Packet == sunrm;
+ Packet == fcgi;
+ Packet == tpkt;
+ Packet == line;
+ Packet == http;
+ Packet == httph;
+ Packet == http_bin;
+ Packet == httph_bin ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{packet = Packet}, Other);
+set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) when is_integer(Header) ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{header = Header}, Other);
+set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) ->
+ {{error,{options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) when Active == once;
+ Active == true;
+ Active == false ->
+ set_socket_opts(Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+set_socket_opts(Transport, Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]).
+
+start_or_recv_cancel_timer(infinity, _RecvFrom) ->
+ undefined;
+start_or_recv_cancel_timer(Timeout, RecvFrom) ->
+ erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}).
+
+get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) ->
+ infinity;
+get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) ->
+ HibernateAfter.
+
+terminate_alert(Reason, Version, ConnectionStates) when Reason == normal;
+ Reason == user_close ->
+ {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ Version, ConnectionStates),
+ BinAlert;
+terminate_alert({shutdown, _}, Version, ConnectionStates) ->
+ {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ Version, ConnectionStates),
+ BinAlert;
+
+terminate_alert(_, Version, ConnectionStates) ->
+ {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR),
+ Version, ConnectionStates),
+ BinAlert.
+
+handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport,
+ protocol_cb = Connection} = State) ->
+ ssl_socket:setopts(Transport, Socket, [{active, false}]),
+ case Transport:recv(Socket, 0, 0) of
+ {error, closed} ->
+ ok;
+ {ok, Data} ->
+ Connection:handle_close_alert(Data, StateName, State)
+ end.
+
+handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>}}) ->
+ %% No trusted certs specified
+ ok;
+handle_trusted_certs_db(#state{cert_db_ref = Ref,
+ cert_db = CertDb,
+ ssl_options = #ssl_options{cacertfile = undefined}}) ->
+ %% Certs provided as DER directly can not be shared
+ %% with other connections and it is safe to delete them when the connection ends.
+ ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
+handle_trusted_certs_db(#state{file_ref_db = undefined}) ->
+ %% Something went wrong early (typically cacertfile does not exist) so there is nothing to handle
+ ok;
+handle_trusted_certs_db(#state{cert_db_ref = Ref,
+ file_ref_db = RefDb,
+ ssl_options = #ssl_options{cacertfile = File}}) ->
+ case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
+ 0 ->
+ ssl_manager:clean_cert_db(Ref, File);
+ _ ->
+ ok
+ end.
+
+notify_senders(SendQueue) ->
+ lists:foreach(fun({From, _}) ->
+ gen_fsm:reply(From, {error, closed})
+ end, queue:to_list(SendQueue)).
+
+notify_renegotiater({true, From}) when not is_atom(From) ->
+ gen_fsm:reply(From, {error, closed});
+notify_renegotiater(_) ->
+ ok.
+
+ack_connection(#state{renegotiation = {true, Initiater}} = State)
+ when Initiater == internal;
+ Initiater == peer ->
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {true, From}} = State) ->
+ gen_fsm:reply(From, ok),
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {false, first},
+ start_or_recv_from = StartFrom,
+ timer = Timer} = State) when StartFrom =/= undefined ->
+ gen_fsm:reply(StartFrom, connected),
+ cancel_timer(Timer),
+ State#state{renegotiation = undefined, start_or_recv_from = undefined, timer = undefined};
+ack_connection(State) ->
+ State.
+
+cancel_timer(undefined) ->
+ ok;
+cancel_timer(Timer) ->
+ erlang:cancel_timer(Timer),
+ ok.
+
+register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Host, Port, Session),
+ Session;
+register_session(server, _, Port, #session{is_resumable = new} = Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Port, Session),
+ Session;
+register_session(_, _, _, Session) ->
+ Session. %% Already registered
+
+handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0,
+ protocol_cb = Connection} = State0) ->
+ Session = Session0#session{session_id = NewId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {Record, State} = Connection:next_record(State0#state{session = Session}),
+ Connection:next_state(hello, certify, Record, State).
+
+handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ host = Host, port = Port,
+ protocol_cb = Connection,
+ session_cache = Cache,
+ session_cache_cb = CacheCb} = State0) ->
+ Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
+ case ssl_handshake:master_secret(tls_record, Version, Session,
+ ConnectionStates0, client) of
+ {_, ConnectionStates} ->
+ {Record, State} =
+ Connection:next_record(State0#state{
+ connection_states = ConnectionStates,
+ session = Session}),
+ Connection:next_state(hello, abbreviated, Record, State);
+ #alert{} = Alert ->
+ Connection:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+make_premaster_secret({MajVer, MinVer}, rsa) ->
+ Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
+make_premaster_secret(_, _) ->
+ undefined.
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
new file mode 100644
index 0000000000..27489ca325
--- /dev/null
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -0,0 +1,89 @@
+
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: SSL/TLS specific state
+%%----------------------------------------------------------------------
+
+-ifndef(ssl_connection).
+-define(ssl_connection, true).
+
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_srp.hrl").
+-include("ssl_cipher.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-record(state, {
+ role :: client | server,
+ user_application :: {Monitor::reference(), User::pid()},
+ transport_cb :: atom(), % callback module
+ protocol_cb :: tls_connection | dtls_connection,
+ data_tag :: atom(), % ex tcp.
+ close_tag :: atom(), % ex tcp_closed
+ error_tag :: atom(), % ex tcp_error
+ host :: string() | inet:ipaddress(),
+ port :: integer(),
+ socket :: port(),
+ ssl_options :: #ssl_options{},
+ socket_options :: #socket_options{},
+ connection_states :: #connection_states{},
+ protocol_buffers :: term(), %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl
+ tls_handshake_history ::tls_handshake_history(),
+ cert_db :: reference(),
+ session :: #session{},
+ session_cache :: db_handle(),
+ session_cache_cb :: atom(),
+ negotiated_version :: tls_version(),
+ client_certificate_requested = false :: boolean(),
+ key_algorithm :: key_algo(),
+ hashsign_algorithm = {undefined, undefined},
+ cert_hashsign_algorithm,
+ public_key_info ::public_key_info(),
+ private_key ::public_key:private_key(),
+ 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 :: binary(), PrivateKey :: binary()},
+ premaster_secret :: binary(),
+ file_ref_db :: db_handle(),
+ cert_db_ref :: certdb_ref(),
+ bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
+ user_data_buffer :: undefined | binary(),
+ renegotiation :: undefined | {boolean(), From::term() | internal | peer},
+ start_or_recv_from :: term(),
+ timer :: undefined | reference(), % start_or_recive_timer
+ send_queue :: queue(),
+ terminated = false ::boolean(),
+ allow_renegotiate = true ::boolean(),
+ 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).
+
+-endif. % -ifdef(ssl_connection).
diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl
index 9d9afb7707..22614a2d34 100644
--- a/lib/ssl/src/ssl_dist_sup.erl
+++ b/lib/ssl/src/ssl_dist_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -65,7 +65,7 @@ session_and_cert_manager_child_spec() ->
connection_manager_child_spec() ->
Name = ssl_connection_dist,
- StartFunc = {ssl_connection_sup, start_link_dist, []},
+ StartFunc = {tls_connection_sup, start_link_dist, []},
Restart = permanent,
Shutdown = 4000,
Modules = [ssl_connection],
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 9142a260b1..da72ffc043 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -32,13 +32,15 @@
-include_lib("public_key/include/public_key.hrl").
%% Handshake messages
--export([hello_request/0, server_hello_done/0,
+-export([hello_request/0, server_hello/4, server_hello_done/0,
certificate/4, certificate_request/4, key_exchange/3,
finished/5, next_protocol/1]).
%% Handle handshake messages
-export([certify/7, client_certificate_verify/6, certificate_verify/6, verify_signature/5,
- master_secret/5, server_key_exchange_hash/2, verify_connection/6]).
+ master_secret/5, server_key_exchange_hash/2, verify_connection/6,
+ init_handshake_history/0, update_handshake_history/2, verify_server_key/5
+ ]).
%% Encode/Decode
-export([encode_handshake/2, encode_hello_extensions/1,
@@ -60,7 +62,7 @@
%% MISC
-export([select_version/3, prf/5, select_hashsign/2, select_cert_hashsign/3,
- decrypt_premaster_secret/2]).
+ premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%%====================================================================
%% Internal application API
@@ -78,6 +80,25 @@ hello_request() ->
#hello_request{}.
%%--------------------------------------------------------------------
+-spec server_hello(#session{}, tls_version(), #connection_states{},
+ #hello_extensions{}) -> #server_hello{}.
+%%
+%% Description: Creates a server hello message.
+%%--------------------------------------------------------------------
+server_hello(SessionId, Version, ConnectionStates, Extensions) ->
+ Pending = ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = Pending#connection_state.security_parameters,
+
+ #server_hello{server_version = Version,
+ cipher_suite = SecParams#security_parameters.cipher_suite,
+ compression_method =
+ SecParams#security_parameters.compression_algorithm,
+ random = SecParams#security_parameters.server_random,
+ session_id = SessionId,
+ extensions = Extensions
+ }.
+
+%%--------------------------------------------------------------------
-spec server_hello_done() -> #server_hello_done{}.
%%
%% Description: Creates a server hello done message.
@@ -294,6 +315,22 @@ finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the curr
%% ---------- Handle handshake messages ----------
+verify_server_key(#server_key_params{params = Params,
+ params_bin = EncParams,
+ signature = Signature},
+ HashSign = {HashAlgo, _},
+ ConnectionStates, Version, PubKeyInfo) ->
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Hash = server_key_exchange_hash(HashAlgo,
+ <<ClientRandom/binary,
+ ServerRandom/binary,
+ EncParams/binary>>),
+ verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo).
+
%%--------------------------------------------------------------------
-spec certificate_verify(binary(), public_key_info(), tls_version(), term(),
binary(), tls_handshake_history()) -> valid | #alert{}.
@@ -405,14 +442,114 @@ verify_connection(Version, #finished{verify_data = Data},
_ ->
?ALERT_REC(?FATAL, ?DECRYPT_ERROR)
end.
+
+%%--------------------------------------------------------------------
+-spec init_handshake_history() -> tls_handshake_history().
+
+%%
+%% Description: Initialize the empty handshake history buffer.
%%--------------------------------------------------------------------
--spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary().
+init_handshake_history() ->
+ {[], []}.
+%%--------------------------------------------------------------------
+-spec update_handshake_history(tls_handshake_history(), Data ::term()) ->
+ tls_handshake_history().
%%
-%% Description: Public key decryption using the private key.
+%% Description: Update the handshake history buffer with Data.
%%--------------------------------------------------------------------
-decrypt_premaster_secret(Secret, RSAPrivateKey) ->
- try public_key:decrypt_private(Secret, RSAPrivateKey,
+update_handshake_history(Handshake, % special-case SSL2 client hello
+ <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>) ->
+ update_handshake_history(Handshake,
+ <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>);
+update_handshake_history({Handshake0, _Prev}, Data) ->
+ {[Data|Handshake0], Handshake0}.
+
+%% %%--------------------------------------------------------------------
+%% -spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary().
+
+%% %%
+%% %% Description: Public key decryption using the private key.
+%% %%--------------------------------------------------------------------
+%% decrypt_premaster_secret(Secret, RSAPrivateKey) ->
+%% try public_key:decrypt_private(Secret, RSAPrivateKey,
+%% [{rsa_pad, rsa_pkcs1_padding}])
+%% catch
+%% _:_ ->
+%% throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR))
+%% end.
+
+premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) ->
+ public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params);
+
+premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) ->
+ crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]);
+premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime,
+ verifier = Verifier}) ->
+ case crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of
+ error ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ PremasterSecret ->
+ PremasterSecret
+ end;
+
+premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public},
+ ClientKeys, {Username, Password}) ->
+ case ssl_srp_primes:check_srp_params(Generator, Prime) of
+ ok ->
+ DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]),
+ case crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of
+ error ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ PremasterSecret ->
+ PremasterSecret
+ end;
+ _ ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
+ end;
+
+premaster_secret(#client_rsa_psk_identity{
+ identity = PSKIdentity,
+ exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS}
+ }, #'RSAPrivateKey'{} = Key, PSKLookup) ->
+ PremasterSecret = premaster_secret(EncPMS, Key),
+ psk_secret(PSKIdentity, PSKLookup, PremasterSecret);
+
+premaster_secret(#server_dhe_psk_params{
+ hint = IdentityHint,
+ dh_params = #server_dh_params{dh_y = PublicDhKey} = Params},
+ PrivateDhKey,
+ LookupFun) ->
+ PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params),
+ psk_secret(IdentityHint, LookupFun, PremasterSecret);
+
+premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) ->
+ psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret).
+
+premaster_secret(#client_dhe_psk_identity{
+ identity = PSKIdentity,
+ dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) ->
+ PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params),
+ psk_secret(PSKIdentity, PSKLookup, PremasterSecret).
+
+premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) ->
+ psk_secret(PSKIdentity, PSKLookup);
+
+premaster_secret({psk, PSKIdentity}, PSKLookup) ->
+ psk_secret(PSKIdentity, PSKLookup);
+
+premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) ->
+ public_key:compute_key(ECPoint, ECDHKeys);
+premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) ->
+ try public_key:decrypt_private(EncSecret, RSAPrivateKey,
[{rsa_pad, rsa_pkcs1_padding}])
catch
_:_ ->
@@ -464,7 +601,8 @@ select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert) ->
({_, dsa}) ->
false;
({Hash, S}) when S == Sign ->
- ssl_cipher:is_acceptable_hash(Hash, proplists:get_value(hashs, crypto:supports()));
+ ssl_cipher:is_acceptable_hash(Hash,
+ proplists:get_value(hashs, crypto:supports()));
(_) ->
false
end, HashSigns) of
@@ -483,7 +621,8 @@ select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert) ->
%% This function is also used by select_hashsign to extract
%% the alogrithm of the server cert key.
%%--------------------------------------------------------------------
-select_cert_hashsign(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso Major >= 3 andalso Minor >= 3 ->
+select_cert_hashsign(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso
+ Major >= 3 andalso Minor >= 3 ->
HashSign;
select_cert_hashsign(undefined,?'id-ecPublicKey', _) ->
{sha, ecdsa};
@@ -906,13 +1045,12 @@ select_session(SuggestedSessionId, CipherSuites, Compressions, Port, #session{ec
{resumed, Resumed}
end.
-supported_ecc(Version) ->
- case tls_v1:ecc_curves(Version) of
- [] ->
- undefined;
- Curves ->
- #elliptic_curves{elliptic_curve_list = Curves}
- end.
+supported_ecc({Major, Minor} = Version) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) ->
+ Curves = tls_v1:ecc_curves(Version),
+ #elliptic_curves{elliptic_curve_list = Curves};
+supported_ecc(_) ->
+ #elliptic_curves{elliptic_curve_list = []}.
+
%%-------------certificate handling --------------------------------
certificate_types({KeyExchange, _, _, _})
@@ -1697,3 +1835,31 @@ advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
advertised_hash_signs(_) ->
undefined.
+psk_secret(PSKIdentity, PSKLookup) ->
+ case handle_psk_identity(PSKIdentity, PSKLookup) of
+ {ok, PSK} when is_binary(PSK) ->
+ Len = erlang:byte_size(PSK),
+ <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>;
+ #alert{} = Alert ->
+ Alert;
+ _ ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
+ end.
+
+psk_secret(PSKIdentity, PSKLookup, PremasterSecret) ->
+ case handle_psk_identity(PSKIdentity, PSKLookup) of
+ {ok, PSK} when is_binary(PSK) ->
+ Len = erlang:byte_size(PremasterSecret),
+ PSKLen = erlang:byte_size(PSK),
+ <<?UINT16(Len), PremasterSecret/binary, ?UINT16(PSKLen), PSK/binary>>;
+ #alert{} = Alert ->
+ Alert;
+ _ ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
+ end.
+
+handle_psk_identity(_PSKIdentity, LookupFun)
+ when LookupFun == undefined ->
+ error;
+handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
+ Fun(psk, PSKIdentity, UserState).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index a582b8c290..0186f9fca2 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -24,9 +24,6 @@
-include_lib("public_key/include/public_key.hrl").
-%% Looks like it does for backwards compatibility reasons
--record(sslsocket, {fd = nil, pid = nil}).
-
-type reason() :: term().
-type reply() :: term().
-type msg() :: term().
@@ -76,25 +73,26 @@
-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]).
-record(ssl_options, {
- versions, % 'tlsv1.2' | 'tlsv1.1' | tlsv1 | sslv3
- verify, % verify_none | verify_peer
- verify_fun, % fun(CertVerifyErrors) -> boolean()
- fail_if_no_peer_cert, % boolean()
- verify_client_once, % boolean()
+ protocol :: tls | dtls,
+ versions :: ['tlsv1.2' | 'tlsv1.1' | tlsv1 | sslv3] | ['dtlsv1.2' | dtlsv1],
+ verify :: verify_none | verify_peer,
+ verify_fun, %%:: fun(CertVerifyErrors::term()) -> boolean(),
+ fail_if_no_peer_cert :: boolean(),
+ verify_client_once :: boolean(),
%% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError}
validate_extensions_fun,
- depth, % integer()
- certfile, % file()
- cert, % der_encoded()
- keyfile, % file()
- key, % der_encoded()
- password, %
- cacerts, % [der_encoded()]
- cacertfile, % file()
- dh, % der_encoded()
- dhfile, % file()
+ depth :: integer(),
+ certfile :: binary(),
+ cert :: der_encoded(),
+ keyfile :: binary(),
+ key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', der_encoded()},
+ password :: string(),
+ cacerts :: [der_encoded()],
+ cacertfile :: binary(),
+ dh :: der_encoded(),
+ dhfile :: binary(),
user_lookup_fun, % server option, fun to lookup the user
- psk_identity, % binary
+ psk_identity :: binary(),
srp_identity, % client option {User, Password}
ciphers, %
%% Local policy for the server if it want's to reuse the session
@@ -103,22 +101,30 @@
reuse_session,
%% If false sessions will never be reused, if true they
%% will be reused if possible.
- reuse_sessions, % boolean()
+ reuse_sessions :: boolean(),
renegotiate_at,
secure_renegotiate,
debug,
- hibernate_after,% undefined if not hibernating,
- % or number of ms of inactivity
- % after which ssl_connection will
- % go into hibernation
+ %% undefined if not hibernating, or number of ms of
+ %% inactivity after which ssl_connection will go into
+ %% hibernation
+ hibernate_after :: boolean(),
%% This option should only be set to true by inet_tls_dist
- erl_dist = false,
+ erl_dist = false :: boolean(),
next_protocols_advertised = undefined, %% [binary()],
next_protocol_selector = undefined, %% fun([binary()]) -> binary())
- log_alert,
+ log_alert :: boolean(),
server_name_indication = undefined
}).
+-record(config, {ssl, %% SSL parameters
+ inet_user, %% User set inet options
+ emulated, %% #socket_option{} emulated
+ inet_ssl, %% inet options for internal ssl socket
+ transport_info, %% Callback info
+ connection_cb
+ }).
+
-record(socket_options,
{
mode = list,
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 50a45dc16b..018c8befe0 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -47,7 +47,8 @@
%% Compression
-export([compress/3, uncompress/3, compressions/0]).
--export([is_correct_mac/2]).
+%% Payload encryption/decryption
+-export([cipher/4, decipher/3, is_correct_mac/2]).
%%====================================================================
%% Internal application API
@@ -355,6 +356,41 @@ compressions() ->
[?byte(?NULL)].
%%--------------------------------------------------------------------
+-spec cipher(tls_version(), iolist(), #connection_state{}, MacHash::binary()) ->
+ {CipherFragment::binary(), #connection_state{}}.
+%%
+%% Description: Payload encryption
+%%--------------------------------------------------------------------
+cipher(Version, Fragment,
+ #connection_state{cipher_state = CipherS0,
+ security_parameters=
+ #security_parameters{bulk_cipher_algorithm =
+ BulkCipherAlgo}
+ } = WriteState0, MacHash) ->
+
+ {CipherFragment, CipherS1} =
+ ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version),
+ {CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}.
+%%--------------------------------------------------------------------
+-spec decipher(tls_version(), binary(), #connection_state{}) -> {binary(), binary(), #connection_state{}}.
+%%
+%% Description: Payload decryption
+%%--------------------------------------------------------------------
+decipher(Version, CipherFragment,
+ #connection_state{security_parameters =
+ #security_parameters{bulk_cipher_algorithm =
+ BulkCipherAlgo,
+ hash_size = HashSz},
+ cipher_state = CipherS0
+ } = ReadState) ->
+ case ssl_cipher:decipher(BulkCipherAlgo, HashSz, CipherS0, CipherFragment, Version) of
+ {PlainFragment, Mac, CipherS1} ->
+ CS1 = ReadState#connection_state{cipher_state = CipherS1},
+ {PlainFragment, Mac, CS1};
+ #alert{} = Alert ->
+ Alert
+ end.
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
empty_connection_state(ConnectionEnd) ->
diff --git a/lib/ssl/src/ssl_socket.erl b/lib/ssl/src/ssl_socket.erl
index 4778db2333..1b6e637cd3 100644
--- a/lib/ssl/src/ssl_socket.erl
+++ b/lib/ssl/src/ssl_socket.erl
@@ -1,13 +1,14 @@
-module(ssl_socket).
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
--export([socket/3, setopts/3, getopts/3, peername/2, sockname/2, port/2]).
+-export([socket/4, setopts/3, getopts/3, peername/2, sockname/2, port/2]).
-socket(Pid, Transport, Socket) ->
+socket(Pid, Transport, Socket, ConnectionCb) ->
#sslsocket{pid = Pid,
%% "The name "fd" is keept for backwards compatibility
- fd = {Transport, Socket}}.
+ fd = {Transport, Socket, ConnectionCb}}.
setopts(gen_tcp, Socket, Options) ->
inet:setopts(Socket, Options);
diff --git a/lib/ssl/src/ssl_srp.hrl b/lib/ssl/src/ssl_srp.hrl
index ab2be33ab2..af56a91194 100644
--- a/lib/ssl/src/ssl_srp.hrl
+++ b/lib/ssl/src/ssl_srp.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -23,9 +23,14 @@
%% see RFC 5054
%%----------------------------------------------------------------------
+-ifndef(ssl_srp).
+-define(ssl_srp, true).
+
-record(srp_user, {
generator :: binary(),
prime :: binary(),
salt :: binary(),
verifier :: binary()
}).
+
+-endif. % -ifdef(ssl_srp).
diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl
index 59039a6e0a..77b40a7b38 100644
--- a/lib/ssl/src/ssl_sup.erl
+++ b/lib/ssl/src/ssl_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2011. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -43,22 +43,12 @@ start_link() ->
%%%=========================================================================
init([]) ->
- %% OLD ssl - moved start to ssl.erl only if old
- %% ssl is acctualy run!
- %%Child1 = {ssl_server, {ssl_server, start_link, []},
- %% permanent, 2000, worker, [ssl_server]},
-
- %% Does not start any port programs so it does matter
- %% so much if it is not used!
- %% Child2 = {ssl_broker_sup, {ssl_broker_sup, start_link, []},
- %% permanent, 2000, supervisor, [ssl_broker_sup]},
-
-
- %% New ssl
SessionCertManager = session_and_cert_manager_child_spec(),
- ConnetionManager = connection_manager_child_spec(),
+ TLSConnetionManager = tls_connection_manager_child_spec(),
+ %% Not supported yet
+ %%DTLSConnetionManager = tls_connection_manager_child_spec(),
- {ok, {{one_for_all, 10, 3600}, [SessionCertManager, ConnetionManager]}}.
+ {ok, {{one_for_all, 10, 3600}, [SessionCertManager, TLSConnetionManager]}}.
manager_opts() ->
@@ -90,15 +80,23 @@ session_and_cert_manager_child_spec() ->
Type = worker,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-connection_manager_child_spec() ->
- Name = ssl_connection,
- StartFunc = {ssl_connection_sup, start_link, []},
+tls_connection_manager_child_spec() ->
+ Name = tls_connection,
+ StartFunc = {tls_connection_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
- Modules = [ssl_connection],
+ Modules = [tls_connection, ssl_connection],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
+dtls_connection_manager_child_spec() ->
+ Name = dtls_connection,
+ StartFunc = {dtls_connection_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [dtls_connection, ssl_connection],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
session_cb_init_args() ->
case application:get_env(ssl, session_cb_init_args) of
diff --git a/lib/ssl/src/tls.erl b/lib/ssl/src/tls.erl
index f1747dc69e..3e7b2db9c2 100644
--- a/lib/ssl/src/tls.erl
+++ b/lib/ssl/src/tls.erl
@@ -19,98 +19,15 @@
%%
-%%% Purpose : Main API module for SSL.
+%%% Purpose : Reflect TLS specific API options (fairly simple wrapper at the moment)
-module(tls).
--export([start/0, start/1, stop/0, transport_accept/1,
- transport_accept/2, ssl_accept/1, ssl_accept/2, ssl_accept/3,
- cipher_suites/0, cipher_suites/1, suite_definition/1,
- close/1, shutdown/2,
- connect/3, connect/2, connect/4, connection_info/1,
- controlling_process/2, listen/2, peername/1, peercert/1,
- recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1,
- versions/0, session_info/1, format_error/1,
- renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1, negotiated_next_protocol/1]).
-
+-include("ssl_api.hrl").
-include("ssl_internal.hrl").
--include("ssl_record.hrl").
--include("ssl_cipher.hrl").
--include("ssl_handshake.hrl").
--include("ssl_srp.hrl").
-
--include_lib("public_key/include/public_key.hrl").
-
-%% Visible in API
--export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
- erl_cipher_suite/0, %% From ssl_cipher.hrl
- tls_atom_version/0, %% From ssl_internal.hrl
- prf_random/0, sslsocket/0]).
-
--record(config, {ssl, %% SSL parameters
- inet_user, %% User set inet options
- emulated, %% #socket_option{} emulated
- inet_ssl, %% inet options for internal ssl socket
- cb %% Callback info
- }).
-
--type sslsocket() :: #sslsocket{}.
--type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
--type socket_connect_option() :: gen_tcp:connect_option().
--type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
--type socket_listen_option() :: gen_tcp:listen_option().
--type ssl_option() :: {verify, verify_type()} |
- {verify_fun, {fun(), InitialUserState::term()}} |
- {fail_if_no_peer_cert, boolean()} | {depth, integer()} |
- {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} |
- {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
- {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
- {user_lookup_fun, {fun(), InitialUserState::term()}} |
- {psk_identity, string()} |
- {srp_identity, {string(), string()}} |
- {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
- {reuse_session, fun()} | {hibernate_after, integer()|undefined} |
- {next_protocols_advertised, list(binary())} |
- {client_preferred_next_protocols, binary(), client | server, list(binary())}.
-
--type verify_type() :: verify_none | verify_peer.
--type path() :: string().
--type ciphers() :: [erl_cipher_suite()] |
- string(). % (according to old API)
--type ssl_imp() :: new | old.
-
--type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
- ClosedTag::atom(), ErrTag::atom()}}.
--type prf_random() :: client_random | server_random.
-
-%%--------------------------------------------------------------------
--spec start() -> ok | {error, reason()}.
--spec start(permanent | transient | temporary) -> ok | {error, reason()}.
-%%
-%% Description: Utility function that starts the ssl,
-%% crypto and public_key applications. Default type
-%% is temporary. see application(3)
-%%--------------------------------------------------------------------
-start() ->
- application:start(crypto),
- application:start(asn1),
- application:start(public_key),
- application:start(ssl).
-
-start(Type) ->
- application:start(crypto, Type),
- application:start(asn1),
- application:start(public_key, Type),
- application:start(ssl, Type).
-
-%%--------------------------------------------------------------------
--spec stop() -> ok.
-%%
-%% Description: Stops the ssl application.
-%%--------------------------------------------------------------------
-stop() ->
- application:stop(ssl).
+-export([connect/2, connect/3, listen/2, accept/1, accept/2,
+ handshake/1, handshake/2, handshake/3]).
%%--------------------------------------------------------------------
-spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
@@ -122,44 +39,19 @@ stop() ->
{ok, #sslsocket{}} | {error, reason()}.
%%
-%% Description: Connect to an ssl server.
+%% Description: Connect to an TLS server.
%%--------------------------------------------------------------------
-connect(Socket, SslOptions) when is_port(Socket) ->
- connect(Socket, SslOptions, infinity).
-
-connect(Socket, SslOptions0, Timeout) when is_port(Socket) ->
- {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0,
- {gen_tcp, tcp, tcp_closed, tcp_error}),
- EmulatedOptions = emulated_options(),
- {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions),
- try handle_options(SslOptions0 ++ SocketValues, client) of
- {ok, #config{cb = CbInfo, ssl = SslOptions, emulated = EmOpts}} ->
-
- ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()),
- case ssl_socket:peername(Transport, Socket) of
- {ok, {Address, Port}} ->
- tls_connection:connect(Address, Port, Socket,
- {SslOptions, EmOpts},
- self(), CbInfo, Timeout);
- {error, Error} ->
- {error, Error}
- end
- catch
- _:{error, Reason} ->
- {error, Reason}
- end;
+connect(Socket, Options) when is_port(Socket) ->
+ connect(Socket, Options, infinity).
+connect(Socket, SslOptions, Timeout) when is_port(Socket) ->
+ TLSOpts = [{protocol, tls} | SslOptions],
+ ssl:connect(Socket, TLSOpts, Timeout);
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
-
connect(Host, Port, Options, Timeout) ->
- try handle_options(Options, client) of
- {ok, Config} ->
- do_connect(Host,Port,Config,Timeout)
- catch
- throw:Error ->
- Error
- end.
+ TLSOpts = [{protocol, tls} | Options],
+ ssl:connect(Host, Port, TLSOpts, Timeout).
%%--------------------------------------------------------------------
-spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
@@ -167,884 +59,44 @@ connect(Host, Port, Options, Timeout) ->
%%
%% Description: Creates an ssl listen socket.
%%--------------------------------------------------------------------
-listen(_Port, []) ->
- {error, nooptions};
-listen(Port, Options0) ->
- try
- {ok, Config} = handle_options(Options0, server),
- #config{cb = {Transport, _, _, _}, inet_user = Options} = Config,
- case Transport:listen(Port, Options) of
- {ok, ListenSocket} ->
- {ok, #sslsocket{pid = {ListenSocket, Config}}};
- Err = {error, _} ->
- Err
- end
- catch
- Error = {error, _} ->
- Error
- end.
+listen(Port, Options) ->
+ TLSOpts = [{protocol, tls} | Options],
+ ssl:listen(Port, TLSOpts).
+
%%--------------------------------------------------------------------
--spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
+-spec accept(#sslsocket{}) -> {ok, #sslsocket{}} |
{error, reason()}.
--spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+-spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
{error, reason()}.
%%
%% Description: Performs transport accept on an ssl listen socket
%%--------------------------------------------------------------------
-transport_accept(ListenSocket) ->
- transport_accept(ListenSocket, infinity).
-
-transport_accept(#sslsocket{pid = {ListenSocket, #config{cb = CbInfo, ssl = SslOpts}}}, Timeout) ->
-
- %% The setopt could have been invoked on the listen socket
- %% and options should be inherited.
- EmOptions = emulated_options(),
- {Transport,_,_, _} = CbInfo,
- {ok, SocketValues} = ssl_socket:getopts(Transport, ListenSocket, EmOptions),
- ok = ssl_socket:setopts(Transport, ListenSocket, internal_inet_values()),
- case Transport:accept(ListenSocket, Timeout) of
- {ok, Socket} ->
- ok = ssl_socket:setopts(Transport, ListenSocket, SocketValues),
- {ok, Port} = ssl_socket:port(Transport, Socket),
- ConnArgs = [server, "localhost", Port, Socket,
- {SslOpts, socket_options(SocketValues)}, self(), CbInfo],
- case ssl_connection_sup:start_child(ConnArgs) of
- {ok, Pid} ->
- tls_connection:socket_control(Socket, Pid, Transport);
- {error, Reason} ->
- {error, Reason}
- end;
- {error, Reason} ->
- {error, Reason}
- end.
+accept(ListenSocket) ->
+ accept(ListenSocket, infinity).
+accept(Socket, Timeout) ->
+ ssl:transport_accept(Socket, Timeout).
%%--------------------------------------------------------------------
--spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}.
--spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option()
+-spec handshake(#sslsocket{}) -> ok | {error, reason()}.
+-spec handshake(#sslsocket{} | port(), timeout()| [ssl_option()
| transport_option()]) ->
ok | {ok, #sslsocket{}} | {error, reason()}.
--spec ssl_accept(port(), [ssl_option()| transport_option()], timeout()) ->
+-spec handshake(port(), [ssl_option()| transport_option()], timeout()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
-ssl_accept(ListenSocket) ->
- ssl_accept(ListenSocket, infinity).
-
-ssl_accept(#sslsocket{} = Socket, Timeout) ->
- tls_connection:handshake(Socket, Timeout);
-
-ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
- ssl_accept(ListenSocket, SslOptions, infinity).
-
-ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
- {Transport,_,_,_} =
- proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}),
- EmulatedOptions = emulated_options(),
- {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions),
- try handle_options(SslOptions ++ SocketValues, server) of
- {ok, #config{cb = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
- ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()),
- {ok, Port} = ssl_socket:port(Transport, Socket),
- tls_connection:ssl_accept(Port, Socket,
- {SslOpts, EmOpts},
- self(), CbInfo, Timeout)
- catch
- Error = {error, _Reason} -> Error
- end.
-
-%%--------------------------------------------------------------------
--spec close(#sslsocket{}) -> term().
-%%
-%% Description: Close an ssl connection
-%%--------------------------------------------------------------------
-close(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- tls_connection:close(Pid);
-close(#sslsocket{pid = {ListenSocket, #config{cb={Transport,_, _, _}}}}) ->
- Transport:close(ListenSocket).
-
-%%--------------------------------------------------------------------
--spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}.
-%%
-%% Description: Sends data over the ssl connection
-%%--------------------------------------------------------------------
-send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) ->
- tls_connection:send(Pid, Data);
-send(#sslsocket{pid = {ListenSocket, #config{cb={Transport, _, _, _}}}}, Data) ->
- Transport:send(ListenSocket, Data). %% {error,enotconn}
-
-%%--------------------------------------------------------------------
--spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
--spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
-%%
-%% Description: Receives data when active = false
-%%--------------------------------------------------------------------
-recv(Socket, Length) ->
- recv(Socket, Length, infinity).
-recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid) ->
- tls_connection:recv(Pid, Length, Timeout);
-recv(#sslsocket{pid = {Listen,
- #config{cb={Transport, _, _, _}}}}, _,_) when is_port(Listen)->
- Transport:recv(Listen, 0). %% {error,enotconn}
-
-%%--------------------------------------------------------------------
--spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}.
-%%
-%% Description: Changes process that receives the messages when active = true
-%% or once.
-%%--------------------------------------------------------------------
-controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
- tls_connection:new_user(Pid, NewOwner);
-controlling_process(#sslsocket{pid = {Listen,
- #config{cb={Transport, _, _, _}}}},
- NewOwner) when is_port(Listen),
- is_pid(NewOwner) ->
- Transport:controlling_process(Listen, NewOwner).
-
-%%--------------------------------------------------------------------
--spec connection_info(#sslsocket{}) -> {ok, {tls_atom_version(), erl_cipher_suite()}} |
- {error, reason()}.
-%%
-%% Description: Returns ssl protocol and cipher used for the connection
-%%--------------------------------------------------------------------
-connection_info(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- tls_connection:info(Pid);
-connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
- {error, enotconn}.
-
-%%--------------------------------------------------------------------
--spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
-%%
-%% Description: same as inet:peername/1.
-%%--------------------------------------------------------------------
-peername(#sslsocket{pid = Pid, fd = {Transport, Socket}}) when is_pid(Pid)->
- ssl_socket:peername(Transport, Socket);
-peername(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}) ->
- ssl_socket:peername(Transport, ListenSocket). %% Will return {error, enotconn}
-
-%%--------------------------------------------------------------------
--spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}.
-%%
-%% Description: Returns the peercert.
-%%--------------------------------------------------------------------
-peercert(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- case tls_connection:peer_certificate(Pid) of
- {ok, undefined} ->
- {error, no_peercert};
- Result ->
- Result
- end;
-peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
- {error, enotconn}.
-
-%%--------------------------------------------------------------------
--spec suite_definition(cipher_suite()) -> erl_cipher_suite().
-%%
-%% Description: Return erlang cipher suite definition.
-%%--------------------------------------------------------------------
-suite_definition(S) ->
- {KeyExchange, Cipher, Hash, _} = ssl_cipher:suite_definition(S),
- {KeyExchange, Cipher, Hash}.
-
-%%--------------------------------------------------------------------
--spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}.
-%%
-%% Description: Returns the next protocol that has been negotiated. If no
-%% protocol has been negotiated will return {error, next_protocol_not_negotiated}
-%%--------------------------------------------------------------------
-negotiated_next_protocol(#sslsocket{pid = Pid}) ->
- tls_connection:negotiated_next_protocol(Pid).
-
--spec cipher_suites() -> [erl_cipher_suite()].
--spec cipher_suites(erlang | openssl | all) -> [erl_cipher_suite()] | [string()].
-
-%% Description: Returns all supported cipher suites.
-%%--------------------------------------------------------------------
-cipher_suites() ->
- cipher_suites(erlang).
-
-cipher_suites(erlang) ->
- Version = tls_record:highest_protocol_version([]),
- [suite_definition(S) || S <- ssl_cipher:suites(Version)];
-
-cipher_suites(openssl) ->
- Version = tls_record:highest_protocol_version([]),
- [ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)];
-cipher_suites(all) ->
- Version = tls_record:highest_protocol_version([]),
- Supported = ssl_cipher:suites(Version)
- ++ ssl_cipher:anonymous_suites()
- ++ ssl_cipher:psk_suites(Version)
- ++ ssl_cipher:srp_suites(),
- [suite_definition(S) || S <- Supported].
-
-%%--------------------------------------------------------------------
--spec getopts(#sslsocket{}, [gen_tcp:option_name()]) ->
- {ok, [gen_tcp:option()]} | {error, reason()}.
-%%
-%% Description: Gets options
-%%--------------------------------------------------------------------
-getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) ->
- tls_connection:get_opts(Pid, OptionTags);
-getopts(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}},
- OptionTags) when is_list(OptionTags) ->
- try ssl_socket:getopts(Transport, ListenSocket, OptionTags) of
- {ok, _} = Result ->
- Result;
- {error, InetError} ->
- {error, {options, {socket_options, OptionTags, InetError}}}
- catch
- _:_ ->
- {error, {options, {socket_options, OptionTags}}}
- end;
-getopts(#sslsocket{}, OptionTags) ->
- {error, {options, {socket_options, OptionTags}}}.
-
-%%--------------------------------------------------------------------
--spec setopts(#sslsocket{}, [gen_tcp:option()]) -> ok | {error, reason()}.
-%%
-%% Description: Sets options
-%%--------------------------------------------------------------------
-setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) ->
- try proplists:expand([{binary, [{mode, binary}]},
- {list, [{mode, list}]}], Options0) of
- Options ->
- tls_connection:set_opts(Pid, Options)
- catch
- _:_ ->
- {error, {options, {not_a_proplist, Options0}}}
- end;
-setopts(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}, Options) when is_list(Options) ->
- try ssl_socket:setopts(Transport, ListenSocket, Options) of
- ok ->
- ok;
- {error, InetError} ->
- {error, {options, {socket_options, Options, InetError}}}
- catch
- _:Error ->
- {error, {options, {socket_options, Options, Error}}}
- end;
-setopts(#sslsocket{}, Options) ->
- {error, {options,{not_a_proplist, Options}}}.
+handshake(ListenSocket) ->
+ handshake(ListenSocket, infinity).
-%%---------------------------------------------------------------
--spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}.
-%%
-%% Description: Same as gen_tcp:shutdown/2
-%%--------------------------------------------------------------------
-shutdown(#sslsocket{pid = {Listen, #config{cb={Transport,_, _, _}}}},
- How) when is_port(Listen) ->
- Transport:shutdown(Listen, How);
-shutdown(#sslsocket{pid = Pid}, How) ->
- tls_connection:shutdown(Pid, How).
-
-%%--------------------------------------------------------------------
--spec sockname(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
-%%
-%% Description: Same as inet:sockname/1
-%%--------------------------------------------------------------------
-sockname(#sslsocket{pid = {Listen, #config{cb={Transport,_, _, _}}}}) when is_port(Listen) ->
- ssl_socket:sockname(Transport, Listen);
-
-sockname(#sslsocket{pid = Pid, fd = {Transport, Socket}}) when is_pid(Pid) ->
- ssl_socket:sockname(Transport, Socket).
-
-%%---------------------------------------------------------------
--spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Returns list of session info currently [{session_id, session_id(),
-%% {cipher_suite, cipher_suite()}]
-%%--------------------------------------------------------------------
-session_info(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- tls_connection:session_info(Pid);
-session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
- {error, enotconn}.
-
-%%---------------------------------------------------------------
--spec versions() -> [{ssl_app, string()} | {supported, [tls_atom_version()]} |
- {available, [tls_atom_version()]}].
-%%
-%% Description: Returns a list of relevant versions.
-%%--------------------------------------------------------------------
-versions() ->
- Vsns = tls_record:supported_protocol_versions(),
- SupportedVsns = [tls_record:protocol_version(Vsn) || Vsn <- Vsns],
- AvailableVsns = ?ALL_SUPPORTED_VERSIONS,
- [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}].
-
-
-%%---------------------------------------------------------------
--spec renegotiate(#sslsocket{}) -> ok | {error, reason()}.
-%%
-%% Description: Initiates a renegotiation.
-%%--------------------------------------------------------------------
-renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) ->
- tls_connection:renegotiation(Pid);
-renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
- {error, enotconn}.
-
-%%--------------------------------------------------------------------
--spec prf(#sslsocket{}, binary() | 'master_secret', binary(),
- binary() | prf_random(), non_neg_integer()) ->
- {ok, binary()} | {error, reason()}.
-%%
-%% Description: use a ssl sessions TLS PRF to generate key material
-%%--------------------------------------------------------------------
-prf(#sslsocket{pid = Pid},
- Secret, Label, Seed, WantedLength) when is_pid(Pid) ->
- tls_connection:prf(Pid, Secret, Label, Seed, WantedLength);
-prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) ->
- {error, enotconn}.
-
-%%--------------------------------------------------------------------
--spec clear_pem_cache() -> ok.
-%%
-%% Description: Clear the PEM cache
-%%--------------------------------------------------------------------
-clear_pem_cache() ->
- ssl_manager:clear_pem_cache().
-
-%%---------------------------------------------------------------
--spec format_error({error, term()}) -> list().
-%%
-%% Description: Creates error string.
-%%--------------------------------------------------------------------
-format_error({error, Reason}) ->
- format_error(Reason);
-format_error(Reason) when is_list(Reason) ->
- Reason;
-format_error(closed) ->
- "TLS connection is closed";
-format_error({tls_alert, Description}) ->
- "TLS Alert: " ++ Description;
-format_error({options,{FileType, File, Reason}}) when FileType == cacertfile;
- FileType == certfile;
- FileType == keyfile;
- FileType == dhfile ->
- Error = file_error_format(Reason),
- file_desc(FileType) ++ File ++ ": " ++ Error;
-format_error({options, {socket_options, Option, Error}}) ->
- lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, format_error(Error)]));
-format_error({options, {socket_options, Option}}) ->
- lists:flatten(io_lib:format("Invalid socket option: ~p", [Option]));
-format_error({options, Options}) ->
- lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options]));
-
-format_error(Error) ->
- case inet:format_error(Error) of
- "unknown POSIX" ++ _ ->
- unexpected_format(Error);
- Other ->
- Other
- end.
-
-%%--------------------------------------------------------------------
--spec random_bytes(integer()) -> binary().
-
-%%
-%% Description: Generates cryptographically secure random sequence if possible
-%% fallbacks on pseudo random function
-%%--------------------------------------------------------------------
-random_bytes(N) ->
- try crypto:strong_rand_bytes(N) of
- RandBytes ->
- RandBytes
- catch
- error:low_entropy ->
- crypto:rand_bytes(N)
- end.
-
-%%%--------------------------------------------------------------
-%%% Internal functions
-%%%--------------------------------------------------------------------
-do_connect(Address, Port,
- #config{cb=CbInfo, inet_user=UserOpts, ssl=SslOpts,
- emulated=EmOpts,inet_ssl=SocketOpts},
- Timeout) ->
- {Transport, _, _, _} = CbInfo,
- try Transport:connect(Address, Port, SocketOpts, Timeout) of
- {ok, Socket} ->
- tls_connection:connect(Address, Port, Socket, {SslOpts,EmOpts},
- self(), CbInfo, Timeout);
- {error, Reason} ->
- {error, Reason}
- catch
- exit:{function_clause, _} ->
- {error, {options, {cb_info, CbInfo}}};
- exit:badarg ->
- {error, {options, {socket_options, UserOpts}}};
- exit:{badarg, _} ->
- {error, {options, {socket_options, UserOpts}}}
- end.
-
-handle_options(Opts0, _Role) ->
- Opts = proplists:expand([{binary, [{mode, binary}]},
- {list, [{mode, list}]}], Opts0),
- ReuseSessionFun = fun(_, _, _, _) -> true end,
-
- DefaultVerifyNoneFun =
- {fun(_,{bad_cert, _}, UserState) ->
- {valid, UserState};
- (_,{extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, []},
-
- VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun),
- UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false),
- UserVerifyFun = handle_option(verify_fun, Opts, undefined),
- CaCerts = handle_option(cacerts, Opts, undefined),
-
- {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} =
- %% Handle 0, 1, 2 for backwards compatibility
- case proplists:get_value(verify, Opts, verify_none) of
- 0 ->
- {verify_none, false,
- ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
- 1 ->
- {verify_peer, false,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
- 2 ->
- {verify_peer, true,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
- verify_none ->
- {verify_none, false,
- ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
- verify_peer ->
- {verify_peer, UserFailIfNoPeerCert,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
- Value ->
- throw({error, {options, {verify, Value}}})
- end,
-
- CertFile = handle_option(certfile, Opts, <<>>),
-
- Versions = case handle_option(versions, Opts, []) of
- [] ->
- tls_record:supported_protocol_versions();
- Vsns ->
- [tls_record:protocol_version(Vsn) || Vsn <- Vsns]
- end,
-
- SSLOptions = #ssl_options{
- versions = Versions,
- verify = validate_option(verify, Verify),
- verify_fun = VerifyFun,
- fail_if_no_peer_cert = FailIfNoPeerCert,
- verify_client_once = handle_option(verify_client_once, Opts, false),
- depth = handle_option(depth, Opts, 1),
- cert = handle_option(cert, Opts, undefined),
- certfile = CertFile,
- key = handle_option(key, Opts, undefined),
- keyfile = handle_option(keyfile, Opts, CertFile),
- password = handle_option(password, Opts, ""),
- cacerts = CaCerts,
- cacertfile = handle_option(cacertfile, Opts, CaCertDefault),
- dh = handle_option(dh, Opts, undefined),
- dhfile = handle_option(dhfile, Opts, undefined),
- user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined),
- psk_identity = handle_option(psk_identity, Opts, undefined),
- srp_identity = handle_option(srp_identity, Opts, undefined),
- ciphers = handle_option(ciphers, Opts, []),
- %% Server side option
- reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
- reuse_sessions = handle_option(reuse_sessions, Opts, true),
- secure_renegotiate = handle_option(secure_renegotiate, Opts, false),
- renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT),
- hibernate_after = handle_option(hibernate_after, Opts, undefined),
- erl_dist = handle_option(erl_dist, Opts, false),
- next_protocols_advertised =
- handle_option(next_protocols_advertised, Opts, undefined),
- next_protocol_selector =
- make_next_protocol_selector(
- handle_option(client_preferred_next_protocols, Opts, undefined)),
- log_alert = handle_option(log_alert, Opts, true),
- server_name_indication = handle_option(server_name_indication, Opts, undefined)
- },
-
- CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
- SslOptions = [versions, verify, verify_fun,
- fail_if_no_peer_cert, verify_client_once,
- depth, cert, certfile, key, keyfile,
- password, cacerts, cacertfile, dh, dhfile,
- user_lookup_fun, psk_identity, srp_identity, ciphers,
- reuse_session, reuse_sessions, ssl_imp,
- cb_info, renegotiate_at, secure_renegotiate, hibernate_after,
- erl_dist, next_protocols_advertised,
- client_preferred_next_protocols, log_alert],
+handshake(#sslsocket{} = Socket, Timeout) ->
+ ssl:ssl_accept(Socket, Timeout);
- SockOpts = lists:foldl(fun(Key, PropList) ->
- proplists:delete(Key, PropList)
- end, Opts, SslOptions),
-
- {SSLsock, Emulated} = emulated_options(SockOpts),
- {ok, #config{ssl=SSLOptions, emulated=Emulated, inet_ssl=SSLsock,
- inet_user=SockOpts, cb=CbInfo}}.
-
-handle_option(OptionName, Opts, Default) ->
- validate_option(OptionName,
- proplists:get_value(OptionName, Opts, Default)).
-
-
-validate_option(versions, Versions) ->
- validate_versions(Versions, Versions);
-validate_option(verify, Value)
- when Value == verify_none; Value == verify_peer ->
- Value;
-validate_option(verify_fun, undefined) ->
- undefined;
-%% Backwards compatibility
-validate_option(verify_fun, Fun) when is_function(Fun) ->
- {fun(_,{bad_cert, _} = Reason, OldFun) ->
- case OldFun([Reason]) of
- true ->
- {valid, OldFun};
- false ->
- {fail, Reason}
- end;
- (_,{extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, Fun};
-validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) ->
- Value;
-validate_option(fail_if_no_peer_cert, Value)
- when Value == true; Value == false ->
- Value;
-validate_option(verify_client_once, Value)
- when Value == true; Value == false ->
- Value;
-validate_option(depth, Value) when is_integer(Value),
- Value >= 0, Value =< 255->
- Value;
-validate_option(cert, Value) when Value == undefined;
- is_binary(Value) ->
- Value;
-validate_option(certfile, undefined = Value) ->
- Value;
-validate_option(certfile, Value) when is_binary(Value) ->
- Value;
-validate_option(certfile, Value) when is_list(Value) ->
- list_to_binary(Value);
-
-validate_option(key, undefined) ->
- undefined;
-validate_option(key, {KeyType, Value}) when is_binary(Value),
- KeyType == rsa; %% Backwards compatibility
- KeyType == dsa; %% Backwards compatibility
- KeyType == 'RSAPrivateKey';
- KeyType == 'DSAPrivateKey';
- KeyType == 'PrivateKeyInfo' ->
- {KeyType, Value};
-
-validate_option(keyfile, undefined) ->
- <<>>;
-validate_option(keyfile, Value) when is_binary(Value) ->
- Value;
-validate_option(keyfile, Value) when is_list(Value), Value =/= "" ->
- list_to_binary(Value);
-validate_option(password, Value) when is_list(Value) ->
- Value;
-
-validate_option(cacerts, Value) when Value == undefined;
- is_list(Value) ->
- Value;
-%% certfile must be present in some cases otherwhise it can be set
-%% to the empty string.
-validate_option(cacertfile, undefined) ->
- <<>>;
-validate_option(cacertfile, Value) when is_binary(Value) ->
- Value;
-validate_option(cacertfile, Value) when is_list(Value), Value =/= ""->
- list_to_binary(Value);
-validate_option(dh, Value) when Value == undefined;
- is_binary(Value) ->
- Value;
-validate_option(dhfile, undefined = Value) ->
- Value;
-validate_option(dhfile, Value) when is_binary(Value) ->
- Value;
-validate_option(dhfile, Value) when is_list(Value), Value =/= "" ->
- list_to_binary(Value);
-validate_option(psk_identity, undefined) ->
- undefined;
-validate_option(psk_identity, Identity)
- when is_list(Identity), Identity =/= "", length(Identity) =< 65535 ->
- list_to_binary(Identity);
-validate_option(user_lookup_fun, undefined) ->
- undefined;
-validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) ->
- Value;
-validate_option(srp_identity, undefined) ->
- undefined;
-validate_option(srp_identity, {Username, Password})
- when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 ->
- {list_to_binary(Username), list_to_binary(Password)};
-
-validate_option(ciphers, Value) when is_list(Value) ->
- Version = tls_record:highest_protocol_version([]),
- try cipher_suites(Version, Value)
- catch
- exit:_ ->
- throw({error, {options, {ciphers, Value}}});
- error:_->
- throw({error, {options, {ciphers, Value}}})
- end;
-validate_option(reuse_session, Value) when is_function(Value) ->
- Value;
-validate_option(reuse_sessions, Value) when Value == true;
- Value == false ->
- Value;
-
-validate_option(secure_renegotiate, Value) when Value == true;
- Value == false ->
- Value;
-validate_option(renegotiate_at, Value) when is_integer(Value) ->
- erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
-
-validate_option(hibernate_after, undefined) ->
- undefined;
-validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 ->
- Value;
-validate_option(erl_dist,Value) when Value == true;
- Value == false ->
- Value;
-validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value)
- when is_list(PreferredProtocols) ->
- case tls_record:highest_protocol_version([]) of
- {3,0} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- _ ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- {Precedence, PreferredProtocols, ?NO_PROTOCOL}
- end;
-validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value)
- when is_list(PreferredProtocols), is_binary(Default),
- byte_size(Default) > 0, byte_size(Default) < 256 ->
- case tls_record:highest_protocol_version([]) of
- {3,0} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- _ ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- Value
- end;
-
-validate_option(client_preferred_next_protocols, undefined) ->
- undefined;
-validate_option(log_alert, Value) when Value == true;
- Value == false ->
- Value;
-validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) ->
- case tls_record:highest_protocol_version([]) of
- {3,0} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- _ ->
- validate_binary_list(next_protocols_advertised, Value),
- Value
- end;
-
-validate_option(next_protocols_advertised, undefined) ->
- undefined;
-validate_option(server_name_indication, Value) when is_list(Value) ->
- Value;
-validate_option(server_name_indication, disable) ->
- disable;
-validate_option(server_name_indication, undefined) ->
- undefined;
-validate_option(Opt, Value) ->
- throw({error, {options, {Opt, Value}}}).
-
-validate_npn_ordering(client) ->
- ok;
-validate_npn_ordering(server) ->
- ok;
-validate_npn_ordering(Value) ->
- throw({error, {options, {client_preferred_next_protocols, {invalid_precedence, Value}}}}).
-
-validate_binary_list(Opt, List) ->
- lists:foreach(
- fun(Bin) when is_binary(Bin),
- byte_size(Bin) > 0,
- byte_size(Bin) < 256 ->
- ok;
- (Bin) ->
- throw({error, {options, {Opt, {invalid_protocol, Bin}}}})
- end, List).
-
-validate_versions([], Versions) ->
- Versions;
-validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1;
- Version == sslv3 ->
- validate_versions(Rest, Versions);
-validate_versions([Ver| _], Versions) ->
- throw({error, {options, {Ver, {versions, Versions}}}}).
-
-validate_inet_option(mode, Value)
- when Value =/= list, Value =/= binary ->
- throw({error, {options, {mode,Value}}});
-validate_inet_option(packet, Value)
- when not (is_atom(Value) orelse is_integer(Value)) ->
- throw({error, {options, {packet,Value}}});
-validate_inet_option(packet_size, Value)
- when not is_integer(Value) ->
- throw({error, {options, {packet_size,Value}}});
-validate_inet_option(header, Value)
- when not is_integer(Value) ->
- throw({error, {options, {header,Value}}});
-validate_inet_option(active, Value)
- when Value =/= true, Value =/= false, Value =/= once ->
- throw({error, {options, {active,Value}}});
-validate_inet_option(_, _) ->
- ok.
-
-%% The option cacerts overrides cacertsfile
-ca_cert_default(_,_, [_|_]) ->
- undefined;
-ca_cert_default(verify_none, _, _) ->
- undefined;
-ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) ->
- undefined;
-%% Server that wants to verify_peer and has no verify_fun must have
-%% some trusted certs.
-ca_cert_default(verify_peer, undefined, _) ->
- "".
-
-emulated_options() ->
- [mode, packet, active, header, packet_size].
-
-internal_inet_values() ->
- [{packet_size,0},{packet, 0},{header, 0},{active, false},{mode,binary}].
-
-socket_options(InetValues) ->
- #socket_options{
- mode = proplists:get_value(mode, InetValues, lists),
- header = proplists:get_value(header, InetValues, 0),
- active = proplists:get_value(active, InetValues, active),
- packet = proplists:get_value(packet, InetValues, 0),
- packet_size = proplists:get_value(packet_size, InetValues)
- }.
-
-emulated_options(Opts) ->
- emulated_options(Opts, internal_inet_values(), #socket_options{}).
-
-emulated_options([{mode,Opt}|Opts], Inet, Emulated) ->
- validate_inet_option(mode,Opt),
- emulated_options(Opts, Inet, Emulated#socket_options{mode=Opt});
-emulated_options([{header,Opt}|Opts], Inet, Emulated) ->
- validate_inet_option(header,Opt),
- emulated_options(Opts, Inet, Emulated#socket_options{header=Opt});
-emulated_options([{active,Opt}|Opts], Inet, Emulated) ->
- validate_inet_option(active,Opt),
- emulated_options(Opts, Inet, Emulated#socket_options{active=Opt});
-emulated_options([{packet,Opt}|Opts], Inet, Emulated) ->
- validate_inet_option(packet,Opt),
- emulated_options(Opts, Inet, Emulated#socket_options{packet=Opt});
-emulated_options([{packet_size,Opt}|Opts], Inet, Emulated) ->
- validate_inet_option(packet_size,Opt),
- emulated_options(Opts, Inet, Emulated#socket_options{packet_size=Opt});
-emulated_options([Opt|Opts], Inet, Emulated) ->
- emulated_options(Opts, [Opt|Inet], Emulated);
-emulated_options([], Inet,Emulated) ->
- {Inet, Emulated}.
-
-cipher_suites(Version, []) ->
- ssl_cipher:suites(Version);
-cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility
- Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0],
- cipher_suites(Version, Ciphers);
-cipher_suites(Version, [{_,_,_}| _] = Ciphers0) ->
- Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0],
- cipher_suites(Version, Ciphers);
-
-cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
- Supported0 = ssl_cipher:suites(Version)
- ++ ssl_cipher:anonymous_suites()
- ++ ssl_cipher:psk_suites(Version)
- ++ ssl_cipher:srp_suites(),
- Supported = ssl_cipher:filter_suites(Supported0),
- case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of
- [] ->
- Supported;
- Ciphers ->
- Ciphers
- end;
-cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) ->
- %% Format: ["RC4-SHA","RC4-MD5"]
- Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0],
- cipher_suites(Version, Ciphers);
-cipher_suites(Version, Ciphers0) ->
- %% Format: "RC4-SHA:RC4-MD5"
- Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")],
- cipher_suites(Version, Ciphers).
-
-unexpected_format(Error) ->
- lists:flatten(io_lib:format("Unexpected error: ~p", [Error])).
-
-file_error_format({error, Error})->
- case file:format_error(Error) of
- "unknown POSIX error" ->
- "decoding error";
- Str ->
- Str
- end;
-file_error_format(_) ->
- "decoding error".
-
-file_desc(cacertfile) ->
- "Invalid CA certificate file ";
-file_desc(certfile) ->
- "Invalid certificate file ";
-file_desc(keyfile) ->
- "Invalid key file ";
-file_desc(dhfile) ->
- "Invalid DH params file ".
-
-detect(_Pred, []) ->
- undefined;
-detect(Pred, [H|T]) ->
- case Pred(H) of
- true ->
- H;
- _ ->
- detect(Pred, T)
- end.
-
-make_next_protocol_selector(undefined) ->
- undefined;
-make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) ->
- fun(AdvertisedProtocols) ->
- case detect(fun(PreferredProtocol) ->
- lists:member(PreferredProtocol, AdvertisedProtocols)
- end, AllProtocols) of
- undefined ->
- DefaultProtocol;
- PreferredProtocol ->
- PreferredProtocol
- end
- end;
+handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+ handshake(ListenSocket, SslOptions, infinity).
-make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) ->
- fun(AdvertisedProtocols) ->
- case detect(fun(PreferredProtocol) ->
- lists:member(PreferredProtocol, AllProtocols)
- end,
- AdvertisedProtocols) of
- undefined ->
- DefaultProtocol;
- PreferredProtocol ->
- PreferredProtocol
- end
- end.
+handshake(Socket, SslOptions, Timeout) when is_port(Socket) ->
+ ssl:ssl_accept(Socket, SslOptions, Timeout).
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 39595b4f95..8e6f80da1e 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,237 +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()
- }).
-
--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
%%====================================================================
%%--------------------------------------------------------------------
@@ -296,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{
@@ -325,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),
@@ -353,59 +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}},
State = #state{connection_states = ConnectionStates0,
- port = Port,
- session = #session{own_certificate = Cert} = Session0,
+ port = Port, session = #session{own_certificate = Cert} = Session0,
renegotiation = {Renegotiation, _},
session_cache = Cache,
session_cache_cb = CacheCb,
@@ -413,430 +209,59 @@ hello(Hello = #client_hello{client_version = ClientVersion,
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, 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});
+ {Version, {Type, Session},
+ ConnectionStates,
+ #hello_extensions{ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves} = 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
@@ -846,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
@@ -875,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
@@ -1071,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
@@ -1102,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
@@ -1148,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}
@@ -1197,1094 +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,
- session = #session{ecc = Curve}
- } = State)
- when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa;
- Algo == ecdh_anon ->
-
- ECDHKeys = public_key:generate_key(Curve),
- 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
@@ -2340,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(_, _, <<>>) ->
@@ -2444,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)).
@@ -2452,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);
@@ -2485,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;
@@ -2786,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),
@@ -2836,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) ->
@@ -2855,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
@@ -2886,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
@@ -2968,129 +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}.
-
-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.
diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl
new file mode 100644
index 0000000000..2beecbb84d
--- /dev/null
+++ b/lib/ssl/src/tls_connection.hrl
@@ -0,0 +1,38 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: SSL/TLS specific state
+%%----------------------------------------------------------------------
+
+-ifndef(tls_connection).
+-define(tls_connection, true).
+
+-include("ssl_connection.hrl").
+-include("tls_record.hrl").
+
+-record(protocol_buffers, {
+ tls_packets = [], %% :: [#ssl_tls{}], % Not yet handled decode SSL/TLS packets.
+ tls_record_buffer = <<>>, %% :: binary(), % Buffer of incomplete records
+ tls_handshake_buffer = <<>>, %% :: binary(), % Buffer of incomplete handshakes
+ tls_cipher_texts = [] %%:: [binary()]
+ }).
+
+-endif. % -ifdef(tls_connection).
diff --git a/lib/ssl/src/ssl_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl
index fb1c6e11a6..6f0d8a7262 100644
--- a/lib/ssl/src/ssl_connection_sup.erl
+++ b/lib/ssl/src/tls_connection_sup.erl
@@ -19,9 +19,9 @@
%%
%%----------------------------------------------------------------------
-%% Purpose: The top supervisor for the ftp hangs under inets_sup.
+%% Purpose: Supervisor for a SSL/TLS connection
%%----------------------------------------------------------------------
--module(ssl_connection_sup).
+-module(tls_connection_sup).
-behaviour(supervisor).
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 262f2d929f..003614b448 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -26,15 +26,12 @@
-include("tls_handshake.hrl").
-include("tls_record.hrl").
--include("ssl_cipher.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
--include("ssl_srp.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([client_hello/8, server_hello/4, hello/4,
- get_tls_handshake/3, encode_handshake/2, decode_handshake/3,
- init_handshake_history/0, update_handshake_history/2]).
+-export([client_hello/8, hello/4,
+ get_tls_handshake/3, encode_handshake/2, decode_handshake/3]).
%%====================================================================
%% Internal application API
@@ -70,25 +67,6 @@ client_hello(Host, Port, ConnectionStates,
}.
%%--------------------------------------------------------------------
--spec server_hello(binary(), tls_version(), #connection_states{},
- #hello_extensions{}) -> #server_hello{}.
-%%
-%% Description: Creates a server hello message.
-%%--------------------------------------------------------------------
-server_hello(SessionId, Version, ConnectionStates, Extensions) ->
- Pending = ssl_record:pending_connection_state(ConnectionStates, read),
- SecParams = Pending#connection_state.security_parameters,
-
- #server_hello{server_version = Version,
- cipher_suite = SecParams#security_parameters.cipher_suite,
- compression_method =
- SecParams#security_parameters.compression_algorithm,
- random = SecParams#security_parameters.server_random,
- session_id = SessionId,
- extensions = Extensions
- }.
-
-%%--------------------------------------------------------------------
-spec hello(#server_hello{} | #client_hello{}, #ssl_options{},
#connection_states{} | {inet:port_number(), #session{}, db_handle(),
atom(), #connection_states{}, binary() | undefined},
@@ -167,36 +145,8 @@ get_tls_handshake(Version, Data, Buffer) ->
get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), []).
%%--------------------------------------------------------------------
--spec init_handshake_history() -> tls_handshake_history().
-
-%%
-%% Description: Initialize the empty handshake history buffer.
-%%--------------------------------------------------------------------
-init_handshake_history() ->
- {[], []}.
-
-%%--------------------------------------------------------------------
--spec update_handshake_history(tls_handshake_history(), Data ::term()) ->
- tls_handshake_history().
-%%
-%% Description: Update the handshake history buffer with Data.
+%%% Internal functions
%%--------------------------------------------------------------------
-update_handshake_history(Handshake, % special-case SSL2 client hello
- <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor),
- ?UINT16(CSLength), ?UINT16(0),
- ?UINT16(CDLength),
- CipherSuites:CSLength/binary,
- ChallengeData:CDLength/binary>>) ->
- update_handshake_history(Handshake,
- <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor),
- ?UINT16(CSLength), ?UINT16(0),
- ?UINT16(CDLength),
- CipherSuites:CSLength/binary,
- ChallengeData:CDLength/binary>>);
-update_handshake_history({Handshake0, _Prev}, Data) ->
- {[Data|Handshake0], Handshake0}.
-
-
get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
Body:Length/binary,Rest/binary>>, Acc) ->
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
@@ -205,10 +155,6 @@ get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
get_tls_handshake_aux(_Version, Data, Acc) ->
{lists:reverse(Acc), Data}.
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-
decode_handshake(_, ?HELLO_REQUEST, <<>>) ->
#hello_request{};
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 54cf8d0b80..88107557a0 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -121,17 +121,20 @@ get_tls_records_aux(Data, Acc) ->
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
end.
-encode_plain_text(Type, Version, Data, ConnectionStates) ->
- #connection_states{current_write=#connection_state{
- compression_state=CompS0,
- security_parameters=
- #security_parameters{compression_algorithm=CompAlg}
- }=CS0} = ConnectionStates,
+encode_plain_text(Type, Version, Data,
+ #connection_states{current_write =
+ #connection_state{
+ sequence_number = Seq,
+ compression_state=CompS0,
+ security_parameters=
+ #security_parameters{compression_algorithm=CompAlg}
+ }= WriteState0} = ConnectionStates) ->
{Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
- CS1 = CS0#connection_state{compression_state = CompS1},
- {CipherFragment, CS2} = cipher(Type, Version, Comp, CS1),
- CTBin = encode_tls_cipher_text(Type, Version, CipherFragment),
- {CTBin, ConnectionStates#connection_states{current_write = CS2}}.
+ WriteState1 = WriteState0#connection_state{compression_state = CompS1},
+ MacHash = calc_mac_hash(Type, Version, Comp, WriteState1),
+ {CipherFragment, WriteState} = ssl_record:cipher(Version, Comp, WriteState1, MacHash),
+ CipherText = encode_tls_cipher_text(Type, Version, CipherFragment),
+ {CipherText, ConnectionStates#connection_states{current_write = WriteState#connection_state{sequence_number = Seq +1}}}.
%%--------------------------------------------------------------------
-spec decode_cipher_text(#ssl_tls{}, #connection_states{}) ->
@@ -143,19 +146,23 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version,
fragment = CipherFragment} = CipherText, ConnnectionStates0) ->
ReadState0 = ConnnectionStates0#connection_states.current_read,
#connection_state{compression_state = CompressionS0,
+ sequence_number = Seq,
security_parameters = SecParams} = ReadState0,
CompressAlg = SecParams#security_parameters.compression_algorithm,
- case decipher(Type, Version, CipherFragment, ReadState0) of
- {PlainFragment, ReadState1} ->
- {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg,
- PlainFragment, CompressionS0),
- ConnnectionStates = ConnnectionStates0#connection_states{
- current_read = ReadState1#connection_state{
- compression_state = CompressionS1}},
- {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
- #alert{} = Alert ->
- Alert
- end.
+ {PlainFragment, Mac, ReadState1} = ssl_record:decipher(Version, CipherFragment, ReadState0),
+ MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState1),
+ case ssl_record:is_correct_mac(Mac, MacHash) of
+ true ->
+ {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg,
+ PlainFragment, CompressionS0),
+ ConnnectionStates = ConnnectionStates0#connection_states{
+ current_read = ReadState1#connection_state{
+ sequence_number = Seq + 1,
+ compression_state = CompressionS1}},
+ {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end.
%%--------------------------------------------------------------------
-spec protocol_version(tls_atom_version() | tls_version()) ->
@@ -280,39 +287,6 @@ encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) ->
Length = erlang:iolist_size(Fragment),
[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment].
-cipher(Type, Version, Fragment,
- #connection_state{cipher_state = CipherS0,
- sequence_number = SeqNo,
- security_parameters=
- #security_parameters{bulk_cipher_algorithm =
- BCA}
- } = WriteState0) ->
- MacHash = calc_mac_hash(Type, Version, Fragment, WriteState0),
- {CipherFragment, CipherS1} =
- ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment, Version),
- WriteState = WriteState0#connection_state{cipher_state=CipherS1},
- {CipherFragment, WriteState#connection_state{sequence_number = SeqNo+1}}.
-
-decipher(Type, Version, CipherFragment,
- #connection_state{sequence_number = SeqNo} = ReadState) ->
- SP = ReadState#connection_state.security_parameters,
- BCA = SP#security_parameters.bulk_cipher_algorithm,
- HashSz = SP#security_parameters.hash_size,
- CipherS0 = ReadState#connection_state.cipher_state,
- case ssl_cipher:decipher(BCA, HashSz, CipherS0, CipherFragment, Version) of
- {PlainFragment, Mac, CipherS1} ->
- CS1 = ReadState#connection_state{cipher_state = CipherS1},
- MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState),
- case ssl_record:is_correct_mac(Mac, MacHash) of
- true ->
- {PlainFragment,
- CS1#connection_state{sequence_number = SeqNo+1}};
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
- end;
- #alert{} = Alert ->
- Alert
- end.
mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
_Length, _Fragment) ->
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index cb919baf4e..244eb5ce0a 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -57,6 +57,7 @@ ERL_FILES = $(MODULES:%=%.erl)
HRL_FILES =
HRL_FILES_SRC = \
+ ssl_api.hrl\
ssl_internal.hrl\
ssl_alert.hrl \
tls_handshake.hrl \
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index b8849d5cbd..54029ebe6d 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -27,6 +27,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
@@ -2489,7 +2490,10 @@ ssl_accept_timeout(Config) ->
ssl_test_lib:check_result(Server, {error, timeout}),
receive
{'EXIT', Server, _} ->
- [] = supervisor:which_children(ssl_connection_sup)
+ %% Make sure supervisor had time to react on process exit
+ %% Could we come up with a better solution to this?
+ ct:sleep(500),
+ [] = supervisor:which_children(tls_connection_sup)
end
end.
@@ -2645,7 +2649,7 @@ tcp_error_propagation_in_active_mode(Config) when is_list(Config) ->
{status, _, _, StatusInfo} = sys:get_status(Pid),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- Socket = element(10, State),
+ Socket = element(11, State),
%% Fake tcp error
Pid ! {tcp_error, Socket, etimedout},
diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
index f76c55f670..14047c6e9c 100644
--- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl
+++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
@@ -250,10 +250,15 @@ server_require_peer_cert_fail(Config) when is_list(Config) ->
{host, Hostname},
{from, self()},
{options, [{active, false} | BadClientOpts]}]),
-
- ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}},
- Client, {error, {tls_alert, "handshake failure"}}).
-
+ receive
+ {Server, {error, {tls_alert, "handshake failure"}}} ->
+ receive
+ {Client, {error, {tls_alert, "handshake failure"}}} ->
+ ok;
+ {Client, {error, closed}} ->
+ ok
+ end
+ end.
%%--------------------------------------------------------------------
verify_fun_always_run_client() ->
@@ -827,9 +832,16 @@ unknown_server_ca_fail(Config) when is_list(Config) ->
[{verify, verify_peer},
{verify_fun, FunAndState}
| ClientOpts]}]),
+ receive
+ {Server, {error, {tls_alert, "unknown ca"}}} ->
+ receive
+ {Client, {error, {tls_alert, "unknown ca"}}} ->
+ ok;
+ {Client, {error, closed}} ->
+ ok
+ end
+ end.
- ssl_test_lib:check_result(Server, {error, {tls_alert, "unknown ca"}},
- Client, {error, {tls_alert, "unknown ca"}}).
%%--------------------------------------------------------------------
unknown_server_ca_accept_verify_none() ->
diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl
index 7bfd678f4b..d3b523ca8c 100644
--- a/lib/ssl/test/ssl_dist_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_SUITE.erl
@@ -95,7 +95,7 @@ common_init(Case, Config) ->
end_per_testcase(Case, Config) when is_list(Config) ->
Flags = proplists:get_value(old_flags, Config),
- os:putenv("ERL_FLAGS", Flags),
+ catch os:putenv("ERL_FLAGS", Flags),
common_end(Case, Config).
common_end(_, Config) ->
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index 27e1090114..68ff9172e9 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -82,11 +82,11 @@ encode_and_decode_npn_server_hello_test(_Config) ->
%%--------------------------------------------------------------------
create_server_hello_with_no_advertised_protocols_test(_Config) ->
- Hello = tls_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #hello_extensions{}),
+ Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #hello_extensions{}),
undefined = (Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation.
%%--------------------------------------------------------------------
create_server_hello_with_advertised_protocols_test(_Config) ->
- Hello = tls_handshake:server_hello(<<>>, {3, 0}, create_connection_states(),
+ Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(),
#hello_extensions{next_protocol_negotiation = [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]}),
[<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>] =
(Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation.
diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl
index b576b8f70d..21f0172dba 100644
--- a/lib/ssl/test/ssl_to_openssl_SUITE.erl
+++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl
@@ -897,8 +897,11 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) ->
OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
true = port_command(OpenSslPort, Data),
+
+ ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]),
receive
- {'EXIT', OpenSslPort, _} ->
+ {'EXIT', OpenSslPort, _} = Exit ->
+ ct:log("Received: ~p ~n", [Exit]),
ok
end,
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index 78424ee578..a2dd3f5930 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 5.3.1
+SSL_VSN = 5.3.2