diff options
Diffstat (limited to 'lib/ssl')
39 files changed, 4315 insertions, 4060 deletions
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 6029a09730..1d74faf1b3 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -52,6 +52,8 @@ <item>CRL and policy certificate extensions are not supported yet. However CRL verification is supported by public_key, only not integrated in ssl yet. </item> + <item>Support for 'Server Name Indication' extension client side + (RFC 6066 section 3).</item> </list> </section> @@ -74,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()} | @@ -86,8 +88,8 @@ {user_lookup_fun, {fun(), term()}}, {psk_identity, string()}, {srp_identity, {string(), string()}} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()} {next_protocols_advertised, [binary()]} | - {client_preferred_next_protocols, client | server, [binary()]} | - {log_alert, boolean()} + {client_preferred_next_protocols, {client | server, [binary()]} | {client | server, [binary()], binary()}} | + {log_alert, boolean()} | {server_name_indication, hostname() | disable} </c></p> <p><c>transportoption() = {cb_info, {CallbackModule::atom(), DataTag::atom(), ClosedTag::atom(), ErrTag:atom()}} @@ -353,8 +355,8 @@ fun(srp, Username :: string(), UserState :: term()) -> when possible. </item> - <tag>{client_preferred_next_protocols, Precedence :: server | client, ClientPrefs :: [binary()]}</tag> - <tag>{client_preferred_next_protocols, Precedence :: server | client, ClientPrefs :: [binary()], Default :: binary()}</tag> + <tag>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()]}}</tag> + <tag>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()], Default :: binary()}}</tag> <item> <p>Indicates the client will try to perform Next Protocol Negotiation.</p> @@ -382,6 +384,15 @@ fun(srp, Username :: string(), UserState :: term()) -> <tag>{srp_identity, {Username :: string(), Password :: string()}</tag> <item>Specifies the Username and Password to use to authenticate to the server. </item> + <tag>{server_name_indication, hostname()}</tag> + <tag>{server_name_indication, disable}</tag> + <item> + <p>This option can be specified when upgrading a TCP socket to a TLS + socket to use the TLS Server Name Indication extension.</p> + <p>When starting a TLS connection without upgrade the Server Name + Indication extension will be sent if possible, this option may also be + used to disable that behavior.</p> + </item> </taglist> </section> 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 26e8ce7503..ec7f21bd35 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{}, @@ -46,7 +59,7 @@ client_hello(Host, Port, Cookie, ConnectionStates, SecParams = Pending#connection_state.security_parameters, CipherSuites = ssl_handshake:available_suites(UserSuites, Version), - Extensions = ssl_handshake:client_hello_extensions(Version, CipherSuites, + Extensions = ssl_handshake:client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), @@ -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/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index 57c859bf24..7367b5c224 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -95,11 +95,6 @@ do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> end. close(Socket) -> - try - erlang:error(foo) - catch _:_ -> - io:format("close called ~p ~p~n",[Socket, erlang:get_stacktrace()]) - end, gen_tcp:close(Socket), ok. 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 6513042e98..b2077c662a 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -34,7 +34,7 @@ -export([security_parameters/2, security_parameters/3, suite_definition/1, decipher/5, cipher/5, - suite/1, suites/1, anonymous_suites/0, psk_suites/1, srp_suites/0, + suite/1, suites/1, ec_keyed_suites/0, anonymous_suites/0, psk_suites/1, srp_suites/0, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2]). @@ -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 29a8996bd6..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, @@ -49,18 +51,18 @@ ]). %% Cipher suites handling --export([available_suites/2, available_suites/3, cipher_suites/2, - select_session/10]). +-export([available_suites/2, cipher_suites/2, + select_session/10, supported_ecc/1]). %% Extensions handling --export([client_hello_extensions/5, +-export([client_hello_extensions/6, handle_client_hello_extensions/8, %% Returns server hello extensions - handle_server_hello_extensions/9 + handle_server_hello_extensions/9, select_curve/2 ]). %% 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. @@ -85,11 +106,11 @@ hello_request() -> server_hello_done() -> #server_hello_done{}. -client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> +client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> {EcPointFormats, EllipticCurves} = case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of true -> - ecc_extensions(tls_v1, Version); + client_ecc_extensions(tls_v1, Version); false -> {undefined, undefined} end, @@ -104,7 +125,8 @@ client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renego elliptic_curves = EllipticCurves, next_protocol_negotiation = encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, - Renegotiation)}. + Renegotiation), + sni = sni(Host, SslOpts#ssl_options.server_name_indication)}. %%-------------------------------------------------------------------- -spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. @@ -293,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{}. @@ -404,14 +442,114 @@ verify_connection(Version, #finished{verify_data = Data}, _ -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) end. + %%-------------------------------------------------------------------- --spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). +-spec init_handshake_history() -> tls_handshake_history(). %% -%% Description: Public key decryption using the private key. +%% Description: Initialize the empty handshake history buffer. %%-------------------------------------------------------------------- -decrypt_premaster_secret(Secret, RSAPrivateKey) -> - try public_key:decrypt_private(Secret, RSAPrivateKey, +init_handshake_history() -> + {[], []}. + +%%-------------------------------------------------------------------- +-spec update_handshake_history(tls_handshake_history(), Data ::term()) -> + tls_handshake_history(). +%% +%% Description: Update the handshake history buffer with Data. +%%-------------------------------------------------------------------- +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 _:_ -> @@ -463,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 @@ -482,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}; @@ -641,7 +781,19 @@ encode_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Res ListLen = byte_size(SignAlgoList), Len = ListLen + 2, encode_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT), - ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>). + ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>); +encode_hello_extensions([#sni{hostname = Hostname} | Rest], Acc) -> + HostLen = length(Hostname), + HostnameBin = list_to_binary(Hostname), + % Hostname type (1 byte) + Hostname length (2 bytes) + Hostname (HostLen bytes) + ServerNameLength = 1 + 2 + HostLen, + % ServerNameListSize (2 bytes) + ServerNameLength + ExtLength = 2 + ServerNameLength, + encode_hello_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength), + ?UINT16(ServerNameLength), + ?BYTE(?SNI_NAMETYPE_HOST_NAME), + ?UINT16(HostLen), HostnameBin/binary, + Acc/binary>>). enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, ClientRandom, ServerRandom, PrivateKey) -> @@ -861,22 +1013,29 @@ available_suites(UserSuites, Version) -> UserSuites end. -available_suites(ServerCert, UserSuites, Version) -> - ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)). +available_suites(ServerCert, UserSuites, Version, Curve) -> + ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)) + -- unavailable_ecc_suites(Curve). + +unavailable_ecc_suites(no_curve) -> + ssl_cipher:ec_keyed_suites(); +unavailable_ecc_suites(_) -> + []. cipher_suites(Suites, false) -> [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; cipher_suites(Suites, true) -> Suites. -select_session(SuggestedSessionId, CipherSuites, Compressions, Port, Session, Version, +select_session(SuggestedSessionId, CipherSuites, Compressions, Port, #session{ecc = ECCCurve} = + Session, Version, #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) -> {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId, SslOpts, Cert, Cache, CacheCb), - Suites = ssl_handshake:available_suites(Cert, UserSuites, Version), case Resumed of undefined -> + Suites = available_suites(Cert, UserSuites, Version, ECCCurve), CipherSuite = select_cipher_suite(CipherSuites, Suites), Compression = select_compression(Compressions), {new, Session#session{session_id = SessionId, @@ -886,6 +1045,12 @@ select_session(SuggestedSessionId, CipherSuites, Compressions, Port, Session, Ve {resumed, Resumed} 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, _, _, _}) @@ -926,9 +1091,8 @@ certificate_authorities_from_db(CertDbHandle, CertDbRef) -> handle_client_hello_extensions(RecordCB, Random, #hello_extensions{renegotiation_info = Info, srp = SRP, - next_protocol_negotiation = NextProtocolNegotiation, - ec_point_formats = EcPointFormats0, - elliptic_curves = EllipticCurves0}, Version, + ec_point_formats = ECCFormat, + next_protocol_negotiation = NextProtocolNegotiation}, Version, #ssl_options{secure_renegotiate = SecureRenegotation} = Opts, #session{cipher_suite = CipherSuite, compression_method = Compression} = Session0, ConnectionStates0, Renegotiation) -> @@ -937,12 +1101,11 @@ handle_client_hello_extensions(RecordCB, Random, Random, CipherSuite, Compression, ConnectionStates0, Renegotiation, SecureRenegotation), ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), - {EcPointFormats, EllipticCurves} = handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0), + ServerHelloExtensions = #hello_extensions{ renegotiation_info = renegotiation_info(RecordCB, server, ConnectionStates, Renegotiation), - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, + ec_point_formats = server_ecc_extension(Version, ECCFormat), next_protocol_negotiation = encode_protocols_advertised_on_server(ProtocolsToAdvertise) }, @@ -1069,16 +1232,17 @@ hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo, hash_signs = HashSigns, ec_point_formats = EcPointFormats, elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}) -> + next_protocol_negotiation = NextProtocolNegotiation, + sni = Sni}) -> [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns, - EcPointFormats,EllipticCurves, NextProtocolNegotiation], Ext =/= undefined]. + EcPointFormats, EllipticCurves, NextProtocolNegotiation, Sni], Ext =/= undefined]. srp_user(#ssl_options{srp_identity = {UserName, _}}) -> #srp{username = UserName}; srp_user(_) -> undefined. -ecc_extensions(Module, Version) -> +client_ecc_extensions(Module, Version) -> CryptoSupport = proplists:get_value(public_keys, crypto:supports()), case proplists:get_bool(ecdh, CryptoSupport) of true -> @@ -1089,15 +1253,13 @@ ecc_extensions(Module, Version) -> {undefined, undefined} end. -handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0) -> +server_ecc_extension(_Version, EcPointFormats) -> CryptoSupport = proplists:get_value(public_keys, crypto:supports()), case proplists:get_bool(ecdh, CryptoSupport) of true -> - EcPointFormats1 = handle_ecc_point_fmt_extension(EcPointFormats0), - EllipticCurves1 = handle_ecc_curves_extension(Version, EllipticCurves0), - {EcPointFormats1, EllipticCurves1}; - _ -> - {undefined, undefined} + handle_ecc_point_fmt_extension(EcPointFormats); + false -> + undefined end. handle_ecc_point_fmt_extension(undefined) -> @@ -1105,11 +1267,6 @@ handle_ecc_point_fmt_extension(undefined) -> handle_ecc_point_fmt_extension(_) -> #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. -handle_ecc_curves_extension(_Version, undefined) -> - undefined; -handle_ecc_curves_extension(Version, _) -> - #elliptic_curves{elliptic_curve_list = tls_v1:ecc_curves(Version)}. - advertises_ec_ciphers([]) -> false; advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) -> @@ -1124,7 +1281,36 @@ advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) -> true; advertises_ec_ciphers([_| Rest]) -> advertises_ec_ciphers(Rest). - +select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, + #elliptic_curves{elliptic_curve_list = ServerCurves}) -> + select_curve(ClientCurves, ServerCurves); +select_curve(undefined, _) -> + %% Client did not send ECC extension use default curve if + %% ECC cipher is negotiated + {namedCurve, ?secp256k1}; +select_curve(_, []) -> + no_curve; +select_curve(Curves, [Curve| Rest]) -> + case lists:member(Curve, Curves) of + true -> + {namedCurve, Curve}; + false -> + select_curve(Curves, Rest) + end. +%% RFC 6066, Section 3: Currently, the only server names supported are +%% DNS hostnames +sni(_, disable) -> + undefined; +sni(Host, undefined) -> + sni1(Host); +sni(_Host, SNIOption) -> + sni1(SNIOption). + +sni1(Hostname) -> + case inet_parse:domain(Hostname) of + false -> undefined; + true -> #sni{hostname = Hostname} + end. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -1648,3 +1834,32 @@ advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 -> ({Hash, _}) -> proplists:get_bool(Hash, Hashs) end, HashSigns)}; 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_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 3a3ad8cf35..75160526b9 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -45,7 +45,8 @@ master_secret, srp_username, is_resumable, - time_stamp + time_stamp, + ecc }). -define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3 @@ -97,7 +98,8 @@ next_protocol_negotiation = undefined, % [binary()] srp, ec_point_formats, - elliptic_curves + elliptic_curves, + sni }). -record(server_hello, { @@ -338,6 +340,19 @@ -define(NAMED_CURVE, 3). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Server name indication RFC 6066 section 3 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SNI_EXT, 16#0000). + +%% enum { host_name(0), (255) } NameType; +-define(SNI_NAMETYPE_HOST_NAME, 0). + +-record(sni, { + hostname = undefined + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Dialyzer types %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -352,6 +367,3 @@ -endif. % -ifdef(ssl_handshake). - - - diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 96e3280fb5..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,21 +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 b220a48f73..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,877 +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) - }, - - 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(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 5618837506..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,238 +68,69 @@ abbreviated/2, connection/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). --record(state, { - role, % client | server - user_application, % {MonitorRef, pid()} - transport_cb, % atom() - callback module - data_tag, % atom() - ex tcp. - close_tag, % atom() - ex tcp_closed - error_tag, % atom() - ex tcp_error - host, % string() | ipadress() - port, % integer() - socket, % socket() - ssl_options, % #ssl_options{} - socket_options, % #socket_options{} - connection_states, % #connection_states{} from ssl_record.hrl - tls_packets = [], % Not yet handled decode ssl/tls packets. - tls_record_buffer, % binary() buffer of incomplete records - tls_handshake_buffer, % binary() buffer of incomplete handshakes - tls_handshake_history, % tls_handshake_history() - tls_cipher_texts, % list() received but not deciphered yet - cert_db, % - session, % #session{} from tls_handshake.hrl - session_cache, % - session_cache_cb, % - negotiated_version, % tls_version() - client_certificate_requested = false, - key_algorithm, % atom as defined by cipher_suite - hashsign_algorithm = {undefined, undefined}, - cert_hashsign_algorithm, - public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams} - private_key, % PKIX: #'RSAPrivateKey'{} - diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side - diffie_hellman_keys, % {PublicKey, PrivateKey} - psk_identity, % binary() - server psk identity hint - srp_params, % #srp_user{} - srp_keys, % {PublicKey, PrivateKey} - premaster_secret, % - file_ref_db, % ets() - cert_db_ref, % ref() - bytes_to_read, % integer(), # bytes to read in passive mode - user_data_buffer, % binary() - renegotiation, % {boolean(), From | internal | peer} - start_or_recv_from, % "gen_fsm From" - timer, % start_or_recv_timer - send_queue, % queue() - terminated = false, % - allow_renegotiate = true, - expecting_next_protocol_negotiation = false :: boolean(), - next_protocol = undefined :: undefined | binary(), - client_ecc % {Curves, PointFmt} - }). - --define(DEFAULT_DIFFIE_HELLMAN_PARAMS, - #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, - base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). --define(WAIT_TO_ALLOW_RENEGOTIATION, 12000). - - %%==================================================================== %% Internal application API %%==================================================================== - -%%-------------------------------------------------------------------- --spec send(pid(), iodata()) -> ok | {error, reason()}. -%% -%% Description: Sends data over the ssl connection -%%-------------------------------------------------------------------- -send(Pid, Data) -> - sync_send_all_state_event(Pid, {application_data, - %% iolist_to_binary should really - %% be called iodata_to_binary() - erlang:iolist_to_binary(Data)}). - -%%-------------------------------------------------------------------- --spec recv(pid(), integer(), timeout()) -> - {ok, binary() | list()} | {error, reason()}. -%% -%% Description: Receives data when active = false -%%-------------------------------------------------------------------- -recv(Pid, Length, Timeout) -> - sync_send_all_state_event(Pid, {recv, Length, Timeout}). -%%-------------------------------------------------------------------- --spec connect(host(), inet:port_number(), port(), {#ssl_options{}, #socket_options{}}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Connect to an ssl server. -%%-------------------------------------------------------------------- -connect(Host, Port, Socket, Options, User, CbInfo, Timeout) -> - try start_fsm(client, Host, Port, Socket, Options, User, CbInfo, - Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. -%%-------------------------------------------------------------------- --spec ssl_accept(inet:port_number(), port(), {#ssl_options{}, #socket_options{}}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Performs accept on an ssl listen socket. e.i. performs -%% ssl handshake. -%%-------------------------------------------------------------------- -ssl_accept(Port, Socket, Opts, User, CbInfo, Timeout) -> - try start_fsm(server, "localhost", Port, Socket, Opts, User, - CbInfo, Timeout) +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts, + User, {CbModule, _,_, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule), + ok = ssl_connection:handshake(SslSocket, Timeout), + {ok, SslSocket} catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}. -%% -%% Description: Starts ssl handshake. -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = Pid}, Timeout) -> - case sync_send_all_state_event(Pid, {start, Timeout}) of - connected -> - ok; - Error -> + error:{badmatch, {error, _} = Error} -> Error - end. -%-------------------------------------------------------------------- --spec socket_control(port(), pid(), atom()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Set the ssl process to own the accept socket -%%-------------------------------------------------------------------- -socket_control(Socket, Pid, Transport) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, ssl_socket:socket(Pid, Transport, Socket)}; - {error, Reason} -> - {error, Reason} - end. + end; -%%-------------------------------------------------------------------- --spec close(pid()) -> ok | {error, reason()}. -%% -%% Description: Close an ssl connection -%%-------------------------------------------------------------------- -close(ConnectionPid) -> - case sync_send_all_state_event(ConnectionPid, close) of - {error, closed} -> - ok; - Other -> - Other +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts, + User, {CbModule, _,_, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule), + ok = ssl_connection:handshake(SslSocket, Timeout), + {ok, SslSocket} + catch + error:{badmatch, {error, _} = Error} -> + Error end. -%%-------------------------------------------------------------------- --spec shutdown(pid(), atom()) -> ok | {error, reason()}. -%% -%% Description: Same as gen_tcp:shutdown/2 -%%-------------------------------------------------------------------- -shutdown(ConnectionPid, How) -> - sync_send_all_state_event(ConnectionPid, {shutdown, How}). - -%%-------------------------------------------------------------------- --spec new_user(pid(), pid()) -> ok | {error, reason()}. -%% -%% Description: Changes process that receives the messages when active = true -%% or once. -%%-------------------------------------------------------------------- -new_user(ConnectionPid, User) -> - sync_send_all_state_event(ConnectionPid, {new_user, User}). - -%%-------------------------------------------------------------------- --spec negotiated_next_protocol(pid()) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the negotiated protocol -%%-------------------------------------------------------------------- -negotiated_next_protocol(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, negotiated_next_protocol). +send_handshake(Handshake, #state{negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + tls_handshake_history = Hist0, + connection_states = ConnectionStates0} = State0) -> + {BinHandshake, ConnectionStates, Hist} = + encode_handshake(Handshake, Version, ConnectionStates0, Hist0), + Transport:send(Socket, BinHandshake), + State0#state{connection_states = ConnectionStates, + tls_handshake_history = Hist + }. -%%-------------------------------------------------------------------- --spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Same as inet:getopts/2 -%%-------------------------------------------------------------------- -get_opts(ConnectionPid, OptTags) -> - sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}). -%%-------------------------------------------------------------------- --spec set_opts(pid(), list()) -> ok | {error, reason()}. -%% -%% Description: Same as inet:setopts/2 -%%-------------------------------------------------------------------- -set_opts(ConnectionPid, Options) -> - sync_send_all_state_event(ConnectionPid, {set_opts, Options}). - -%%-------------------------------------------------------------------- --spec info(pid()) -> {ok, {atom(), tuple()}} | {error, reason()}. -%% -%% Description: Returns ssl protocol and cipher used for the connection -%%-------------------------------------------------------------------- -info(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, info). - -%%-------------------------------------------------------------------- --spec session_info(pid()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns info about the ssl session -%%-------------------------------------------------------------------- -session_info(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, session_info). - -%%-------------------------------------------------------------------- --spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. -%% -%% Description: Returns the peer cert -%%-------------------------------------------------------------------- -peer_certificate(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, peer_certificate). - -%%-------------------------------------------------------------------- --spec renegotiation(pid()) -> ok | {error, reason()}. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -renegotiation(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, renegotiate). +send_alert(Alert, #state{negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0} = State0) -> + {BinMsg, ConnectionStates} = + ssl_alert:encode(Alert, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + State0#state{connection_states = ConnectionStates}. -%%-------------------------------------------------------------------- --spec prf(pid(), binary() | 'master_secret', binary(), - binary() | ssl:prf_random(), non_neg_integer()) -> - {ok, binary()} | {error, reason()} | {'EXIT', term()}. -%% -%% Description: use a ssl sessions TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> - sync_send_all_state_event(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). +send_change_cipher(Msg, #state{connection_states = ConnectionStates0, + socket = Socket, + negotiated_version = Version, + transport_cb = Transport} = State0) -> + {BinChangeCipher, ConnectionStates} = + encode_change_cipher(Msg, Version, ConnectionStates0), + Transport:send(Socket, BinChangeCipher), + State0#state{connection_states = ConnectionStates}. %%==================================================================== -%% ssl_connection_sup API +%% tls_connection_sup API %%==================================================================== %%-------------------------------------------------------------------- @@ -297,10 +145,11 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> + process_flag(trap_exit, true), State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), - Handshake = tls_handshake:init_handshake_history(), + Handshake = ssl_handshake:init_handshake_history(), TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), - try ssl_init(SSLOpts0, Role) of + try ssl_config:init(SSLOpts0, Role) of {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, Key, DHParams} -> Session = State0#state.session, State = State0#state{ @@ -326,23 +175,18 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> %% same name as the current state name StateName is called to handle %% the event. It is also called if a timeout occurs. %% - -%%-------------------------------------------------------------------- --spec hello(start | #hello_request{} | #client_hello{} | #server_hello{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- hello(start, #state{host = Host, port = Port, role = client, - ssl_options = SslOpts, - session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + session_cache = Cache, session_cache_cb = CacheCb, + transport_cb = Transport, socket = Socket, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, - Handshake0 = tls_handshake:init_handshake_history(), + Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Hello, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -354,54 +198,6 @@ 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, @@ -412,434 +208,60 @@ hello(Hello = #client_hello{client_version = ClientVersion, ssl_options = SslOpts}) -> HashSign = ssl_handshake:select_hashsign(HashSigns, Cert), case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, - ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, #session{cipher_suite = CipherSuite} = Session}, + ConnectionStates0, Cert}, Renegotiation) of + {Version, {Type, Session}, ConnectionStates, #hello_extensions{ec_point_formats = EcPointFormats, elliptic_curves = EllipticCurves} = ServerHelloExt} -> - {KeyAlg, _, _, _} = ssl_cipher:suite_definition(CipherSuite), - NegotiatedHashSign = negotiated_hashsign(HashSign, KeyAlg, Version), - do_server_hello(Type, ServerHelloExt, - State#state{connection_states = ConnectionStates, - negotiated_version = Version, - session = Session, - hashsign_algorithm = NegotiatedHashSign, - client_ecc = {EllipticCurves, EcPointFormats}}); + ssl_connection:hello({common_client_hello, Type, ServerHelloExt, HashSign}, + State#state{connection_states = ConnectionStates, + negotiated_version = Version, + session = Session, + client_ecc = {EllipticCurves, EcPointFormats}}, ?MODULE); #alert{} = Alert -> handle_own_alert(Alert, ClientVersion, hello, State) end; - -hello(timeout, State) -> - { next_state, hello, State, hibernate }; - -hello(Msg, State) -> - handle_unexpected_message(Msg, hello, State). -%%-------------------------------------------------------------------- --spec abbreviated(#hello_request{} | #finished{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -abbreviated(#hello_request{}, State0) -> - {Record, State} = next_record(State0), - next_state(abbreviated, hello, Record, State); - -abbreviated(#finished{verify_data = Data} = Finished, - #state{role = server, - negotiated_version = Version, - tls_handshake_history = Handshake, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = - State) -> - case ssl_handshake:verify_connection(Version, Finished, client, - get_current_connection_state_prf(ConnectionStates0, write), - MasterSecret, Handshake) of - verified -> - ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), - next_state_connection(abbreviated, - ack_connection(State#state{connection_states = ConnectionStates})); +hello(Hello, + #state{connection_states = ConnectionStates0, + negotiated_version = ReqVersion, + role = client, + renegotiation = {Renegotiation, _}, + ssl_options = SslOptions} = State) -> + case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State) - end; - -abbreviated(#finished{verify_data = Data} = Finished, - #state{role = client, tls_handshake_history = Handshake0, - session = #session{master_secret = MasterSecret}, - negotiated_version = Version, - connection_states = ConnectionStates0} = State) -> - case ssl_handshake:verify_connection(Version, Finished, server, - get_pending_connection_state_prf(ConnectionStates0, write), - MasterSecret, Handshake0) of - verified -> - ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), - {ConnectionStates, Handshake} = - finalize_handshake(State#state{connection_states = ConnectionStates1}, abbreviated), - next_state_connection(abbreviated, - ack_connection(State#state{tls_handshake_history = Handshake, - connection_states = - ConnectionStates})); - #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State) + handle_own_alert(Alert, ReqVersion, hello, State); + {Version, NewId, ConnectionStates, NextProtocol} -> + ssl_connection:handle_session(Hello, + Version, NewId, ConnectionStates, NextProtocol, State) end; -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -abbreviated(#next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> - {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), - next_state(abbreviated, abbreviated, Record, State); - -abbreviated(timeout, State) -> - { next_state, abbreviated, State, hibernate }; +hello(Msg, State) -> + ssl_connection:hello(Msg, State, ?MODULE). abbreviated(Msg, State) -> - handle_unexpected_message(Msg, abbreviated, State). - -%%-------------------------------------------------------------------- --spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} | - #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -certify(#hello_request{}, State0) -> - {Record, State} = next_record(State0), - next_state(certify, hello, Record, State); - -certify(#certificate{asn1_certificates = []}, - #state{role = server, negotiated_version = Version, - ssl_options = #ssl_options{verify = verify_peer, - fail_if_no_peer_cert = true}} = - State) -> - Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), - handle_own_alert(Alert, Version, certify, State); - -certify(#certificate{asn1_certificates = []}, - #state{role = server, - ssl_options = #ssl_options{verify = verify_peer, - fail_if_no_peer_cert = false}} = - State0) -> - {Record, State} = next_record(State0#state{client_certificate_requested = false}), - next_state(certify, certify, Record, State); - -certify(#certificate{} = Cert, - #state{negotiated_version = Version, - role = Role, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - ssl_options = Opts} = State) -> - case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth, - Opts#ssl_options.verify, - Opts#ssl_options.verify_fun, Role) of - {PeerCert, PublicKeyInfo} -> - handle_peer_cert(Role, PeerCert, PublicKeyInfo, - State#state{client_certificate_requested = false}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) - end; - -certify(#server_key_exchange{} = KeyExchangeMsg, - #state{role = client, negotiated_version = Version, - key_algorithm = Alg} = State0) - when Alg == dhe_dss; Alg == dhe_rsa; - Alg == ecdhe_rsa; Alg == ecdhe_ecdsa; - Alg == dh_anon; Alg == ecdh_anon; - Alg == psk; Alg == dhe_psk; Alg == rsa_psk; - Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> - case handle_server_key(KeyExchangeMsg, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, certify, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify(#server_key_exchange{} = Msg, - #state{role = client, key_algorithm = rsa} = State) -> - handle_unexpected_message(Msg, certify_server_keyexchange, State); - -certify(#certificate_request{hashsign_algorithms = HashSigns}, - #state{session = #session{own_certificate = Cert}} = State0) -> - HashSign = ssl_handshake:select_hashsign(HashSigns, Cert), - {Record, State} = next_record(State0#state{client_certificate_requested = true}), - next_state(certify, certify, Record, State#state{cert_hashsign_algorithm = HashSign}); - -%% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(#server_hello_done{}, - #state{session = #session{master_secret = undefined}, - negotiated_version = Version, - psk_identity = PSKIdentity, - premaster_secret = undefined, - role = client, - key_algorithm = Alg} = State0) - when Alg == psk -> - case server_psk_master_secret(PSKIdentity, State0) of - #state{} = State -> - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify(#server_hello_done{}, - #state{session = #session{master_secret = undefined}, - ssl_options = SslOpts, - negotiated_version = Version, - psk_identity = PSKIdentity, - premaster_secret = undefined, - role = client, - key_algorithm = Alg} = State0) - when Alg == rsa_psk -> - case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - PremasterSecret = make_premaster_secret(Version, rsa), - Len = byte_size(PSK), - RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>, - State1 = State0#state{premaster_secret = PremasterSecret}, - State = master_from_premaster_secret(RealPMS, State1), - client_certify_and_key_exchange(State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end; - -%% Master secret was determined with help of server-key exchange msg -certify(#server_hello_done{}, - #state{session = #session{master_secret = MasterSecret} = Session, - connection_states = ConnectionStates0, - negotiated_version = Version, - premaster_secret = undefined, - role = client} = State0) -> - case ssl_handshake:master_secret(tls_record, Version, Session, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - State = State0#state{connection_states = ConnectionStates}, - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -%% Master secret is calculated from premaster_secret -certify(#server_hello_done{}, - #state{session = Session0, - connection_states = ConnectionStates0, - negotiated_version = Version, - premaster_secret = PremasterSecret, - role = client} = State0) -> - case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify(#client_key_exchange{} = Msg, - #state{role = server, - client_certificate_requested = true, - ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State) -> - %% We expect a certificate here - handle_unexpected_message(Msg, certify_client_key_exchange, State); - -certify(#client_key_exchange{exchange_keys = Keys}, - State = #state{key_algorithm = KeyAlg, negotiated_version = Version}) -> - try - certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), State) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) - end; - - -certify(timeout, State) -> - { next_state, certify, State, hibernate }; + ssl_connection:abbreviated(Msg, State, ?MODULE). certify(Msg, State) -> - handle_unexpected_message(Msg, certify, State). - -certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{negotiated_version = Version, - connection_states = ConnectionStates0, - session = Session0, - private_key = Key} = State0) -> - PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), - case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, - ConnectionStates0, server) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State1 = State0#state{connection_states = ConnectionStates, - session = Session}, - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, - #state{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{} = Params, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> - case dh_master_secret(Params, ClientPublicDhKey, ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{negotiated_version = Version, - diffie_hellman_keys = ECDHKey} = State0) -> - case ec_dh_master_secret(ECDHKey, #'ECPoint'{point = ClientPublicEcDhPoint}, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_psk_identity{identity = ClientPSKIdentity}, - #state{negotiated_version = Version} = State0) -> - case server_psk_master_secret(ClientPSKIdentity, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_dhe_psk_identity{ - identity = ClientPSKIdentity, - dh_public = ClientPublicDhKey}, - #state{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{prime = P, - base = G}, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> - case dhe_psk_master_secret(ClientPSKIdentity, P, G, ClientPublicDhKey, ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_rsa_psk_identity{ - identity = PskIdentity, - exchange_keys = - #encrypted_premaster_secret{premaster_secret= EncPMS}}, - #state{negotiated_version = Version, - private_key = Key} = State0) -> - PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), - case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_srp_public{srp_a = ClientPublicKey}, - #state{negotiated_version = Version, - srp_params = - #srp_user{prime = Prime, - verifier = Verifier} - } = State0) -> - case server_srp_master_secret(Verifier, Prime, ClientPublicKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. - -%%-------------------------------------------------------------------- --spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -cipher(#hello_request{}, State0) -> - {Record, State} = next_record(State0), - next_state(cipher, hello, Record, State); - -cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, - #state{role = server, - public_key_info = {Algo, _, _} =PublicKeyInfo, - negotiated_version = Version, - session = #session{master_secret = MasterSecret}, - tls_handshake_history = Handshake - } = State0) -> - - HashSign = ssl_handshake:select_cert_hashsign(CertHashSign, Algo, Version), - case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, - Version, HashSign, MasterSecret, Handshake) of - valid -> - {Record, State} = next_record(State0), - next_state(cipher, cipher, Record, State#state{cert_hashsign_algorithm = HashSign}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State0) - end; - -%% client must send a next protocol message if we are expecting it -cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true, - next_protocol = undefined, negotiated_version = Version} = State0) -> - handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); - -cipher(#finished{verify_data = Data} = Finished, - #state{negotiated_version = Version, - host = Host, - port = Port, - role = Role, - session = #session{master_secret = MasterSecret} - = Session0, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> - case ssl_handshake:verify_connection(Version, Finished, - opposite_role(Role), - get_current_connection_state_prf(ConnectionStates0, read), - MasterSecret, Handshake0) of - verified -> - Session = register_session(Role, Host, Port, Session0), - cipher_role(Role, Data, Session, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State) - end; - -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -cipher(#next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> - {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), - next_state(cipher, cipher, Record, State); - -cipher(timeout, State) -> - { next_state, cipher, State, hibernate }; + ssl_connection:certify(Msg, State, ?MODULE). cipher(Msg, State) -> - handle_unexpected_message(Msg, cipher, State). + ssl_connection:cipher(Msg, State, ?MODULE). -%%-------------------------------------------------------------------- --spec connection(#hello_request{} | #client_hello{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- connection(#hello_request{}, #state{host = Host, port = Port, - socket = Socket, session = #session{own_certificate = Cert} = Session0, session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts, - negotiated_version = Version, - transport_cb = Transport, connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - tls_handshake_history = Handshake0} = State0) -> + renegotiation = {Renegotiation, _}} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), - - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - {Record, State} = next_record(State0#state{connection_states = - ConnectionStates, - session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake}), + State1 = send_handshake(Hello, State0), + {Record, State} = + next_record( + State1#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}), next_state(connection, hello, Record, State); + connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html @@ -849,21 +271,13 @@ connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = tr erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), hello(Hello, State#state{allow_renegotiate = false}); -connection(#client_hello{}, #state{role = server, allow_renegotiate = false, - connection_states = ConnectionStates0, - socket = Socket, transport_cb = Transport, - negotiated_version = Version} = State0) -> +connection(#client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), - Transport:send(Socket, BinMsg), - next_state_connection(connection, State0#state{connection_states = ConnectionStates}); + State = send_alert(Alert, State0), + next_state_connection(connection, State); -connection(timeout, State) -> - {next_state, connection, State, hibernate}; - connection(Msg, State) -> - handle_unexpected_message(Msg, connection, State). + ssl_connection:connection(Msg, State, tls_connection). %%-------------------------------------------------------------------- %% Description: Whenever a gen_fsm receives an event sent using @@ -878,195 +292,8 @@ handle_event(_Event, StateName, State) -> %% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle %% the event. %%-------------------------------------------------------------------- -handle_sync_event({application_data, Data}, From, connection, State) -> - %% We should look into having a worker process to do this to - %% parallize send and receive decoding and not block the receiver - %% if sending is overloading the socket. - try - write_application_data(Data, From, State) - catch throw:Error -> - {reply, Error, connection, State, get_timeout(State)} - end; -handle_sync_event({application_data, Data}, From, StateName, - #state{send_queue = Queue} = State) -> - %% In renegotiation priorities handshake, send data when handshake is finished - {next_state, StateName, - State#state{send_queue = queue:in({From, Data}, Queue)}, - get_timeout(State)}; - -handle_sync_event({start, Timeout}, StartFrom, hello, State) -> - Timer = start_or_recv_cancel_timer(Timeout, StartFrom), - hello(start, State#state{start_or_recv_from = StartFrom, - timer = Timer}); - -%% The two clauses below could happen if a server upgrades a socket in -%% active mode. Note that in this case we are lucky that -%% controlling_process has been evalueated before receiving handshake -%% messages from client. The server should put the socket in passive -%% mode before telling the client that it is willing to upgrade -%% and before calling ssl:ssl_accept/2. These clauses are -%% here to make sure it is the users problem and not owers if -%% they upgrade an active socket. -handle_sync_event({start,_}, _, connection, State) -> - {reply, connected, connection, State, get_timeout(State)}; -handle_sync_event({start,_}, _From, error, {Error, State = #state{}}) -> - {stop, {shutdown, Error}, {error, Error}, State}; - -handle_sync_event({start, Timeout}, StartFrom, StateName, State) -> - Timer = start_or_recv_cancel_timer(Timeout, StartFrom), - {next_state, StateName, State#state{start_or_recv_from = StartFrom, - timer = Timer}, get_timeout(State)}; - -handle_sync_event(close, _, StateName, State) -> - %% Run terminate before returning - %% so that the reuseaddr inet-option will work - %% as intended. - (catch terminate(user_close, StateName, State)), - {stop, normal, ok, State#state{terminated = true}}; - -handle_sync_event({shutdown, How0}, _, StateName, - #state{transport_cb = Transport, - negotiated_version = Version, - connection_states = ConnectionStates, - socket = Socket} = State) -> - case How0 of - How when How == write; How == both -> - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - {BinMsg, _} = - encode_alert(Alert, Version, ConnectionStates), - Transport:send(Socket, BinMsg); - _ -> - ok - end, - - case Transport:shutdown(Socket, How0) of - ok -> - {reply, ok, StateName, State, get_timeout(State)}; - Error -> - {stop, normal, Error, State} - end; - -handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName, State0) -> - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom, timer = Timer}, StateName); - -%% Doing renegotiate wait with handling request until renegotiate is -%% finished. Will be handled by next_state_is_connection/2. -handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) -> - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom, - timer = Timer}, - get_timeout(State)}; - -handle_sync_event({new_user, User}, _From, StateName, - State =#state{user_application = {OldMon, _}}) -> - NewMon = erlang:monitor(process, User), - erlang:demonitor(OldMon, [flush]), - {reply, ok, StateName, State#state{user_application = {NewMon,User}}, - get_timeout(State)}; - -handle_sync_event({get_opts, OptTags}, _From, StateName, - #state{socket = Socket, - transport_cb = Transport, - socket_options = SockOpts} = State) -> - OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []), - {reply, OptsReply, StateName, State, get_timeout(State)}; - -handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) -> - {reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)}; -handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) -> - {reply, {ok, NextProtocol}, StateName, State, get_timeout(State)}; - -handle_sync_event({set_opts, Opts0}, _From, StateName0, - #state{socket_options = Opts1, - socket = Socket, - transport_cb = Transport, - user_data_buffer = Buffer} = State0) -> - {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []), - State1 = State0#state{socket_options = Opts}, - if - Opts#socket_options.active =:= false -> - {reply, Reply, StateName0, State1, get_timeout(State1)}; - Buffer =:= <<>>, Opts1#socket_options.active =:= false -> - %% Need data, set active once - {Record, State2} = next_record_if_active(State1), - %% Note: Renogotiation may cause StateName0 =/= StateName - case next_state(StateName0, StateName0, Record, State2) of - {next_state, StateName, State, Timeout} -> - {reply, Reply, StateName, State, Timeout}; - {stop, Reason, State} -> - {stop, Reason, State} - end; - Buffer =:= <<>> -> - %% Active once already set - {reply, Reply, StateName0, State1, get_timeout(State1)}; - true -> - case read_application_data(<<>>, State1) of - Stop = {stop,_,_} -> - Stop; - {Record, State2} -> - %% Note: Renogotiation may cause StateName0 =/= StateName - case next_state(StateName0, StateName0, Record, State2) of - {next_state, StateName, State, Timeout} -> - {reply, Reply, StateName, State, Timeout}; - {stop, Reason, State} -> - {stop, Reason, State} - end - end - end; - -handle_sync_event(renegotiate, From, connection, State) -> - renegotiate(State#state{renegotiation = {true, From}}); - -handle_sync_event(renegotiate, _, StateName, State) -> - {reply, {error, already_renegotiating}, StateName, State, get_timeout(State)}; - -handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName, - #state{connection_states = ConnectionStates, - negotiated_version = Version} = State) -> - ConnectionState = - ssl_record:current_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{master_secret = MasterSecret, - client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Reply = try - SecretToUse = case Secret of - _ when is_binary(Secret) -> Secret; - master_secret -> MasterSecret - end, - SeedToUse = lists:reverse( - lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; - (client_random, Acc) -> [ClientRandom|Acc]; - (server_random, Acc) -> [ServerRandom|Acc] - end, [], Seed)), - ssl_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength) - catch - exit:_ -> {error, badarg}; - error:Reason -> {error, Reason} - end, - {reply, Reply, StateName, State, get_timeout(State)}; - -handle_sync_event(info, _, StateName, - #state{negotiated_version = Version, - session = #session{cipher_suite = Suite}} = State) -> - - AtomVersion = tls_record:protocol_version(Version), - {reply, {ok, {AtomVersion, ssl:suite_definition(Suite)}}, - StateName, State, get_timeout(State)}; - -handle_sync_event(session_info, _, StateName, - #state{session = #session{session_id = Id, - cipher_suite = Suite}} = State) -> - {reply, [{session_id, Id}, - {cipher_suite, ssl:suite_definition(Suite)}], - StateName, State, get_timeout(State)}; - -handle_sync_event(peer_certificate, _, StateName, - #state{session = #session{peer_certificate = Cert}} - = State) -> - {reply, {ok, Cert}, StateName, State, get_timeout(State)}. +handle_sync_event(Event, From, StateName, State) -> + ssl_connection:handle_sync_event(Event, From, StateName, State). %%-------------------------------------------------------------------- %% Description: This function is called by a gen_fsm when it receives any @@ -1074,7 +301,7 @@ handle_sync_event(peer_certificate, _, StateName, %% (or a system message). %%-------------------------------------------------------------------- -%% raw data from TCP, unpack records +%% raw data from socket, unpack records handle_info({Protocol, _, Data}, StateName, #state{data_tag = Protocol} = State0) -> case next_tls_record(Data, State0) of @@ -1105,45 +332,8 @@ handle_info({CloseTag, Socket}, StateName, handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), {stop, {shutdown, transport_closed}, State}; -handle_info({ErrorTag, Socket, econnaborted}, StateName, - #state{socket = Socket, transport_cb = Transport, - start_or_recv_from = StartFrom, role = Role, - error_tag = ErrorTag} = State) when StateName =/= connection -> - alert_user(Transport, Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), - {stop, normal, State}; - -handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, - error_tag = ErrorTag} = State) -> - Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), - error_logger:info_report(Report), - handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, normal, State}; - -handle_info({'DOWN', MonitorRef, _, _, _}, _, - State = #state{user_application={MonitorRef,_Pid}}) -> - {stop, normal, State}; - -handle_info(allow_renegotiate, StateName, State) -> - {next_state, StateName, State#state{allow_renegotiate = true}, get_timeout(State)}; - -handle_info({cancel_start_or_recv, StartFrom}, StateName, - #state{renegotiation = {false, first}} = State) when StateName =/= connection -> - gen_fsm:reply(StartFrom, {error, timeout}), - {stop, {shutdown, user_timeout}, State#state{timer = undefined}}; - -handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) -> - gen_fsm:reply(RecvFrom, {error, timeout}), - {next_state, StateName, State#state{start_or_recv_from = undefined, - bytes_to_read = undefined, - timer = undefined}, get_timeout(State)}; - -handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> - {next_state, StateName, State#state{timer = undefined}, get_timeout(State)}; - handle_info(Msg, StateName, State) -> - Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [Msg]), - error_logger:info_report(Report), - {next_state, StateName, State, get_timeout(State)}. + ssl_connection:handle_info(Msg, StateName, State). %%-------------------------------------------------------------------- %% Description:This function is called by a gen_fsm when it is about @@ -1151,44 +341,9 @@ handle_info(Msg, StateName, State) -> %% necessary cleaning up. When it returns, the gen_fsm terminates with %% Reason. The return value is ignored. %%-------------------------------------------------------------------- -terminate(_, _, #state{terminated = true}) -> - %% Happens when user closes the connection using ssl:close/1 - %% we want to guarantee that Transport:close has been called - %% when ssl:close/1 returns. - ok; +terminate(Reason, StateName, State) -> + ssl_connection:terminate(Reason, StateName, State). -terminate({shutdown, transport_closed}, StateName, #state{send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_unrecv_data(StateName, State), - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate); - -terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate); - -terminate(Reason, connection, #state{negotiated_version = Version, - connection_states = ConnectionStates, - transport_cb = Transport, - socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), - BinAlert = terminate_alert(Reason, Version, ConnectionStates), - Transport:send(Socket, BinAlert), - workaround_transport_delivery_problems(Socket, Transport); - -terminate(_Reason, _StateName, #state{transport_cb = Transport, - socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), - Transport:close(Socket). %%-------------------------------------------------------------------- %% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} @@ -1200,1093 +355,206 @@ code_change(_OldVsn, StateName, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts, - User, {CbModule, _,_, _} = CbInfo, - Timeout) -> - try - {ok, Pid} = ssl_connection_sup:start_child([Role, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = socket_control(Socket, Pid, CbModule), - ok = handshake(SslSocket, Timeout), - {ok, SslSocket} - catch - error:{badmatch, {error, _} = Error} -> - Error - end; - -start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts, - User, {CbModule, _,_, _} = CbInfo, - Timeout) -> - try - {ok, Pid} = ssl_connection_sup:start_child_dist([Role, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = socket_control(Socket, Pid, CbModule), - ok = handshake(SslSocket, Timeout), - {ok, SslSocket} - catch - error:{badmatch, {error, _} = Error} -> - Error - end. - -ssl_init(SslOpts, Role) -> - - init_manager_name(SslOpts#ssl_options.erl_dist), - - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} = init_certificates(SslOpts, Role), - PrivateKey = - init_private_key(PemCacheHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, - SslOpts#ssl_options.password, Role), - DHParams = init_diffie_hellman(PemCacheHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), - {ok, CertDbRef, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, PrivateKey, DHParams}. - -init_manager_name(false) -> - put(ssl_manager, ssl_manager:manager_name(normal)); -init_manager_name(true) -> - put(ssl_manager, ssl_manager:manager_name(dist)). - -init_certificates(#ssl_options{cacerts = CaCerts, - cacertfile = CACertFile, - certfile = CertFile, - cert = Cert}, Role) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle} = - try - Certs = case CaCerts of - undefined -> - CACertFile; - _ -> - {der, CaCerts} - end, - {ok, _, _, _, _, _} = ssl_manager:connection_init(Certs, Role) - catch - _:Reason -> - file_error(CACertFile, {cacertfile, Reason}) - end, - init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, Role). - -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, <<>>, _) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined}; - -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, client) -> - try - %% Ignoring potential proxy-certificates see: - %% http://dev.globus.org/wiki/Security/ProxyFileFormat - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} - catch _Error:_Reason -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined} - end; - -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CertFile, server) -> - try - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, OwnCert} - catch - _:Reason -> - file_error(CertFile, {certfile, Reason}) - end; -init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, _, _) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, Cert}. - -init_private_key(_, undefined, <<>>, _Password, _Client) -> - undefined; -init_private_key(DbHandle, undefined, KeyFile, Password, _) -> - try - {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle), - [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List, - PKey =:= 'RSAPrivateKey' orelse - PKey =:= 'DSAPrivateKey' orelse - PKey =:= 'ECPrivateKey' orelse - PKey =:= 'PrivateKeyInfo' - ], - private_key(public_key:pem_entry_decode(PemEntry, Password)) - catch - _:Reason -> - file_error(KeyFile, {keyfile, Reason}) - end; - -%% First two clauses are for backwards compatibility -init_private_key(_,{rsa, PrivateKey}, _, _,_) -> - init_private_key('RSAPrivateKey', PrivateKey); -init_private_key(_,{dsa, PrivateKey},_,_,_) -> - init_private_key('DSAPrivateKey', PrivateKey); -init_private_key(_,{ec, PrivateKey},_,_,_) -> - init_private_key('ECPrivateKey', PrivateKey); -init_private_key(_,{Asn1Type, PrivateKey},_,_,_) -> - private_key(init_private_key(Asn1Type, PrivateKey)). - -init_private_key(Asn1Type, PrivateKey) -> - public_key:der_decode(Asn1Type, PrivateKey). - -private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = - #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'}, - privateKey = Key}) -> - public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key)); - -private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = - #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, - privateKey = Key}) -> - public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); - -private_key(Key) -> - Key. - --spec(file_error(_,_) -> no_return()). -file_error(File, Throw) -> - case Throw of - {Opt,{badmatch, {error, {badmatch, Error}}}} -> - throw({options, {Opt, binary_to_list(File), Error}}); - _ -> - throw(Throw) - end. - -init_diffie_hellman(_,Params, _,_) when is_binary(Params)-> - public_key:der_decode('DHParameter', Params); -init_diffie_hellman(_,_,_, client) -> - undefined; -init_diffie_hellman(_,_,undefined, _) -> - ?DEFAULT_DIFFIE_HELLMAN_PARAMS; -init_diffie_hellman(DbHandle,_, DHParamFile, server) -> - try - {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle), - case [Entry || Entry = {'DHParameter', _ , _} <- List] of - [Entry] -> - public_key:pem_entry_decode(Entry); - [] -> - ?DEFAULT_DIFFIE_HELLMAN_PARAMS - end - catch - _:Reason -> - file_error(DHParamFile, {dhfile, Reason}) - end. - -sync_send_all_state_event(FsmPid, Event) -> - try gen_fsm:sync_send_all_state_event(FsmPid, Event, infinity) - catch - exit:{noproc, _} -> - {error, closed}; - exit:{normal, _} -> - {error, closed}; - exit:{{shutdown, _},_} -> - {error, closed} - end. - -handle_peer_cert(Role, PeerCert, PublicKeyInfo, - #state{session = #session{cipher_suite = CipherSuite} = Session} = State0) -> - State1 = State0#state{session = - Session#session{peer_certificate = PeerCert}, - public_key_info = PublicKeyInfo}, - {KeyAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite), - State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1), - - {Record, State} = next_record(State2), - next_state(certify, certify, Record, State). - -handle_peer_cert_key(client, _, - {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, PublicKeyParams}, - KeyAlg, State) when KeyAlg == ecdh_rsa; - KeyAlg == ecdh_ecdsa -> - ECDHKey = public_key:generate_key(PublicKeyParams), - ec_dh_master_secret(ECDHKey, PublicKey, State#state{diffie_hellman_keys = ECDHKey}); - -%% We do currently not support cipher suites that use fixed DH. -%% If we want to implement that the following clause can be used -%% to extract DH parameters form cert. -%% handle_peer_cert_key(client, _PeerCert, {?dhpublicnumber, PublicKey, PublicKeyParams}, {_,SignAlg}, -%% #state{diffie_hellman_keys = {_, MyPrivatKey}} = State) when SignAlg == dh_rsa; -%% SignAlg == dh_dss -> -%% dh_master_secret(PublicKeyParams, PublicKey, MyPrivatKey, State); -handle_peer_cert_key(_, _, _, _, State) -> - State. - -certify_client(#state{client_certificate_requested = true, role = client, - connection_states = ConnectionStates0, - transport_cb = Transport, - negotiated_version = Version, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - session = #session{own_certificate = OwnCert}, - socket = Socket, - tls_handshake_history = Handshake0} = State) -> - Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), - {BinCert, ConnectionStates, Handshake} = - encode_handshake(Certificate, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinCert), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; -certify_client(#state{client_certificate_requested = false} = State) -> - State. - -verify_client_cert(#state{client_certificate_requested = true, role = client, - connection_states = ConnectionStates0, - transport_cb = Transport, - negotiated_version = Version, - socket = Socket, - private_key = PrivateKey, - session = #session{master_secret = MasterSecret, - own_certificate = OwnCert}, - cert_hashsign_algorithm = HashSign, - tls_handshake_history = Handshake0} = State) -> - - case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, - Version, HashSign, PrivateKey, Handshake0) of - #certificate_verify{} = Verified -> - {BinVerified, ConnectionStates, Handshake} = - encode_handshake(Verified, Version, - ConnectionStates0, Handshake0), - Transport:send(Socket, BinVerified), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - ignore -> - State; - #alert{} = Alert -> - throw(Alert) - end; -verify_client_cert(#state{client_certificate_requested = false} = State) -> - State. - -do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt, - #state{negotiated_version = Version, - session = #session{session_id = SessId}, - connection_states = ConnectionStates0} - = State0) when is_atom(Type) -> - - ServerHello = - tls_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt), - State = server_hello(ServerHello, - State0#state{expecting_next_protocol_negotiation = - NextProtocols =/= undefined}), - case Type of - new -> - new_server_hello(ServerHello, State); - resumed -> - resumed_server_hello(State) - end. +encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> + Frag = tls_handshake:encode_handshake(Handshake, Version), + Hist = ssl_handshake:update_handshake_history(Hist0, Frag), + {Encoded, ConnectionStates} = + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), + {Encoded, ConnectionStates, Hist}. -new_server_hello(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression, - session_id = SessionId}, - #state{session = Session0, - negotiated_version = Version} = State0) -> - try server_certify_and_key_exchange(State0) of - #state{} = State1 -> - State2 = server_hello_done(State1), - Session = - Session0#session{session_id = SessionId, - cipher_suite = CipherSuite, - compression_method = Compression}, - {Record, State} = next_record(State2#state{session = Session}), - next_state(hello, certify, Record, State) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. -resumed_server_hello(#state{session = Session, - connection_states = ConnectionStates0, - negotiated_version = Version} = State0) -> - - case ssl_handshake:master_secret(tls_record, Version, Session, - ConnectionStates0, server) of - {_, ConnectionStates1} -> - State1 = State0#state{connection_states = ConnectionStates1, - session = Session}, - {ConnectionStates, Handshake} = - finalize_handshake(State1, abbreviated), - State2 = State1#state{connection_states = - ConnectionStates, - tls_handshake_history = Handshake}, - {Record, State} = next_record(State2), - next_state(hello, abbreviated, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. +encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> + ssl_record:encode_change_cipher_spec(Version, ConnectionStates). -handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0} = State0) -> - Session = Session0#session{session_id = NewId, - cipher_suite = CipherSuite, - compression_method = Compression}, - {Record, State} = next_record(State0#state{session = Session}), - next_state(hello, certify, Record, State). - -handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, - negotiated_version = Version, - host = Host, port = Port, - session_cache = Cache, - session_cache_cb = CacheCb} = State0) -> - Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - case ssl_handshake:master_secret(tls_record, Version, Session, - ConnectionStates0, client) of - {_, ConnectionStates} -> - {Record, State} = - next_record(State0#state{ - connection_states = ConnectionStates, - session = Session}), - next_state(hello, abbreviated, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. -client_certify_and_key_exchange(#state{negotiated_version = Version} = - State0) -> - try do_client_certify_and_key_exchange(State0) of - State1 = #state{} -> - {ConnectionStates, Handshake} = finalize_handshake(State1, certify), - State2 = State1#state{connection_states = ConnectionStates, - %% Reinitialize - client_certificate_requested = false, - tls_handshake_history = Handshake}, - {Record, State} = next_record(State2), - next_state(certify, cipher, Record, State) - catch - throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. +decode_alerts(Bin) -> + decode_alerts(Bin, []). -do_client_certify_and_key_exchange(State0) -> - State1 = certify_client(State0), - State2 = key_exchange(State1), - verify_client_cert(State2). +decode_alerts(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc) -> + A = ?ALERT_REC(Level, Description), + decode_alerts(Rest, [A | Acc]); +decode_alerts(<<>>, Acc) -> + lists:reverse(Acc, []). -server_certify_and_key_exchange(State0) -> - State1 = certify_server(State0), - State2 = key_exchange(State1), - request_client_cert(State2). - -server_hello(ServerHello, #state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> - CipherSuite = ServerHello#server_hello.cipher_suite, - {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), - {BinMsg, ConnectionStates1, Handshake1} = - encode_handshake(ServerHello, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates1, - tls_handshake_history = Handshake1, - key_algorithm = KeyAlgorithm}. - -server_hello_done(#state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> +initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, + {CbModule, DataTag, CloseTag, ErrorTag}) -> + ConnectionStates = ssl_record:init_connection_states(Role), - HelloDone = ssl_handshake:server_hello_done(), + SessionCacheCb = case application:get_env(ssl, session_cb) of + {ok, Cb} when is_atom(Cb) -> + Cb; + _ -> + ssl_session_cache + end, - {BinHelloDone, ConnectionStates, Handshake} = - encode_handshake(HelloDone, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinHelloDone), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}. - -certify_server(#state{key_algorithm = Algo} = State) - when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; Algo == srp_anon -> - State; - -certify_server(#state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - session = #session{own_certificate = OwnCert}} = State) -> - case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of - CertMsg = #certificate{} -> - {BinCertMsg, ConnectionStates, Handshake} = - encode_handshake(CertMsg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinCertMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake - }; - Alert = #alert{} -> - throw(Alert) - end. - -key_exchange(#state{role = server, key_algorithm = rsa} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == dhe_dss; - Algo == dhe_rsa; - Algo == dh_anon -> - DHKeys = public_key:generate_key(Params), - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = DHKeys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State) - when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> - State#state{diffie_hellman_keys = Key}; -key_exchange(#state{role = server, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa; - Algo == ecdh_anon -> - - ECDHKeys = public_key:generate_key(select_curve(State)), - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake1} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = ECDHKeys, - tls_handshake_history = Handshake1}; - -key_exchange(#state{role = server, key_algorithm = psk, - ssl_options = #ssl_options{psk_identity = undefined}} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = dhe_psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - DHKeys = public_key:generate_key(Params), - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = DHKeys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = rsa_psk, - ssl_options = #ssl_options{psk_identity = undefined}} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = rsa_psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = Algo, - ssl_options = #ssl_options{user_lookup_fun = LookupFun}, - hashsign_algorithm = HashSignAlgo, - session = #session{srp_username = Username}, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == srp_dss; - Algo == srp_rsa; - Algo == srp_anon -> - SrpParams = handle_srp_identity(Username, LookupFun), - Keys = case generate_srp_server_keys(SrpParams, 0) of - Alert = #alert{} -> - throw(Alert); - Keys0 = {_,_} -> - Keys0 - end, - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - srp_params = SrpParams, - srp_keys = Keys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = rsa, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - premaster_secret = PremasterSecret, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - diffie_hellman_keys = {DhPubKey, _}, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == dhe_dss; - Algorithm == dhe_rsa; - Algorithm == dh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - diffie_hellman_keys = Keys, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; - Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; - Algorithm == ecdh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - key_algorithm = psk, - negotiated_version = Version, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; + Monitor = erlang:monitor(process, User), -key_exchange(#state{role = client, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - key_algorithm = dhe_psk, - negotiated_version = Version, - diffie_hellman_keys = {DhPubKey, _}, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = ssl_handshake:key_exchange(client, Version, {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; + #state{socket_options = SocketOptions, + %% We do not want to save the password in the state so that + %% could be written in the clear into error logs. + ssl_options = SSLOptions#ssl_options{password = undefined}, + session = #session{is_resumable = new}, + transport_cb = CbModule, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + role = Role, + host = Host, + port = Port, + socket = Socket, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_application = {Monitor, User}, + user_data_buffer = <<>>, + session_cache_cb = SessionCacheCb, + renegotiation = {false, first}, + start_or_recv_from = undefined, + send_queue = queue:new(), + protocol_cb = ?MODULE + }. -key_exchange(#state{role = client, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - key_algorithm = rsa_psk, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - premaster_secret = PremasterSecret, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; +next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> + handle_own_alert(Alert, Version, Current, State); -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - srp_keys = {ClientPubKey, _}, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == srp_dss; - Algorithm == srp_rsa; - Algorithm == srp_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}. - -rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - ssl_handshake:key_exchange(client, Version, - {premaster_secret, PremasterSecret, - PublicKeyInfo}); -rsa_key_exchange(_, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). - -rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - ssl_handshake:key_exchange(client, Version, - {psk_premaster_secret, PskIdentity, PremasterSecret, - PublicKeyInfo}); -rsa_psk_key_exchange(_, _, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). - -request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, - connection_states = ConnectionStates0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - tls_handshake_history = Handshake0, - negotiated_version = Version, - socket = Socket, - transport_cb = Transport} = State) -> - #connection_state{security_parameters = - #security_parameters{cipher_suite = CipherSuite}} = - ssl_record:pending_connection_state(ConnectionStates0, read), - Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version), +next_state(_,Next, no_record, State) -> + {next_state, Next, State, get_timeout(State)}; - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{client_certificate_requested = true, - connection_states = ConnectionStates, - tls_handshake_history = Handshake}; -request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} = - State) -> - State. - -finalize_handshake(State, StateName) -> - ConnectionStates0 = cipher_protocol(State), - - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, - write), - - State1 = State#state{connection_states = ConnectionStates}, - State2 = next_protocol(State1), - finished(State2, StateName). - -next_protocol(#state{role = server} = State) -> - State; -next_protocol(#state{next_protocol = undefined} = State) -> - State; -next_protocol(#state{expecting_next_protocol_negotiation = false} = State) -> - State; -next_protocol(#state{transport_cb = Transport, socket = Socket, - negotiated_version = Version, - next_protocol = NextProtocol, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> - NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), - {BinMsg, ConnectionStates, Handshake} = encode_handshake(NextProtocolMessage, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}. +next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> + Alerts = decode_alerts(EncAlerts), + handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)}); -cipher_protocol(#state{connection_states = ConnectionStates0, - socket = Socket, - negotiated_version = Version, - transport_cb = Transport}) -> - {BinChangeCipher, ConnectionStates} = - encode_change_cipher(#change_cipher_spec{}, - Version, ConnectionStates0), - Transport:send(Socket, BinChangeCipher), - ConnectionStates. - -finished(#state{role = Role, socket = Socket, negotiated_version = Version, - transport_cb = Transport, - session = Session, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0}, StateName) -> - MasterSecret = Session#session.master_secret, - Finished = ssl_handshake:finished(Version, Role, - get_current_connection_state_prf(ConnectionStates0, write), - MasterSecret, Handshake0), - ConnectionStates1 = save_verify_data(Role, Finished, ConnectionStates0, StateName), - {BinFinished, ConnectionStates, Handshake} = - encode_handshake(Finished, Version, ConnectionStates1, Handshake0), - Transport:send(Socket, BinFinished), - {ConnectionStates, Handshake}. - -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> - ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> - ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). - -handle_server_key(#server_key_exchange{exchange_keys = Keys}, - #state{key_algorithm = KeyAlg, - negotiated_version = Version} = State) -> - - Params = ssl_handshake:decode_server_key(Keys, KeyAlg, Version), - HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KeyAlg, Version), - case is_anonymous(KeyAlg) of - true -> - server_master_secret(Params#server_key_params.params, - State#state{hashsign_algorithm = HashSign}); - false -> - verify_server_key(Params, HashSign, State#state{hashsign_algorithm = HashSign}) - end. +next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, + State0 = #state{protocol_buffers = + #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, + negotiated_version = Version}) -> + Handle = + fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> + %% This message should not be included in handshake + %% message hashes. Starts new handshake (renegotiation) + Hs0 = ssl_handshake:init_handshake_history(), + ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs0, + renegotiation = {true, peer}}); + ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> + %% This message should not be included in handshake + %% message hashes. Already in negotiation so it will be ignored! + ?MODULE:SName(Packet, State); + ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> + Version = Packet#client_hello.client_version, + Hs0 = ssl_handshake:init_handshake_history(), + Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw), + ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1, + renegotiation = {true, peer}}); + ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_history=Hs0}}) -> + Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw), + ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1}); + (_, StopState) -> StopState + end, + try + {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0), + State = State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_packets = Packets, + tls_handshake_buffer = Buf}}, + handle_tls_handshake(Handle, Next, State) + catch throw:#alert{} = Alert -> + handle_own_alert(Alert, Version, Current, State0) + end; -verify_server_key(#server_key_params{params = Params, - params_bin = EncParams, - signature = Signature}, - HashSign = {HashAlgo, _}, - #state{negotiated_version = Version, - public_key_info = PubKeyInfo, - connection_states = ConnectionStates} = State) -> - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Hash = ssl_handshake:server_key_exchange_hash(HashAlgo, - <<ClientRandom/binary, - ServerRandom/binary, - EncParams/binary>>), - case ssl_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of - true -> - server_master_secret(Params, State); - false -> - ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) - end. +next_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> + case read_application_data(Data, State0) of + Stop = {stop,_,_} -> + Stop; + {Record, State} -> + next_state(StateName, StateName, Record, State) + end; +next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = + _ChangeCipher, + #state{connection_states = ConnectionStates0} = State0) -> + ConnectionStates1 = + ssl_record:activate_pending_connection_state(ConnectionStates0, read), + {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), + next_state(Current, Next, Record, State); +next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> + %% Ignore unknown type + {Record, State} = next_record(State0), + next_state(Current, Next, Record, State). -server_master_secret(#server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}, - State) -> - dh_master_secret(P, G, ServerPublicDhKey, undefined, State); - -server_master_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - State) -> - ECDHKeys = public_key:generate_key(ECCurve), - ec_dh_master_secret(ECDHKeys, #'ECPoint'{point = ECServerPubKey}, State#state{diffie_hellman_keys = ECDHKeys}); - -server_master_secret(#server_psk_params{ - hint = IdentityHint}, - State) -> - %% store for later use - State#state{psk_identity = IdentityHint}; - -server_master_secret(#server_dhe_psk_params{ - hint = IdentityHint, - dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}}, - State) -> - dhe_psk_master_secret(IdentityHint, P, G, ServerPublicDhKey, undefined, State); - -server_master_secret(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, - State) -> - client_srp_master_secret(G, N, S, B, undefined, State). - -master_from_premaster_secret(PremasterSecret, - #state{session = Session, - negotiated_version = Version, role = Role, - connection_states = ConnectionStates0} = State) -> - case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, - ConnectionStates0, Role) of - {MasterSecret, ConnectionStates} -> - State#state{ - session = - Session#session{master_secret = MasterSecret}, - connection_states = ConnectionStates}; +next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0, + tls_cipher_texts = CT0} = Buffers} = State0) -> + case tls_record:get_tls_records(Data, Buf0) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_record_buffer = Buf1, + tls_cipher_texts = CT1}}); #alert{} = Alert -> Alert end. -dh_master_secret(#'DHParameter'{} = Params, OtherPublicDhKey, MyPrivateKey, State) -> - PremasterSecret = - public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params), - master_from_premaster_secret(PremasterSecret, State). - -dh_master_secret(Prime, Base, PublicDhKey, undefined, State) -> - Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), - dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State#state{diffie_hellman_keys = Keys}); - -dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State) -> - PremasterSecret = - crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]), - master_from_premaster_secret(PremasterSecret, State). - -ec_dh_master_secret(ECDHKeys, ECPoint, State) -> - PremasterSecret = - public_key:compute_key(ECPoint, ECDHKeys), - master_from_premaster_secret(PremasterSecret, State). - -handle_psk_identity(_PSKIdentity, LookupFun) - when LookupFun == undefined -> - error; -handle_psk_identity(PSKIdentity, {Fun, UserState}) -> - Fun(psk, PSKIdentity, UserState). - -server_psk_master_secret(ClientPSKIdentity, - #state{ssl_options = SslOpts} = State) -> - case handle_psk_identity(ClientPSKIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - Len = byte_size(PSK), - PremasterSecret = <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>, - master_from_premaster_secret(PremasterSecret, State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, undefined, State) -> - Keys = {_, PrivateDhKey} = - crypto:generate_key(dh, [Prime, Base]), - dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey, - State#state{diffie_hellman_keys = Keys}); - -dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey, - #state{ssl_options = SslOpts} = State) -> - case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - DHSecret = - crypto:compute_key(dh, PublicDhKey, PrivateDhKey, - [Prime, Base]), - DHLen = erlang:byte_size(DHSecret), - Len = erlang:byte_size(PSK), - PremasterSecret = <<?UINT16(DHLen), DHSecret/binary, ?UINT16(Len), PSK/binary>>, - master_from_premaster_secret(PremasterSecret, State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -server_rsa_psk_master_secret(PskIdentity, PremasterSecret, - #state{ssl_options = SslOpts} = State) -> - case handle_psk_identity(PskIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - Len = byte_size(PSK), - RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>, - master_from_premaster_secret(RealPMS, State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -generate_srp_server_keys(_SrpParams, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_server_keys(SrpParams = - #srp_user{generator = Generator, prime = Prime, - verifier = Verifier}, N) -> - case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of - error -> - generate_srp_server_keys(SrpParams, N+1); - Keys -> - Keys - end. - -generate_srp_client_keys(_Generator, _Prime, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_client_keys(Generator, Prime, N) -> - - case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of - error -> - generate_srp_client_keys(Generator, Prime, N+1); - Keys -> - Keys - end. - -handle_srp_identity(Username, {Fun, UserState}) -> - case Fun(srp, Username, UserState) of - {ok, {SRPParams, Salt, DerivedKey}} - when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> - {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), - Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), - #srp_user{generator = Generator, prime = Prime, - salt = Salt, verifier = Verifier}; +next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, + socket = Socket, + transport_cb = Transport} = State) -> + ssl_socket:setopts(Transport, Socket, [{active,once}]), + {no_record, State}; +next_record(#state{protocol_buffers = + #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]} + = Buffers, + connection_states = ConnStates0} = State) -> + case tls_record:decode_cipher_text(CT, ConnStates0) of + {Plain, ConnStates} -> + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{tls_cipher_texts = Rest}, + connection_states = ConnStates}}; #alert{} = Alert -> - throw(Alert); - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end. - -server_srp_master_secret(Verifier, Prime, ClientPub, State = #state{srp_keys = ServerKeys}) -> - case crypto:compute_key(srp, ClientPub, ServerKeys, {host, [Verifier, Prime, '6a']}) of - error -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); - PremasterSecret -> - master_from_premaster_secret(PremasterSecret, State) - end. - -client_srp_master_secret(_Generator, _Prime, _Salt, _ServerPub, #alert{} = Alert, _State) -> - Alert; -client_srp_master_secret(Generator, Prime, Salt, ServerPub, undefined, State) -> - Keys = generate_srp_client_keys(Generator, Prime, 0), - client_srp_master_secret(Generator, Prime, Salt, ServerPub, Keys, State#state{srp_keys = Keys}); - -client_srp_master_secret(Generator, Prime, Salt, ServerPub, ClientKeys, - #state{ssl_options = SslOpts} = State) -> - case ssl_srp_primes:check_srp_params(Generator, Prime) of - ok -> - {Username, Password} = SslOpts#ssl_options.srp_identity, - DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), - case crypto:compute_key(srp, ServerPub, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of - error -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); - PremasterSecret -> - master_from_premaster_secret(PremasterSecret, State) - end; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) -> - ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0), - next_state_connection(cipher, ack_connection(State#state{session = Session, - connection_states = ConnectionStates})); - -cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State) -> - ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0), - {ConnectionStates, Handshake} = - finalize_handshake(State#state{connection_states = ConnectionStates1, - session = Session}, cipher), - next_state_connection(cipher, ack_connection(State#state{connection_states = - ConnectionStates, - session = Session, - tls_handshake_history = - Handshake})). -encode_alert(#alert{} = Alert, Version, ConnectionStates) -> - ssl_record:encode_alert_record(Alert, Version, ConnectionStates). - -encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> - ssl_record:encode_change_cipher_spec(Version, ConnectionStates). + {Alert, State} + end; +next_record(State) -> + {no_record, State}. -encode_handshake(HandshakeRec, Version, ConnectionStates0, Handshake0) -> - Frag = tls_handshake:encode_handshake(HandshakeRec, Version), - Handshake1 = tls_handshake:update_handshake_history(Handshake0, Frag), - {E, ConnectionStates1} = - ssl_record:encode_handshake(Frag, Version, ConnectionStates0), - {E, ConnectionStates1, Handshake1}. +next_record_if_active(State = + #state{socket_options = + #socket_options{active = false}}) -> + {no_record ,State}; -encode_packet(Data, #socket_options{packet=Packet}) -> - case Packet of - 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); - 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); - 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); - _ -> Data - end. +next_record_if_active(State) -> + next_record(State). -encode_size_packet(Bin, Size, Max) -> - Len = erlang:byte_size(Bin), - case Len > Max of - true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); - false -> <<Len:Size, Bin/binary>> +next_state_connection(StateName, #state{send_queue = Queue0, + negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0 + } = State) -> + %% Send queued up data that was queued while renegotiating + case queue:out(Queue0) of + {{value, {From, Data}}, Queue} -> + {Msgs, ConnectionStates} = + ssl_record:encode_data(Data, Version, ConnectionStates0), + Result = Transport:send(Socket, Msgs), + gen_fsm:reply(From, Result), + next_state_connection(StateName, + State#state{connection_states = ConnectionStates, + send_queue = Queue}); + {empty, Queue0} -> + next_state_is_connection(StateName, State) end. -decode_alerts(Bin) -> - decode_alerts(Bin, []). +%% In next_state_is_connection/1: clear tls_handshake, +%% premaster_secret and public_key_info (only needed during handshake) +%% to reduce memory foot print of a connection. +next_state_is_connection(_, State = + #state{start_or_recv_from = RecvFrom, + socket_options = + #socket_options{active = false}}) when RecvFrom =/= undefined -> + passive_receive(State#state{premaster_secret = undefined, + public_key_info = undefined, + tls_handshake_history = ssl_handshake:init_handshake_history()}, connection); -decode_alerts(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc) -> - A = ?ALERT_REC(Level, Description), - decode_alerts(Rest, [A | Acc]); -decode_alerts(<<>>, Acc) -> - lists:reverse(Acc, []). +next_state_is_connection(StateName, State0) -> + {Record, State} = next_record_if_active(State0), + next_state(StateName, connection, Record, State#state{premaster_secret = undefined, + public_key_info = undefined, + tls_handshake_history = ssl_handshake:init_handshake_history()}). passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> case Buffer of @@ -2342,38 +610,10 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, {stop, normal, State0} end. -write_application_data(Data0, From, #state{socket = Socket, - negotiated_version = Version, - transport_cb = Transport, - connection_states = ConnectionStates0, - send_queue = SendQueue, - socket_options = SockOpts, - ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> - Data = encode_packet(Data0, SockOpts), - - case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of - true -> - renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue), - renegotiation = {true, internal}}); - false -> - {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0), - Result = Transport:send(Socket, Msgs), - {reply, Result, - connection, State#state{connection_states = ConnectionStates}, get_timeout(State)} - end. - -time_to_renegotiate(_Data, #connection_states{current_write = - #connection_state{sequence_number = Num}}, RenegotiateAt) -> - - %% We could do test: - %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), - %% but we chose to have a some what lower renegotiateAt and a much cheaper test - is_time_to_renegotiate(Num, RenegotiateAt). - -is_time_to_renegotiate(N, M) when N < M-> - false; -is_time_to_renegotiate(_,_) -> - true. +get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> + infinity; +get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> + HibernateAfter. %% Picks ClientData get_data(_, _, <<>>) -> @@ -2446,7 +686,8 @@ format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, {ok, do_format_reply(Mode, Packet, Header, Data)}; format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, header = Header}, Data) -> - {ssl, ssl_socket:socket(self(), Transport, Socket), do_format_reply(Mode, Packet, Header, Data)}. + {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE), + do_format_reply(Mode, Packet, Header, Data)}. deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From) -> send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data)). @@ -2454,7 +695,8 @@ deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Da format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data) -> {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data) -> - {ssl_error, ssl_socket:socket(self(), Transport, Socket), {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. + {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE), + {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode header(N, Data); @@ -2487,297 +729,99 @@ send_or_reply(false, no_pid, _, _) -> send_or_reply(_, Pid, _From, Data) -> send_user(Pid, Data). -opposite_role(client) -> - server; -opposite_role(server) -> - client. - send_user(Pid, Msg) -> Pid ! Msg. -handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet]} = State) -> - FsmReturn = {next_state, StateName, State#state{tls_packets = []}}, +handle_tls_handshake(Handle, StateName, + #state{protocol_buffers = + #protocol_buffers{tls_packets = [Packet]} = Buffers} = State) -> + FsmReturn = {next_state, StateName, State#state{protocol_buffers = + Buffers#protocol_buffers{tls_packets = []}}}, Handle(Packet, FsmReturn); -handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet | Packets]} = State0) -> - FsmReturn = {next_state, StateName, State0#state{tls_packets = Packets}}, +handle_tls_handshake(Handle, StateName, + #state{protocol_buffers = + #protocol_buffers{tls_packets = [Packet | Packets]} = Buffers} = + State0) -> + FsmReturn = {next_state, StateName, State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_packets = + Packets}}}, case Handle(Packet, FsmReturn) of {next_state, NextStateName, State, _Timeout} -> handle_tls_handshake(Handle, NextStateName, State); {stop, _,_} = Stop -> Stop end. - -next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> - handle_own_alert(Alert, Version, Current, State); - -next_state(_,Next, no_record, State) -> - {next_state, Next, State, get_timeout(State)}; - -next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> - Alerts = decode_alerts(EncAlerts), - handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)}); - -next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, - State0 = #state{tls_handshake_buffer = Buf0, negotiated_version = Version}) -> - Handle = - fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Starts new handshake (renegotiation) - Hs0 = tls_handshake:init_handshake_history(), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs0, - renegotiation = {true, peer}}); - ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Already in negotiation so it will be ignored! - ?MODULE:SName(Packet, State); - ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> - Version = Packet#client_hello.client_version, - Hs0 = tls_handshake:init_handshake_history(), - Hs1 = tls_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1, - renegotiation = {true, peer}}); - ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_history=Hs0}}) -> - Hs1 = tls_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1}); - (_, StopState) -> StopState - end, - try - {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0), - State = State0#state{tls_packets = Packets, tls_handshake_buffer = Buf}, - handle_tls_handshake(Handle, Next, State) - catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, Current, State0) - end; - -next_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> - case read_application_data(Data, State0) of - Stop = {stop,_,_} -> - Stop; - {Record, State} -> - next_state(StateName, StateName, Record, State) - end; -next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = - _ChangeCipher, - #state{connection_states = ConnectionStates0} = State0) -> - ConnectionStates1 = - ssl_record:activate_pending_connection_state(ConnectionStates0, read), - {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), - next_state(Current, Next, Record, State); -next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> - %% Ignore unknown type - {Record, State} = next_record(State0), - next_state(Current, Next, Record, State). - -next_tls_record(Data, #state{tls_record_buffer = Buf0, - tls_cipher_texts = CT0} = State0) -> - case tls_record:get_tls_records(Data, Buf0) of - {Records, Buf1} -> - CT1 = CT0 ++ Records, - next_record(State0#state{tls_record_buffer = Buf1, - tls_cipher_texts = CT1}); - #alert{} = Alert -> - Alert - end. - -next_record(#state{tls_packets = [], tls_cipher_texts = [], socket = Socket, - transport_cb = Transport} = State) -> - ssl_socket:setopts(Transport, Socket, [{active,once}]), - {no_record, State}; -next_record(#state{tls_packets = [], tls_cipher_texts = [CT | Rest], - connection_states = ConnStates0} = State) -> - case tls_record:decode_cipher_text(CT, ConnStates0) of - {Plain, ConnStates} -> - {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end; -next_record(State) -> - {no_record, State}. - -next_record_if_active(State = - #state{socket_options = - #socket_options{active = false}}) -> - {no_record ,State}; - -next_record_if_active(State) -> - next_record(State). - -next_state_connection(StateName, #state{send_queue = Queue0, - negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - connection_states = ConnectionStates0 - } = State) -> - %% Send queued up data that was queued while renegotiating - case queue:out(Queue0) of - {{value, {From, Data}}, Queue} -> - {Msgs, ConnectionStates} = - ssl_record:encode_data(Data, Version, ConnectionStates0), +write_application_data(Data0, From, + #state{socket = Socket, + negotiated_version = Version, + transport_cb = Transport, + connection_states = ConnectionStates0, + send_queue = SendQueue, + socket_options = SockOpts, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> + Data = encode_packet(Data0, SockOpts), + + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue), + renegotiation = {true, internal}}); + false -> + {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0), Result = Transport:send(Socket, Msgs), - gen_fsm:reply(From, Result), - next_state_connection(StateName, - State#state{connection_states = ConnectionStates, - send_queue = Queue}); - {empty, Queue0} -> - next_state_is_connection(StateName, State) + {reply, Result, + connection, State#state{connection_states = ConnectionStates}, get_timeout(State)} end. -%% In next_state_is_connection/1: clear tls_handshake, -%% premaster_secret and public_key_info (only needed during handshake) -%% to reduce memory foot print of a connection. -next_state_is_connection(_, State = - #state{start_or_recv_from = RecvFrom, - socket_options = - #socket_options{active = false}}) when RecvFrom =/= undefined -> - passive_receive(State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = tls_handshake:init_handshake_history()}, connection); - -next_state_is_connection(StateName, State0) -> - {Record, State} = next_record_if_active(State0), - next_state(StateName, connection, Record, State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = tls_handshake:init_handshake_history()}). - -register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> - Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Host, Port, Session), - Session; -register_session(server, _, Port, #session{is_resumable = new} = Session0) -> - Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Port, Session), - Session; -register_session(_, _, _, Session) -> - Session. %% Already registered +encode_packet(Data, #socket_options{packet=Packet}) -> + case Packet of + 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); + 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); + 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); + _ -> Data + end. -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, Port, Session) -> - ssl_manager:invalidate_session(Port, Session). +encode_size_packet(Bin, Size, Max) -> + Len = erlang:byte_size(Bin), + case Len > Max of + true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); + false -> <<Len:Size, Bin/binary>> + end. -initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, - {CbModule, DataTag, CloseTag, ErrorTag}) -> - ConnectionStates = ssl_record:init_connection_states(Role), +time_to_renegotiate(_Data, + #connection_states{current_write = + #connection_state{sequence_number = Num}}, + RenegotiateAt) -> - SessionCacheCb = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - Cb; - _ -> - ssl_session_cache - end, - - Monitor = erlang:monitor(process, User), - - #state{socket_options = SocketOptions, - %% We do not want to save the password in the state so that - %% could be written in the clear into error logs. - ssl_options = SSLOptions#ssl_options{password = undefined}, - session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, - connection_states = ConnectionStates, - tls_handshake_buffer = <<>>, - tls_record_buffer = <<>>, - tls_cipher_texts = [], - user_application = {Monitor, User}, - user_data_buffer = <<>>, - session_cache_cb = SessionCacheCb, - renegotiation = {false, first}, - start_or_recv_from = undefined, - send_queue = queue:new() - }. - -get_socket_opts(_,_,[], _, Acc) -> - {ok, Acc}; -get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, - [{mode, SockOpts#socket_options.mode} | Acc]); -get_socket_opts(Transport, Socket, [packet | Tags], SockOpts, Acc) -> - case SockOpts#socket_options.packet of - {Type, headers} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); - Type -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) - end; -get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, - [{header, SockOpts#socket_options.header} | Acc]); -get_socket_opts(Transport, Socket, [active | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, - [{active, SockOpts#socket_options.active} | Acc]); -get_socket_opts(Transport, Socket, [Tag | Tags], SockOpts, Acc) -> - try ssl_socket:getopts(Transport, Socket, [Tag]) of - {ok, [Opt]} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]); - {error, Error} -> - {error, {options, {socket_options, Tag, Error}}} - catch - %% So that inet behavior does not crash our process - _:Error -> {error, {options, {socket_options, Tag, Error}}} - end; -get_socket_opts(_, _,Opts, _,_) -> - {error, {options, {socket_options, Opts, function_clause}}}. - -set_socket_opts(_,_, [], SockOpts, []) -> - {ok, SockOpts}; -set_socket_opts(Transport, Socket, [], SockOpts, Other) -> - %% Set non emulated options - try ssl_socket:setopts(Transport, Socket, Other) of - ok -> - {ok, SockOpts}; - {error, InetError} -> - {{error, {options, {socket_options, Other, InetError}}}, SockOpts} - catch - _:Error -> - %% So that inet behavior does not crash our process - {{error, {options, {socket_options, Other, Error}}}, SockOpts} - end; + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), + %% but we chose to have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). -set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) when Mode == list; Mode == binary -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{mode = Mode}, Other); -set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) when Packet == raw; - Packet == 0; - Packet == 1; - Packet == 2; - Packet == 4; - Packet == asn1; - Packet == cdr; - Packet == sunrm; - Packet == fcgi; - Packet == tpkt; - Packet == line; - Packet == http; - Packet == httph; - Packet == http_bin; - Packet == httph_bin -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{packet = Packet}, Other); -set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) when is_integer(Header) -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{header = Header}, Other); -set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) -> - {{error,{options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) when Active == once; - Active == true; - Active == false -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{active = Active}, Other); -set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; -set_socket_opts(Transport, Socket, [Opt | Opts], SockOpts, Other) -> - set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]). +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. +renegotiate(#state{role = client} = State) -> + %% Handle same way as if server requested + %% the renegotiation + Hs0 = ssl_handshake:init_handshake_history(), + connection(#hello_request{}, State#state{tls_handshake_history = Hs0}); +renegotiate(#state{role = server, + socket = Socket, + transport_cb = Transport, + negotiated_version = Version, + connection_states = ConnectionStates0} = State0) -> + HelloRequest = ssl_handshake:hello_request(), + Frag = tls_handshake:encode_handshake(HelloRequest, Version), + Hs0 = ssl_handshake:init_handshake_history(), + {BinMsg, ConnectionStates} = + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + {Record, State} = next_record(State0#state{connection_states = + ConnectionStates, + tls_handshake_history = Hs0}), + next_state(connection, hello, Record, State#state{allow_renegotiate = true}). handle_alerts([], Result) -> Result; @@ -2788,7 +832,8 @@ handle_alerts([Alert | Alerts], {next_state, StateName, State, _Timeout}) -> handle_alerts(Alerts, handle_alert(Alert, StateName, State)). handle_alert(#alert{level = ?FATAL} = Alert, StateName, - #state{socket = Socket, transport_cb = Transport, ssl_options = SslOpts, start_or_recv_from = From, host = Host, + #state{socket = Socket, transport_cb = Transport, + ssl_options = SslOpts, start_or_recv_from = From, host = Host, port = Port, session = Session, user_application = {_Mon, Pid}, role = Role, socket_options = Opts} = State) -> invalidate_session(Role, Host, Port, Session), @@ -2838,10 +883,12 @@ alert_user(Transport, Socket, Active, Pid, From, Alert, Role) -> case ssl_alert:reason_code(Alert, Role) of closed -> send_or_reply(Active, Pid, From, - {ssl_closed, ssl_socket:socket(self(), Transport, Socket)}); + {ssl_closed, ssl_socket:socket(self(), + Transport, Socket, ?MODULE)}); ReasonCode -> send_or_reply(Active, Pid, From, - {ssl_error, ssl_socket:socket(self(), Transport, Socket), ReasonCode}) + {ssl_error, ssl_socket:socket(self(), + Transport, Socket, ?MODULE), ReasonCode}) end. log_alert(true, Info, Alert) -> @@ -2857,7 +904,7 @@ handle_own_alert(Alert, Version, StateName, ssl_options = SslOpts} = State) -> try %% Try to tell the other side {BinMsg, _} = - encode_alert(Alert, Version, ConnectionStates), + ssl_alert:encode(Alert, Version, ConnectionStates), Transport:send(Socket, BinMsg), workaround_transport_delivery_problems(Socket, Transport) catch _:_ -> %% Can crash if we are in a uninitialized state @@ -2888,73 +935,26 @@ handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = Stat Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), handle_own_alert(Alert, Version, {Info, Msg}, State). -make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; -make_premaster_secret(_, _) -> - undefined. - -ack_connection(#state{renegotiation = {true, Initiater}} = State) - when Initiater == internal; - Initiater == peer -> - State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {true, From}} = State) -> - gen_fsm:reply(From, ok), - State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {false, first}, - start_or_recv_from = StartFrom, - timer = Timer} = State) when StartFrom =/= undefined -> - gen_fsm:reply(StartFrom, connected), - cancel_timer(Timer), - State#state{renegotiation = undefined, start_or_recv_from = undefined, timer = undefined}; -ack_connection(State) -> - State. -renegotiate(#state{role = client} = State) -> - %% Handle same way as if server requested - %% the renegotiation - Hs0 = tls_handshake:init_handshake_history(), - connection(#hello_request{}, State#state{tls_handshake_history = Hs0}); -renegotiate(#state{role = server, - socket = Socket, - transport_cb = Transport, - negotiated_version = Version, - connection_states = ConnectionStates0} = State0) -> - HelloRequest = ssl_handshake:hello_request(), - Frag = tls_handshake:encode_handshake(HelloRequest, Version), - Hs0 = tls_handshake:init_handshake_history(), - {BinMsg, ConnectionStates} = - ssl_record:encode_handshake(Frag, Version, ConnectionStates0), - Transport:send(Socket, BinMsg), - {Record, State} = next_record(State0#state{connection_states = - ConnectionStates, - tls_handshake_history = Hs0}), - next_state(connection, hello, Record, State#state{allow_renegotiate = true}). - -notify_senders(SendQueue) -> - lists:foreach(fun({From, _}) -> - gen_fsm:reply(From, {error, closed}) - end, queue:to_list(SendQueue)). +handle_close_alert(Data, StateName, State0) -> + case next_tls_record(Data, State0) of + {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> + [Alert|_] = decode_alerts(EncAlerts), + handle_normal_shutdown(Alert, StateName, State); + _ -> + ok + end. -notify_renegotiater({true, From}) when not is_atom(From) -> - gen_fsm:reply(From, {error, closed}); -notify_renegotiater(_) -> +cancel_timer(undefined) -> + ok; +cancel_timer(Timer) -> + erlang:cancel_timer(Timer), ok. -terminate_alert(Reason, Version, ConnectionStates) when Reason == normal; - Reason == user_close -> - {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates), - BinAlert; -terminate_alert({shutdown, _}, Version, ConnectionStates) -> - {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates), - BinAlert; - -terminate_alert(_, Version, ConnectionStates) -> - {BinAlert, _} = encode_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR), - Version, ConnectionStates), - BinAlert. +invalidate_session(client, Host, Port, Session) -> + ssl_manager:invalidate_session(Host, Port, Session); +invalidate_session(server, _, Port, Session) -> + ssl_manager:invalidate_session(Port, Session). workaround_transport_delivery_problems(Socket, gen_tcp = Transport) -> %% Standard trick to try to make sure all @@ -2970,134 +970,3 @@ workaround_transport_delivery_problems(Socket, gen_tcp = Transport) -> Transport:recv(Socket, 0, 30000); workaround_transport_delivery_problems(Socket, Transport) -> Transport:close(Socket). - -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> - infinity; -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> - HibernateAfter. - -handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>}}) -> - %% No trusted certs specified - ok; -handle_trusted_certs_db(#state{cert_db_ref = Ref, - cert_db = CertDb, - ssl_options = #ssl_options{cacertfile = undefined}}) -> - %% Certs provided as DER directly can not be shared - %% with other connections and it is safe to delete them when the connection ends. - ssl_pkix_db:remove_trusted_certs(Ref, CertDb); -handle_trusted_certs_db(#state{file_ref_db = undefined}) -> - %% Something went wrong early (typically cacertfile does not exist) so there is nothing to handle - ok; -handle_trusted_certs_db(#state{cert_db_ref = Ref, - file_ref_db = RefDb, - ssl_options = #ssl_options{cacertfile = File}}) -> - case ssl_pkix_db:ref_count(Ref, RefDb, -1) of - 0 -> - ssl_manager:clean_cert_db(Ref, File); - _ -> - ok - end. - -get_current_connection_state_prf(CStates, Direction) -> - CS = ssl_record:current_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. -get_pending_connection_state_prf(CStates, Direction) -> - CS = ssl_record:pending_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. - -start_or_recv_cancel_timer(infinity, _RecvFrom) -> - undefined; -start_or_recv_cancel_timer(Timeout, RecvFrom) -> - erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). - -cancel_timer(undefined) -> - ok; -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - ok. - -handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} = State) -> - ssl_socket:setopts(Transport, Socket, [{active, false}]), - case Transport:recv(Socket, 0, 0) of - {error, closed} -> - ok; - {ok, Data} -> - handle_close_alert(Data, StateName, State) - end. - -handle_close_alert(Data, StateName, State0) -> - case next_tls_record(Data, State0) of - {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> - [Alert|_] = decode_alerts(EncAlerts), - handle_normal_shutdown(Alert, StateName, State); - _ -> - ok - end. -negotiated_hashsign(undefined, Algo, Version) -> - default_hashsign(Version, Algo); -negotiated_hashsign(HashSign = {_, _}, _, _) -> - HashSign. - -%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms -%% If the client does not send the signature_algorithms extension, the -%% server MUST do the following: -%% -%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, -%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had -%% sent the value {sha1,rsa}. -%% -%% - If the negotiated key exchange algorithm is one of (DHE_DSS, -%% DH_DSS), behave as if the client had sent the value {sha1,dsa}. -%% -%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, -%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. - -default_hashsign(_Version = {Major, Minor}, KeyExchange) - when Major >= 3 andalso Minor >= 3 andalso - (KeyExchange == rsa orelse - KeyExchange == dhe_rsa orelse - KeyExchange == dh_rsa orelse - KeyExchange == ecdhe_rsa orelse - KeyExchange == ecdh_rsa orelse - KeyExchange == srp_rsa) -> - {sha, rsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == rsa; - KeyExchange == dhe_rsa; - KeyExchange == dh_rsa; - KeyExchange == ecdhe_rsa; - KeyExchange == ecdh_rsa; - KeyExchange == srp_rsa -> - {md5sha, rsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == ecdhe_ecdsa; - KeyExchange == ecdh_ecdsa -> - {sha, ecdsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == dhe_dss; - KeyExchange == dh_dss; - KeyExchange == srp_dss -> - {sha, dsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == dh_anon; - KeyExchange == ecdh_anon; - KeyExchange == psk; - KeyExchange == dhe_psk; - KeyExchange == rsa_psk; - KeyExchange == srp_anon -> - {null, anon}. - -select_curve(#state{client_ecc = {[Curve|_], _}}) -> - {namedCurve, Curve}; -select_curve(_) -> - {namedCurve, ?secp256k1}. - -is_anonymous(Algo) when Algo == dh_anon; - Algo == ecdh_anon; - Algo == psk; - Algo == dhe_psk; - Algo == rsa_psk; - Algo == srp_anon -> - true; -is_anonymous(_) -> - false. 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 02bfa69fc5..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 @@ -56,7 +53,7 @@ client_hello(Host, Port, ConnectionStates, SecParams = Pending#connection_state.security_parameters, CipherSuites = ssl_handshake:available_suites(UserSuites, Version), - Extensions = ssl_handshake:client_hello_extensions(Version, CipherSuites, + Extensions = ssl_handshake:client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), @@ -70,25 +67,6 @@ client_hello(Host, Port, ConnectionStates, }. %%-------------------------------------------------------------------- --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 hello(#server_hello{} | #client_hello{}, #ssl_options{}, #connection_states{} | {inet:port_number(), #session{}, db_handle(), atom(), #connection_states{}, binary() | undefined}, @@ -120,17 +98,16 @@ hello(#client_hello{client_version = ClientVersion, cipher_suites = CipherSuites, compression_methods = Compressions, random = Random, - extensions = HelloExt}, + extensions = #hello_extensions{elliptic_curves = Curves} = HelloExt}, #ssl_options{versions = Versions} = SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) -> Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), case tls_record:is_acceptable_version(Version, Versions) of true -> - %% TODO: need to take supported Curves into Account when selecting the CipherSuite.... - %% if whe have an ECDSA cert with an unsupported curve, we need to drop ECDSA ciphers + ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, Compressions, - Port, Session0, Version, + Port, Session0#session{ecc = ECCCurve}, Version, SslOpts, Cache, CacheCb, Cert), case CipherSuite of no_suite -> @@ -168,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>>, @@ -206,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_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 9695710230..7e8e8d2611 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -34,7 +34,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [decode_hello_handshake, decode_single_hello_extension_correctly, - decode_unknown_hello_extension_correctly]. + decode_unknown_hello_extension_correctly, + encode_single_hello_sni_extension_correctly]. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- @@ -73,3 +74,12 @@ decode_unknown_hello_extension_correctly(_Config) -> Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>), #renegotiation_info{renegotiated_connection = <<0>>} = Extensions#hello_extensions.renegotiation_info. + +encode_single_hello_sni_extension_correctly(_Config) -> + Exts = #hello_extensions{sni = #sni{hostname = "test.com"}}, + SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08, + $t, $e, $s, $t, $., $c, $o, $m>>, + ExtSize = byte_size(SNI), + HelloExt = <<ExtSize:16/unsigned-big-integer, SNI/binary>>, + Encoded = ssl_handshake:encode_hello_extensions(Exts), + HelloExt = Encoded. 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, |