From 810c34a7991f2b6edd5e9f41e3c667958a5b2bc8 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 10 Sep 2013 17:43:40 +0200 Subject: ssl: Refactor handshake and record handling --- lib/ssl/src/dtls_handshake.erl | 10 ++-- lib/ssl/src/dtls_record.erl | 123 ++++++++++++++--------------------------- lib/ssl/src/ssl_cipher.erl | 2 +- lib/ssl/src/ssl_record.erl | 38 ++++++++++++- lib/ssl/src/tls_handshake.erl | 2 - lib/ssl/src/tls_record.erl | 82 ++++++++++----------------- 6 files changed, 113 insertions(+), 144 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index d0f9649f9f..d898da65fb 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -87,11 +87,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) -> - <>. - %%-------------------------------------------------------------------- encode_handshake(Package, Version, MsgSeq, Mss) -> {MsgType, Bin} = enc_hs(Package, Version), @@ -426,3 +421,8 @@ decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, < 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) -> + <>. diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index f667458a10..7959c4d860 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()) -> @@ -323,62 +333,13 @@ encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) -> [<>, 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, + mac_hash(Version, MacAlg, MacSecret, (Epoch bsl 48) + SeqNo, 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/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index e6ed0d8626..b2077c662a 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -72,7 +72,7 @@ security_parameters(Version, CipherSuite, SecParams) -> hash_size = hash_size(Hash)}. %%-------------------------------------------------------------------- --spec cipher(cipher_enum(), #cipher_state{}, binary(), binary(), tls_version()) -> +-spec cipher(cipher_enum(), #cipher_state{}, binary(), iolist(), tls_version()) -> {binary(), #cipher_state{}}. %% %% Description: Encrypts the data and the MAC using chipher described diff --git a/lib/ssl/src/ssl_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/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 262f2d929f..f783bacff6 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -26,10 +26,8 @@ -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, 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), [<>, 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) -> -- cgit v1.2.3 From b3d5f82bd3baf5b63ea97c017cb83674ca703c66 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 17 Sep 2013 15:28:20 +0200 Subject: ssl: Refactor connection and handshake handling --- lib/ssl/src/Makefile | 11 +- lib/ssl/src/dtls_connection.erl | 101 --- lib/ssl/src/dtls_connection.hrl | 51 ++ lib/ssl/src/dtls_handshake.erl | 15 +- lib/ssl/src/ssl.app.src | 2 +- lib/ssl/src/ssl_connection.erl | 1268 ++++++++++++++++++++++++++ lib/ssl/src/ssl_connection.hrl | 81 ++ lib/ssl/src/ssl_handshake.erl | 56 +- lib/ssl/src/tls_connection.erl | 1652 ++++------------------------------ lib/ssl/src/tls_connection.hrl | 37 + lib/ssl/src/tls_handshake.erl | 58 +- lib/ssl/test/ssl_npn_hello_SUITE.erl | 4 +- 12 files changed, 1677 insertions(+), 1659 deletions(-) create mode 100644 lib/ssl/src/dtls_connection.hrl create mode 100644 lib/ssl/src/ssl_connection.erl create mode 100644 lib/ssl/src/ssl_connection.hrl create mode 100644 lib/ssl/src/tls_connection.hrl (limited to 'lib') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 6744e2f256..bf9ba1a1cc 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -55,6 +55,7 @@ MODULES= \ ssl_srp_primes \ tls_connection \ dtls_connection \ + ssl_connection \ ssl_connection_sup \ tls_handshake \ dtls_handshake\ @@ -73,8 +74,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_internal.hrl \ ssl_record.hrl tls_record.hrl dtls_record.hrl ssl_srp.hrl ERL_FILES= \ @@ -148,9 +150,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_connection.erl b/lib/ssl/src/dtls_connection.erl index fda488501c..0271c4490a 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -18,82 +18,6 @@ %% -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 = <<>> -%% }). - %% %%==================================================================== @@ -196,32 +120,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_handshake.erl b/lib/ssl/src/dtls_handshake.erl index d898da65fb..6a54c5a305 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{}, diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 44798f8c12..1d47aa9374 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -20,13 +20,13 @@ ssl, ssl_session_cache_api, %% Both TLS/SSL and DTLS + 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, diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl new file mode 100644 index 0000000000..581c80e52e --- /dev/null +++ b/lib/ssl/src/ssl_connection.erl @@ -0,0 +1,1268 @@ +%% +%% %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_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"). + +-export([hello/3, abbreviated/3, certify/3, cipher/3, connection/3]). + +%%-------------------------------------------------------------------- +-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, + Connection: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, + Connection: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{} = KeyExchangeMsg, + #state{role = client, negotiated_version = Version, + key_algorithm = Alg} = State0, 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 -> + case handle_server_key(KeyExchangeMsg, State0) of + #state{} = State1 -> + {Record, State} = Connection:next_record(State1), + Connection:next_state(certify, certify, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0) + 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, + premaster_secret = undefined, + role = client, + key_algorithm = Alg} = State0, Connection) + when Alg == psk -> + case server_psk_master_secret(PSKIdentity, State0) of + #state{} = State -> + client_certify_and_key_exchange(State, Connection); + #alert{} = Alert -> + Connection: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, Connection) + 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 = <>, + State1 = State0#state{premaster_secret = PremasterSecret}, + State = master_from_premaster_secret(RealPMS, State1), + client_certify_and_key_exchange(State, Connection); + #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, 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 = Connection: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). + +%%-------------------------------------------------------------------- +%%% 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), + 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, + 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{negotiated_version = Version, + connection_states = ConnectionStates0, + session = Session0, + private_key = Key} = State0, Connection) -> + PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), + 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(certify, cipher, Record, State); + #alert{} = Alert -> + Connection: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, + Connection) -> + case dh_master_secret(Params, ClientPublicDhKey, ServerDhPrivateKey, State0) of + #state{} = State1 -> + {Record, State} = Connection:next_record(State1), + Connection:next_state(certify, cipher, Record, State); + #alert{} = Alert -> + Connection: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, Connection) -> + case ec_dh_master_secret(ECDHKey, #'ECPoint'{point = ClientPublicEcDhPoint}, State0) of + #state{} = State1 -> + {Record, State} = Connection:next_record(State1), + Connection:next_state(certify, cipher, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0) + end; + +certify_client_key_exchange(#client_psk_identity{identity = ClientPSKIdentity}, + #state{negotiated_version = Version} = State0, Connection) -> + case server_psk_master_secret(ClientPSKIdentity, State0) of + #state{} = State1 -> + {Record, State} = Connection:next_record(State1), + Connection:next_state(certify, cipher, Record, State); + #alert{} = Alert -> + Connection: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, + Connection) -> + case dhe_psk_master_secret(ClientPSKIdentity, P, G, ClientPublicDhKey, + ServerDhPrivateKey, State0) of + #state{} = State1 -> + {Record, State} = Connection:next_record(State1), + Connection:next_state(certify, cipher, Record, State); + #alert{} = Alert -> + Connection: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, Connection) -> + PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), + case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of + #state{} = State1 -> + {Record, State} = Connection:next_record(State1), + Connection:next_state(certify, cipher, Record, State); + #alert{} = Alert -> + Connection: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, Connection) -> + case server_srp_master_secret(Verifier, Prime, ClientPublicKey, State0) of + #state{} = State1 -> + {Record, State} = Connection:next_record(State1), + Connection:next_state(certify, cipher, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0) + end. + +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. + +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). + +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. + +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, + <>), + case ssl_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of + true -> + server_master_secret(Params, State); + false -> + ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) + end. + +make_premaster_secret({MajVer, MinVer}, rsa) -> + Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + <>; +make_premaster_secret(_, _) -> + undefined. + +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}; + #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 = <>, + 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 = <>, + 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 = <>, + 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}; + #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, + Connection) -> + ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0), + Connection:next_state_connection(cipher, + Connection: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, Connection: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. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl new file mode 100644 index 0000000000..92134dfeb3 --- /dev/null +++ b/lib/ssl/src/ssl_connection.hrl @@ -0,0 +1,81 @@ + +%% +%% %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). + +-record(state, { + role :: client | server, + user_application :: {Monitor::reference(), User::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 + protocol_buffers, + tls_handshake_history, % tls_handshake_history() + 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 ::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_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 9142a260b1..ad80c5ce7b 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 + ]). %% Encode/Decode -export([encode_handshake/2, encode_hello_extensions/1, @@ -77,6 +79,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{}. %% @@ -405,6 +426,37 @@ verify_connection(Version, #finished{verify_data = Data}, _ -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) end. + +%%-------------------------------------------------------------------- +-spec init_handshake_history() -> tls_handshake_history(). + +%% +%% Description: Initialize the empty handshake history buffer. +%%-------------------------------------------------------------------- +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 + <>) -> + update_handshake_history(Handshake, + <>); +update_handshake_history({Handshake0, _Prev}, Data) -> + {[Data|Handshake0], Handshake0}. + %%-------------------------------------------------------------------- -spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 39595b4f95..a8885201f8 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -29,6 +29,7 @@ -behaviour(gen_fsm). +-include("tls_connection.hrl"). -include("tls_handshake.hrl"). -include("ssl_alert.hrl"). -include("tls_record.hrl"). @@ -41,7 +42,11 @@ -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]). + peer_certificate/1, renegotiation/1, negotiated_next_protocol/1, prf/5, + send_handshake/2, send_alert/2, send_change_cipher/2, next_record/1, next_state/4, + handle_unexpected_message/3, ack_connection/1, handle_own_alert/4, next_state_connection/2, + register_session/4 + ]). %% Called by ssl_connection_sup -export([start_link/7]). @@ -51,61 +56,6 @@ abbreviated/2, connection/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). --record(state, { - role, % client | server - user_application, % {MonitorRef, pid()} - transport_cb, % atom() - callback module - data_tag, % atom() - ex tcp. - close_tag, % atom() - ex tcp_closed - error_tag, % atom() - ex tcp_error - host, % string() | ipadress() - port, % integer() - socket, % socket() - ssl_options, % #ssl_options{} - socket_options, % #socket_options{} - connection_states, % #connection_states{} from ssl_record.hrl - tls_packets = [], % Not yet handled decode ssl/tls packets. - tls_record_buffer, % binary() buffer of incomplete records - tls_handshake_buffer, % binary() buffer of incomplete handshakes - tls_handshake_history, % tls_handshake_history() - tls_cipher_texts, % list() received but not deciphered yet - cert_db, % - session, % #session{} from tls_handshake.hrl - session_cache, % - session_cache_cb, % - negotiated_version, % tls_version() - client_certificate_requested = false, - key_algorithm, % atom as defined by cipher_suite - hashsign_algorithm = {undefined, undefined}, - cert_hashsign_algorithm, - public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams} - private_key, % PKIX: #'RSAPrivateKey'{} - diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side - diffie_hellman_keys, % {PublicKey, PrivateKey} - psk_identity, % binary() - server psk identity hint - srp_params, % #srp_user{} - srp_keys, % {PublicKey, PrivateKey} - premaster_secret, % - file_ref_db, % ets() - cert_db_ref, % ref() - bytes_to_read, % integer(), # bytes to read in passive mode - user_data_buffer, % binary() - renegotiation, % {boolean(), From | internal | peer} - start_or_recv_from, % "gen_fsm From" - timer, % start_or_recv_timer - send_queue, % queue() - terminated = false, % - allow_renegotiate = true, - expecting_next_protocol_negotiation = false :: boolean(), - next_protocol = undefined :: undefined | binary() - }). - --define(DEFAULT_DIFFIE_HELLMAN_PARAMS, - #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, - base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). --define(WAIT_TO_ALLOW_RENEGOTIATION, 12000). - - %%==================================================================== %% Internal application API %%==================================================================== @@ -280,6 +230,37 @@ renegotiation(ConnectionPid) -> prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> sync_send_all_state_event(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). + +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 + }. + +send_alert(Alert, #state{negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0} = State0) -> + {BinMsg, ConnectionStates} = + encode_alert(Alert, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + State0#state{connection_states = ConnectionStates}. + +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 %%==================================================================== @@ -297,7 +278,7 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> 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 {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, Key, DHParams} -> @@ -325,23 +306,18 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> %% same name as the current state name StateName is called to handle %% the event. It is also called if a timeout occurs. %% - -%%-------------------------------------------------------------------- --spec hello(start | #hello_request{} | #client_hello{} | #server_hello{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- hello(start, #state{host = Host, port = Port, role = client, - ssl_options = SslOpts, - session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + session_cache = Cache, session_cache_cb = CacheCb, + transport_cb = Transport, socket = Socket, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, - Handshake0 = tls_handshake:init_handshake_history(), + Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Hello, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -353,14 +329,29 @@ 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(Hello = #client_hello{client_version = ClientVersion, + extensions = #hello_extensions{hash_signs = HashSigns}}, + State = #state{connection_states = ConnectionStates0, + port = Port, session = #session{own_certificate = Cert} = Session0, + renegotiation = {Renegotiation, _}, + session_cache = Cache, + session_cache_cb = CacheCb, + 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}, + ConnectionStates, + #hello_extensions{ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves} = ServerHelloExt} -> + ssl_connection:hello({common_client_hello, Type, ServerHelloExt, HashSign}, + State#state{connection_states = ConnectionStates, + negotiated_version = Version, + session = Session, + client_ecc = {EllipticCurves, EcPointFormats}}, ?MODULE); + #alert{} = Alert -> + handle_own_alert(Alert, ClientVersion, hello, State) + end; hello(#server_hello{cipher_suite = CipherSuite, compression_method = Compression} = Hello, #state{session = #session{session_id = OldId}, @@ -397,446 +388,38 @@ hello(#server_hello{cipher_suite = CipherSuite, handle_new_session(NewId, CipherSuite, Compression, State#state{connection_states = ConnectionStates}); false -> - handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) + handle_resumed_session(NewId, + State#state{connection_states = ConnectionStates}) end end; -hello(Hello = #client_hello{client_version = ClientVersion, - extensions = #hello_extensions{hash_signs = HashSigns}}, - State = #state{connection_states = ConnectionStates0, - port = Port, - session = #session{own_certificate = Cert} = Session0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb, - ssl_options = SslOpts}) -> - HashSign = ssl_handshake:select_hashsign(HashSigns, Cert), - case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, - ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, #session{cipher_suite = CipherSuite} = Session}, - ConnectionStates, 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}); - #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})); - #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) - 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 }; + 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 = <>, - State1 = State0#state{premaster_secret = PremasterSecret}, - State = master_from_premaster_secret(RealPMS, State1), - client_certify_and_key_exchange(State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end; - -%% Master secret was determined with help of server-key exchange msg -certify(#server_hello_done{}, - #state{session = #session{master_secret = MasterSecret} = Session, - connection_states = ConnectionStates0, - negotiated_version = Version, - premaster_secret = undefined, - role = client} = State0) -> - case ssl_handshake:master_secret(tls_record, Version, Session, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - State = State0#state{connection_states = ConnectionStates}, - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -%% Master secret is calculated from premaster_secret -certify(#server_hello_done{}, - #state{session = Session0, - connection_states = ConnectionStates0, - negotiated_version = Version, - premaster_secret = PremasterSecret, - role = client} = State0) -> - case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify(#client_key_exchange{} = Msg, - #state{role = server, - client_certificate_requested = true, - ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State) -> - %% We expect a certificate here - handle_unexpected_message(Msg, certify_client_key_exchange, State); - -certify(#client_key_exchange{exchange_keys = Keys}, - State = #state{key_algorithm = KeyAlg, negotiated_version = Version}) -> - try - certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), State) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) - end; - - -certify(timeout, State) -> - { next_state, certify, State, hibernate }; + ssl_connection:abbreviated(Msg, State, ?MODULE). certify(Msg, State) -> - handle_unexpected_message(Msg, certify, State). - -certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{negotiated_version = Version, - connection_states = ConnectionStates0, - session = Session0, - private_key = Key} = State0) -> - PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), - case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, - ConnectionStates0, server) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State1 = State0#state{connection_states = ConnectionStates, - session = Session}, - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, - #state{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{} = Params, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> - case dh_master_secret(Params, ClientPublicDhKey, ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{negotiated_version = Version, - diffie_hellman_keys = ECDHKey} = State0) -> - case ec_dh_master_secret(ECDHKey, #'ECPoint'{point = ClientPublicEcDhPoint}, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_psk_identity{identity = ClientPSKIdentity}, - #state{negotiated_version = Version} = State0) -> - case server_psk_master_secret(ClientPSKIdentity, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_dhe_psk_identity{ - identity = ClientPSKIdentity, - dh_public = ClientPublicDhKey}, - #state{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{prime = P, - base = G}, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> - case dhe_psk_master_secret(ClientPSKIdentity, P, G, ClientPublicDhKey, ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_rsa_psk_identity{ - identity = PskIdentity, - exchange_keys = - #encrypted_premaster_secret{premaster_secret= EncPMS}}, - #state{negotiated_version = Version, - private_key = Key} = State0) -> - PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), - case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_srp_public{srp_a = ClientPublicKey}, - #state{negotiated_version = Version, - srp_params = - #srp_user{prime = Prime, - verifier = Verifier} - } = State0) -> - case server_srp_master_secret(Verifier, Prime, ClientPublicKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. - -%%-------------------------------------------------------------------- --spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -cipher(#hello_request{}, State0) -> - {Record, State} = next_record(State0), - next_state(cipher, hello, Record, State); - -cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, - #state{role = server, - public_key_info = {Algo, _, _} =PublicKeyInfo, - negotiated_version = Version, - session = #session{master_secret = MasterSecret}, - tls_handshake_history = Handshake - } = State0) -> - - HashSign = ssl_handshake:select_cert_hashsign(CertHashSign, Algo, Version), - case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, - Version, HashSign, MasterSecret, Handshake) of - valid -> - {Record, State} = next_record(State0), - next_state(cipher, cipher, Record, State#state{cert_hashsign_algorithm = HashSign}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State0) - end; - -%% client must send a next protocol message if we are expecting it -cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true, - next_protocol = undefined, negotiated_version = Version} = State0) -> - handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); - -cipher(#finished{verify_data = Data} = Finished, - #state{negotiated_version = Version, - host = Host, - port = Port, - role = Role, - session = #session{master_secret = MasterSecret} - = Session0, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> - case ssl_handshake:verify_connection(Version, Finished, - opposite_role(Role), - get_current_connection_state_prf(ConnectionStates0, read), - MasterSecret, Handshake0) of - verified -> - Session = register_session(Role, Host, Port, Session0), - cipher_role(Role, Data, Session, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State) - end; - -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -cipher(#next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> - {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), - next_state(cipher, cipher, Record, State); - -cipher(timeout, State) -> - { next_state, cipher, State, hibernate }; + ssl_connection:certify(Msg, State, ?MODULE). cipher(Msg, State) -> - handle_unexpected_message(Msg, cipher, State). + ssl_connection:cipher(Msg, State, ?MODULE). -%%-------------------------------------------------------------------- --spec connection(#hello_request{} | #client_hello{} | term(), - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- connection(#hello_request{}, #state{host = Host, port = Port, - socket = Socket, session = #session{own_certificate = Cert} = Session0, session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts, - negotiated_version = Version, - transport_cb = Transport, connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - tls_handshake_history = Handshake0} = State0) -> + renegotiation = {Renegotiation, _}} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), - - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - {Record, State} = next_record(State0#state{connection_states = - ConnectionStates, - session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake}), + State1 = send_handshake(Hello, State0), + {Record, State} = + next_record( + State1#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}), next_state(connection, hello, Record, State); + connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html @@ -846,21 +429,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 @@ -1367,900 +942,20 @@ sync_send_all_state_event(FsmPid, Event) -> {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. - -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. - -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. - -do_client_certify_and_key_exchange(State0) -> - State1 = certify_client(State0), - State2 = key_exchange(State1), - verify_client_cert(State2). - -server_certify_and_key_exchange(State0) -> - State1 = certify_server(State0), - State2 = key_exchange(State1), - request_client_cert(State2). - -server_hello(ServerHello, #state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_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) -> - - HelloDone = ssl_handshake:server_hello_done(), - - {BinHelloDone, ConnectionStates, Handshake} = - encode_handshake(HelloDone, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinHelloDone), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}. - -certify_server(#state{key_algorithm = Algo} = State) - when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; Algo == srp_anon -> - State; - -certify_server(#state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - session = #session{own_certificate = OwnCert}} = State) -> - case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of - CertMsg = #certificate{} -> - {BinCertMsg, ConnectionStates, Handshake} = - encode_handshake(CertMsg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinCertMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake - }; - Alert = #alert{} -> - throw(Alert) - end. - -key_exchange(#state{role = server, key_algorithm = rsa} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == dhe_dss; - Algo == dhe_rsa; - Algo == dh_anon -> - DHKeys = public_key:generate_key(Params), - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = DHKeys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State) - when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> - State#state{diffie_hellman_keys = Key}; -key_exchange(#state{role = server, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport, - session = #session{ecc = Curve} - } = State) - when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa; - Algo == ecdh_anon -> - - ECDHKeys = public_key:generate_key(Curve), - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake1} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = ECDHKeys, - tls_handshake_history = Handshake1}; - -key_exchange(#state{role = server, key_algorithm = psk, - ssl_options = #ssl_options{psk_identity = undefined}} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = dhe_psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - DHKeys = public_key:generate_key(Params), - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = DHKeys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = rsa_psk, - ssl_options = #ssl_options{psk_identity = undefined}} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = rsa_psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = Algo, - ssl_options = #ssl_options{user_lookup_fun = LookupFun}, - hashsign_algorithm = HashSignAlgo, - session = #session{srp_username = Username}, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == srp_dss; - Algo == srp_rsa; - Algo == srp_anon -> - SrpParams = handle_srp_identity(Username, LookupFun), - Keys = case generate_srp_server_keys(SrpParams, 0) of - Alert = #alert{} -> - throw(Alert); - Keys0 = {_,_} -> - Keys0 - end, - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - srp_params = SrpParams, - srp_keys = Keys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = rsa, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - premaster_secret = PremasterSecret, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - diffie_hellman_keys = {DhPubKey, _}, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == dhe_dss; - Algorithm == dhe_rsa; - Algorithm == dh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - diffie_hellman_keys = Keys, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; - Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; - Algorithm == ecdh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - key_algorithm = psk, - negotiated_version = Version, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -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}; - -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}; - -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), - - {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}. - -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. - -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, - <>), - case ssl_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of - true -> - server_master_secret(Params, State); - false -> - ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) - end. - -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}; - #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 = <>, - 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 = <>, - 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 = <>, - 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}; - #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. +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}. -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). -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}. encode_packet(Data, #socket_options{packet=Packet}) -> case Packet of @@ -2485,20 +1180,25 @@ 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); @@ -2517,12 +1217,14 @@ next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> 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}) -> + 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 = tls_handshake:init_handshake_history(), + 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}) -> @@ -2531,18 +1233,20 @@ next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, ?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), + 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 = tls_handshake:update_handshake_history(Hs0, Raw), + 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{tls_packets = Packets, tls_handshake_buffer = Buf}, + 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) @@ -2567,26 +1271,32 @@ next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> {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) -> +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{tls_record_buffer = Buf1, - tls_cipher_texts = CT1}); + next_record(State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_record_buffer = Buf1, + tls_cipher_texts = CT1}}); #alert{} = Alert -> Alert end. -next_record(#state{tls_packets = [], tls_cipher_texts = [], socket = Socket, +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{tls_packets = [], tls_cipher_texts = [CT | Rest], +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{tls_cipher_texts = Rest, connection_states = ConnStates}}; + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{tls_cipher_texts = Rest}, + connection_states = ConnStates}}; #alert{} = Alert -> {Alert, State} end; @@ -2630,13 +1340,40 @@ next_state_is_connection(_, State = #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); + tls_handshake_history = ssl_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()}). + tls_handshake_history = ssl_handshake:init_handshake_history()}). + + +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. + register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> Session = Session0#session{is_resumable = true}, @@ -2681,9 +1418,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, port = Port, socket = Socket, connection_states = ConnectionStates, - tls_handshake_buffer = <<>>, - tls_record_buffer = <<>>, - tls_cipher_texts = [], + protocol_buffers = #protocol_buffers{}, user_application = {Monitor, User}, user_data_buffer = <<>>, session_cache_cb = SessionCacheCb, @@ -2911,7 +1646,7 @@ ack_connection(State) -> renegotiate(#state{role = client} = State) -> %% Handle same way as if server requested %% the renegotiation - Hs0 = tls_handshake:init_handshake_history(), + Hs0 = ssl_handshake:init_handshake_history(), connection(#hello_request{}, State#state{tls_handshake_history = Hs0}); renegotiate(#state{role = server, socket = Socket, @@ -2920,7 +1655,7 @@ renegotiate(#state{role = server, connection_states = ConnectionStates0} = State0) -> HelloRequest = ssl_handshake:hello_request(), Frag = tls_handshake:encode_handshake(HelloRequest, Version), - Hs0 = tls_handshake:init_handshake_history(), + Hs0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates} = ssl_record:encode_handshake(Frag, Version, ConnectionStates0), Transport:send(Socket, BinMsg), @@ -2996,12 +1731,6 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref, 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; @@ -3031,66 +1760,3 @@ handle_close_alert(Data, StateName, State0) -> _ -> ok end. -negotiated_hashsign(undefined, Algo, Version) -> - default_hashsign(Version, Algo); -negotiated_hashsign(HashSign = {_, _}, _, _) -> - HashSign. - -%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms -%% If the client does not send the signature_algorithms extension, the -%% server MUST do the following: -%% -%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, -%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had -%% sent the value {sha1,rsa}. -%% -%% - If the negotiated key exchange algorithm is one of (DHE_DSS, -%% DH_DSS), behave as if the client had sent the value {sha1,dsa}. -%% -%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, -%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. - -default_hashsign(_Version = {Major, Minor}, KeyExchange) - when Major >= 3 andalso Minor >= 3 andalso - (KeyExchange == rsa orelse - KeyExchange == dhe_rsa orelse - KeyExchange == dh_rsa orelse - KeyExchange == ecdhe_rsa orelse - KeyExchange == ecdh_rsa orelse - KeyExchange == srp_rsa) -> - {sha, rsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == rsa; - KeyExchange == dhe_rsa; - KeyExchange == dh_rsa; - KeyExchange == ecdhe_rsa; - KeyExchange == ecdh_rsa; - KeyExchange == srp_rsa -> - {md5sha, rsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == ecdhe_ecdsa; - KeyExchange == ecdh_ecdsa -> - {sha, ecdsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == dhe_dss; - KeyExchange == dh_dss; - KeyExchange == srp_dss -> - {sha, dsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == dh_anon; - KeyExchange == ecdh_anon; - KeyExchange == psk; - KeyExchange == dhe_psk; - KeyExchange == rsa_psk; - KeyExchange == srp_anon -> - {null, anon}. - -is_anonymous(Algo) when Algo == dh_anon; - Algo == ecdh_anon; - Algo == psk; - Algo == dhe_psk; - Algo == rsa_psk; - Algo == srp_anon -> - true; -is_anonymous(_) -> - false. diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl new file mode 100644 index 0000000000..f802f2afa9 --- /dev/null +++ b/lib/ssl/src/tls_connection.hrl @@ -0,0 +1,37 @@ +%% +%% %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"). + +-record(protocol_buffers, { + tls_packets = [] :: [binary()], % 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/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index f783bacff6..003614b448 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -30,9 +30,8 @@ -include("ssl_internal.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 @@ -67,25 +66,6 @@ client_hello(Host, Port, ConnectionStates, extensions = Extensions }. -%%-------------------------------------------------------------------- --spec server_hello(binary(), tls_version(), #connection_states{}, - #hello_extensions{}) -> #server_hello{}. -%% -%% Description: Creates a server hello message. -%%-------------------------------------------------------------------- -server_hello(SessionId, Version, ConnectionStates, Extensions) -> - Pending = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, - - #server_hello{server_version = Version, - cipher_suite = SecParams#security_parameters.cipher_suite, - compression_method = - SecParams#security_parameters.compression_algorithm, - random = SecParams#security_parameters.server_random, - session_id = SessionId, - extensions = Extensions - }. - %%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, #connection_states{} | {inet:port_number(), #session{}, db_handle(), @@ -165,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 - <>) -> - update_handshake_history(Handshake, - <>); -update_handshake_history({Handshake0, _Prev}, Data) -> - {[Data|Handshake0], Handshake0}. - - get_tls_handshake_aux(Version, <>, Acc) -> Raw = <>, @@ -203,10 +155,6 @@ get_tls_handshake_aux(Version, < {lists:reverse(Acc), Data}. -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- - decode_handshake(_, ?HELLO_REQUEST, <<>>) -> #hello_request{}; 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. -- cgit v1.2.3 From f606903e2b714721b57d1d73a17d31b02f85ef07 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 19 Sep 2013 18:05:36 +0200 Subject: ssl: Refactor premaster secret handling --- lib/ssl/src/ssl_connection.erl | 406 ++++++++++++----------------------------- lib/ssl/src/ssl_handshake.erl | 137 ++++++++++++-- 2 files changed, 247 insertions(+), 296 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 581c80e52e..7e43af08e5 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -58,7 +58,7 @@ hello({common_client_hello, Type, ServerHelloExt, HashSign}, State#state{hashsign_algorithm = NegotiatedHashSign}, Connection); hello(timeout, State, _) -> - { next_state, hello, State, hibernate }; + {next_state, hello, State, hibernate}; hello(Msg, State, Connection) -> Connection:handle_unexpected_message(Msg, hello, State). @@ -168,20 +168,31 @@ certify(#certificate{} = Cert, Connection:handle_own_alert(Alert, Version, certify, State) end; -certify(#server_key_exchange{} = KeyExchangeMsg, +certify(#server_key_exchange{exchange_keys = Keys}, #state{role = client, negotiated_version = Version, - key_algorithm = Alg} = State0, Connection) + 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 -> - case handle_server_key(KeyExchangeMsg, State0) of - #state{} = State1 -> - {Record, State} = Connection:next_record(State1), - Connection:next_state(certify, certify, Record, State); - #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + + 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, @@ -200,38 +211,37 @@ 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 server_psk_master_secret(PSKIdentity, State0) of - #state{} = State -> - client_certify_and_key_exchange(State, Connection); + case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + 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 = SslOpts, - negotiated_version = Version, + 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 -> - 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 = <>, - State1 = State0#state{premaster_secret = PremasterSecret}, - State = master_from_premaster_secret(RealPMS, State1), - client_certify_and_key_exchange(State, Connection); + Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + RSAPremasterSecret = <>, + case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of #alert{} = Alert -> Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + 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 @@ -454,7 +464,8 @@ handle_peer_cert_key(client, _, 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}); + 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 @@ -525,102 +536,47 @@ server_certify_and_key_exchange(State0, Connection) -> request_client_cert(State2, Connection). certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{negotiated_version = Version, - connection_states = ConnectionStates0, - session = Session0, - private_key = Key} = State0, Connection) -> - PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), - 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(certify, cipher, Record, State); - #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) - end; + #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{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{} = Params, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0, + #state{diffie_hellman_params = #'DHParameter'{} = Params, + diffie_hellman_keys = {_, ServerDhPrivateKey}} = State, Connection) -> - case dh_master_secret(Params, ClientPublicDhKey, ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = Connection:next_record(State1), - Connection:next_state(certify, cipher, Record, State); - #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) - end; + 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{negotiated_version = Version, - diffie_hellman_keys = ECDHKey} = State0, Connection) -> - case ec_dh_master_secret(ECDHKey, #'ECPoint'{point = ClientPublicEcDhPoint}, State0) of - #state{} = State1 -> - {Record, State} = Connection:next_record(State1), - Connection:next_state(certify, cipher, Record, State); - #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_psk_identity{identity = ClientPSKIdentity}, - #state{negotiated_version = Version} = State0, Connection) -> - case server_psk_master_secret(ClientPSKIdentity, State0) of - #state{} = State1 -> - {Record, State} = Connection:next_record(State1), - Connection:next_state(certify, cipher, Record, State); - #alert{} = Alert -> - Connection: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, + #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) -> - case dhe_psk_master_secret(ClientPSKIdentity, P, G, ClientPublicDhKey, - ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = Connection:next_record(State1), - Connection:next_state(certify, cipher, Record, State); - #alert{} = Alert -> - Connection: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, Connection) -> - PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), - case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of - #state{} = State1 -> - {Record, State} = Connection:next_record(State1), - Connection:next_state(certify, cipher, Record, State); - #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) - end; + 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{srp_a = ClientPublicKey}, - #state{negotiated_version = Version, - srp_params = - #srp_user{prime = Prime, - verifier = Verifier} +certify_client_key_exchange(#client_srp_public{} = ClientKey, + #state{srp_params = Params, + srp_keys = Key } = State0, Connection) -> - case server_srp_master_secret(Verifier, Prime, ClientPublicKey, State0) of - #state{} = State1 -> - {Record, State} = Connection:next_record(State1), - Connection:next_state(certify, cipher, Record, State); - #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) - end. + 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 -> @@ -894,6 +850,22 @@ 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), @@ -940,79 +912,46 @@ save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbrev 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. - -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, - <>), - case ssl_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of - true -> - server_master_secret(Params, State); - false -> - ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) - end. - -make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - <>; -make_premaster_secret(_, _) -> - undefined. - -server_master_secret(#server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}, - State) -> - dh_master_secret(P, G, ServerPublicDhKey, undefined, State); +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); -server_master_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - State) -> +calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, + State, Connection) -> ECDHKeys = public_key:generate_key(ECCurve), - ec_dh_master_secret(ECDHKeys, #'ECPoint'{point = ECServerPubKey}, - State#state{diffie_hellman_keys = ECDHKeys}); + PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), + calculate_master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKeys}, Connection, certify, certify); -server_master_secret(#server_psk_params{ +calculate_secret(#server_psk_params{ hint = IdentityHint}, - State) -> + State0, Connection) -> %% 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) -> + {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} -> @@ -1024,80 +963,6 @@ master_from_premaster_secret(PremasterSecret, 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 = <>, - 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 = <>, - 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 = <>, - 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 = @@ -1135,35 +1000,6 @@ handle_srp_identity(Username, {Fun, UserState}) -> 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, Connection) -> diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index ad80c5ce7b..d4dd886aab 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -39,7 +39,7 @@ %% 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, - init_handshake_history/0, update_handshake_history/2 + init_handshake_history/0, update_handshake_history/2, verify_server_key/5 ]). %% Encode/Decode @@ -62,7 +62,7 @@ %% MISC -export([select_version/3, prf/5, select_hashsign/2, select_cert_hashsign/3, - decrypt_premaster_secret/2]). + premaster_secret/2, premaster_secret/3, premaster_secret/4]). %%==================================================================== %% Internal application API @@ -315,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, + <>), + verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo). + %%-------------------------------------------------------------------- -spec certificate_verify(binary(), public_key_info(), tls_version(), term(), binary(), tls_handshake_history()) -> valid | #alert{}. @@ -457,14 +473,83 @@ update_handshake_history(Handshake, % special-case SSL2 client hello update_handshake_history({Handshake0, _Prev}, Data) -> {[Data|Handshake0], Handshake0}. -%%-------------------------------------------------------------------- --spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). +%% %%-------------------------------------------------------------------- +%% -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; -%% -%% Description: Public key decryption using the private key. -%%-------------------------------------------------------------------- -decrypt_premaster_secret(Secret, RSAPrivateKey) -> - try public_key:decrypt_private(Secret, RSAPrivateKey, +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 _:_ -> @@ -516,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 @@ -535,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}; @@ -1749,3 +1836,31 @@ advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 -> advertised_hash_signs(_) -> undefined. +psk_secret(PSKIdentity, PSKLookup) -> + case handle_psk_identity(PSKIdentity, PSKLookup) of + {ok, PSK} when is_binary(PSK) -> + Len = erlang:byte_size(PSK), + <>; + #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), + <>; + #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). -- cgit v1.2.3 From 95db85ba3581b9b8722239fb1accc8a4a2d6c2e5 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Mon, 4 Nov 2013 16:41:03 +0100 Subject: ssl, public_key: Dialyzer fixes --- lib/public_key/src/public_key.erl | 2 +- lib/ssl/src/dtls_handshake.erl | 3 +- lib/ssl/src/dtls_record.erl | 6 ++- lib/ssl/src/dtls_v1.erl | 2 +- lib/ssl/src/ssl_connection.erl | 6 ++- lib/ssl/src/ssl_connection.hrl | 84 +++++++++++++++++++++------------------ lib/ssl/src/ssl_handshake.erl | 13 +++--- lib/ssl/src/ssl_srp.hrl | 7 +++- lib/ssl/src/tls_connection.hrl | 9 +++-- 9 files changed, 73 insertions(+), 59 deletions(-) (limited to 'lib') diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index a4b6b8ad15..ceecbcc7f2 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -333,7 +333,7 @@ encrypt_private(PlainText, crypto:private_encrypt(rsa, PlainText, format_rsa_private_key(Key), Padding). %%-------------------------------------------------------------------- --spec generate_key(#'DHParameter'{} | {namedCurve, Name ::atom()} | +-spec generate_key(#'DHParameter'{} | {namedCurve, Name ::oid()} | #'ECParameters'{}) -> {Public::binary(), Private::binary()} | #'ECPrivateKey'{}. %% Description: Generates a new keypair diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 6a54c5a305..ec7f21bd35 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -110,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 @@ -190,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, diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 7959c4d860..b0a7976864 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -296,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. @@ -337,7 +338,8 @@ 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), - mac_hash(Version, MacAlg, MacSecret, (Epoch bsl 48) + SeqNo, Type, + NewSeq = (Epoch bsl 48) + SeqNo, + mac_hash(Version, MacAlg, MacSecret, NewSeq, Type, Length, Fragment). mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl index c12e12e424..6e41641483 100644 --- a/lib/ssl/src/dtls_v1.erl +++ b/lib/ssl/src/dtls_v1.erl @@ -28,7 +28,7 @@ suites(Minor) -> tls_v1:suites(corresponding_minor_tls_version(Minor)). mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, + tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, corresponding_tls_version(Version), Length, Fragment). ecc_curves({_Major, Minor}) -> diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 7e43af08e5..2f5890ed31 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -917,13 +917,15 @@ calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, dh_y = ServerPubli 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_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_master_secret(PremasterSecret, + State#state{diffie_hellman_keys = ECDHKeys}, Connection, certify, certify); calculate_secret(#server_psk_params{ hint = IdentityHint}, diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 92134dfeb3..a444f2ae03 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -26,50 +26,56 @@ -ifndef(ssl_connection). -define(ssl_connection, true). +-include("ssl_internal.hrl"). +-include("ssl_record.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_srp.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 - 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 - protocol_buffers, - tls_handshake_history, % tls_handshake_history() - 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 + role :: client | server, + user_application :: {Monitor::reference(), User::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() | 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, % PKIX: {Algorithm, PublicKey, PublicKeyParams} - private_key, % PKIX: #'RSAPrivateKey'{} + 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, 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 ::boolean(), - allow_renegotiate = true ::boolean(), - expecting_next_protocol_negotiation = false :: boolean(), - next_protocol = undefined :: undefined | binary(), + 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} }). diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index d4dd886aab..bf091b4600 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -1045,13 +1045,12 @@ select_session(SuggestedSessionId, CipherSuites, Compressions, Port, #session{ec {resumed, Resumed} end. -supported_ecc(Version) -> - case tls_v1:ecc_curves(Version) of - [] -> - undefined; - Curves -> - #elliptic_curves{elliptic_curve_list = Curves} - end. +supported_ecc({Major, Minor} = Version) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) -> + Curves = tls_v1:ecc_curves(Version), + #elliptic_curves{elliptic_curve_list = Curves}; +supported_ecc(_) -> + undefined. + %%-------------certificate handling -------------------------------- certificate_types({KeyExchange, _, _, _}) 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/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl index f802f2afa9..2beecbb84d 100644 --- a/lib/ssl/src/tls_connection.hrl +++ b/lib/ssl/src/tls_connection.hrl @@ -26,12 +26,13 @@ -define(tls_connection, true). -include("ssl_connection.hrl"). +-include("tls_record.hrl"). -record(protocol_buffers, { - tls_packets = [] :: [binary()], % 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()] + 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). -- cgit v1.2.3 From c9a45539359d71aa0c68c9ca45b8015dd0e1b9cd Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 15 Nov 2013 17:11:48 +0100 Subject: ssl: Refactor API New design : ssl - Main tls - Reflect tls specific semantics dtls - Reflect dtls specific semantics --- lib/ssl/src/Makefile | 2 +- lib/ssl/src/dtls.erl | 79 ++- lib/ssl/src/ssl.erl | 1017 ++++++++++++++++++++++++++++++++++---- lib/ssl/src/ssl_api.hrl | 66 +++ lib/ssl/src/ssl_handshake.erl | 2 +- lib/ssl/src/ssl_internal.hrl | 58 ++- lib/ssl/src/ssl_socket.erl | 7 +- lib/ssl/src/tls.erl | 1014 ++----------------------------------- lib/ssl/src/tls_connection.erl | 13 +- lib/ssl/test/Makefile | 1 + lib/ssl/test/ssl_basic_SUITE.erl | 1 + 11 files changed, 1133 insertions(+), 1127 deletions(-) create mode 100644 lib/ssl/src/ssl_api.hrl (limited to 'lib') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index bf9ba1a1cc..8f0b01d62f 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -76,7 +76,7 @@ MODULES= \ INTERNAL_HRL_FILES = \ 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_internal.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= \ 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/ssl.erl b/lib/ssl/src/ssl.erl index dc6898d001..067c31d9e8 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). - --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_internal.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%% Application handling +-export([start/0, start/1, stop/0, clear_pem_cache/0]). + +%% 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,245 @@ %% 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}} -> + ConnectionCb:connect(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), + #config{transport_info = {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. +%%-------------------------------------------------------------------- +-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, 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. -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_accept(#sslsocket{fd = {_,_, ConnetionCb}} = Socket, Timeout) -> + ConnetionCb: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), + 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), + tls_connection:ssl_accept(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, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> + ConnetionCb: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, fd = {_,_, ConnetionCb}}, Data) when is_pid(Pid) -> + ConnetionCb: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, fd = {_,_, ConnetionCb}}, Length, Timeout) when is_pid(Pid) -> + ConnetionCb: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, + fd = {_,_, ConnetionCb}}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> + ConnetionCb: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). -peername(Socket) -> - tls:peername(Socket). +%%-------------------------------------------------------------------- +-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, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> + ConnetionCb:info(Pid); +connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> + {error, enotconn}. -peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> - case tls_connection:peer_certificate(Pid) of +%%-------------------------------------------------------------------- +-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, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> + case ConnetionCb:peer_certificate(Pid) of {ok, undefined} -> {error, no_peercert}; Result -> @@ -152,20 +307,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}. -negotiated_next_protocol(#sslsocket{pid = Pid}) -> - tls_connection:negotiated_next_protocol(Pid). +%%-------------------------------------------------------------------- +-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, fd = {_,_, ConnetionCb}}) -> + ConnetionCb: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 +341,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 +349,674 @@ 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, fd = {_,_, ConnetionCb}}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> + ConnetionCb: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, fd = {_,_, ConnetionCb}}, Options0) when is_pid(Pid), is_list(Options0) -> + try proplists:expand([{binary, [{mode, binary}]}, + {list, [{mode, list}]}], Options0) of + Options -> + ConnetionCb: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, fd = {_,_, ConnetionCb}}, How) -> + ConnetionCb:shutdown(Pid, How). -session_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:session_info(Pid); +%%-------------------------------------------------------------------- +-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, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> + ConnetionCb: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, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> + ConnetionCb: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, fd = {_,_,ConnetionCb}}, + Secret, Label, Seed, WantedLength) when is_pid(Pid) -> + ConnetionCb: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(). + +%%--------------------------------------------------------------- +-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. -format_error(Error) -> - tls:format_error(Error). +%%-------------------------------------------------------------------- +-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} -> + ConnetionCb: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 = [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 = case proplists:get_value(protocol, Opts, tls) of + tls -> + tls_connection; + dtls -> + dtls_connection + end, + {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. 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_handshake.erl b/lib/ssl/src/ssl_handshake.erl index bf091b4600..da72ffc043 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -1049,7 +1049,7 @@ supported_ecc({Major, Minor} = Version) when ((Major == 3) and (Minor >= 1)) ore Curves = tls_v1:ecc_curves(Version), #elliptic_curves{elliptic_curve_list = Curves}; supported_ecc(_) -> - undefined. + #elliptic_curves{elliptic_curve_list = []}. %%-------------certificate handling -------------------------------- diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index a582b8c290..8e5662db31 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 :: string(), + cert :: der_encoded(), + keyfile :: string(), + key :: der_encoded(), + password :: string(), + cacerts :: [der_encoded()], + cacertfile :: string(), + dh :: der_encoded(), + dhfile :: string(), user_lookup_fun, % server option, fun to lookup the user - psk_identity, % binary + psk_identity :: binary(), srp_identity, % client option {User, Password} ciphers, % %% Local policy for the server if it want's to reuse the session @@ -103,22 +101,30 @@ reuse_session, %% If false sessions will never be reused, if true they %% will be reused if possible. - reuse_sessions, % boolean() + reuse_sessions :: boolean(), renegotiate_at, secure_renegotiate, debug, - hibernate_after,% undefined if not hibernating, - % or number of ms of inactivity - % after which ssl_connection will - % go into hibernation + %% undefined if not hibernating, or number of ms of + %% inactivity after which ssl_connection will go into + %% hibernation + hibernate_after :: boolean(), %% This option should only be set to true by inet_tls_dist - erl_dist = false, + erl_dist = false :: boolean(), next_protocols_advertised = undefined, %% [binary()], next_protocol_selector = undefined, %% fun([binary()]) -> binary()) - log_alert, + log_alert :: boolean(), server_name_indication = undefined }). +-record(config, {ssl, %% SSL parameters + inet_user, %% User set inet options + emulated, %% #socket_option{} emulated + inet_ssl, %% inet options for internal ssl socket + transport_info, %% Callback info + connection_cb + }). + -record(socket_options, { mode = list, diff --git a/lib/ssl/src/ssl_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/tls.erl b/lib/ssl/src/tls.erl index f1747dc69e..3e7b2db9c2 100644 --- a/lib/ssl/src/tls.erl +++ b/lib/ssl/src/tls.erl @@ -19,98 +19,15 @@ %% -%%% Purpose : Main API module for SSL. +%%% Purpose : Reflect TLS specific API options (fairly simple wrapper at the moment) -module(tls). --export([start/0, start/1, stop/0, transport_accept/1, - transport_accept/2, ssl_accept/1, ssl_accept/2, ssl_accept/3, - cipher_suites/0, cipher_suites/1, suite_definition/1, - close/1, shutdown/2, - connect/3, connect/2, connect/4, connection_info/1, - controlling_process/2, listen/2, peername/1, peercert/1, - recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1, - versions/0, session_info/1, format_error/1, - renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1, negotiated_next_protocol/1]). - +-include("ssl_api.hrl"). -include("ssl_internal.hrl"). --include("ssl_record.hrl"). --include("ssl_cipher.hrl"). --include("ssl_handshake.hrl"). --include("ssl_srp.hrl"). - --include_lib("public_key/include/public_key.hrl"). - -%% Visible in API --export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0, - erl_cipher_suite/0, %% From ssl_cipher.hrl - tls_atom_version/0, %% From ssl_internal.hrl - prf_random/0, sslsocket/0]). - --record(config, {ssl, %% SSL parameters - inet_user, %% User set inet options - emulated, %% #socket_option{} emulated - inet_ssl, %% inet options for internal ssl socket - cb %% Callback info - }). - --type sslsocket() :: #sslsocket{}. --type connect_option() :: socket_connect_option() | ssl_option() | transport_option(). --type socket_connect_option() :: gen_tcp:connect_option(). --type listen_option() :: socket_listen_option() | ssl_option() | transport_option(). --type socket_listen_option() :: gen_tcp:listen_option(). --type ssl_option() :: {verify, verify_type()} | - {verify_fun, {fun(), InitialUserState::term()}} | - {fail_if_no_peer_cert, boolean()} | {depth, integer()} | - {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} | - {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} | - {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} | - {user_lookup_fun, {fun(), InitialUserState::term()}} | - {psk_identity, string()} | - {srp_identity, {string(), string()}} | - {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | - {reuse_session, fun()} | {hibernate_after, integer()|undefined} | - {next_protocols_advertised, list(binary())} | - {client_preferred_next_protocols, binary(), client | server, list(binary())}. - --type verify_type() :: verify_none | verify_peer. --type path() :: string(). --type ciphers() :: [erl_cipher_suite()] | - string(). % (according to old API) --type ssl_imp() :: new | old. - --type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(), - ClosedTag::atom(), ErrTag::atom()}}. --type prf_random() :: client_random | server_random. - -%%-------------------------------------------------------------------- --spec start() -> ok | {error, reason()}. --spec start(permanent | transient | temporary) -> ok | {error, reason()}. -%% -%% Description: Utility function that starts the ssl, -%% crypto and public_key applications. Default type -%% is temporary. see application(3) -%%-------------------------------------------------------------------- -start() -> - application:start(crypto), - application:start(asn1), - application:start(public_key), - application:start(ssl). - -start(Type) -> - application:start(crypto, Type), - application:start(asn1), - application:start(public_key, Type), - application:start(ssl, Type). - -%%-------------------------------------------------------------------- --spec stop() -> ok. -%% -%% Description: Stops the ssl application. -%%-------------------------------------------------------------------- -stop() -> - application:stop(ssl). +-export([connect/2, connect/3, listen/2, accept/1, accept/2, + handshake/1, handshake/2, handshake/3]). %%-------------------------------------------------------------------- -spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | @@ -122,44 +39,19 @@ stop() -> {ok, #sslsocket{}} | {error, reason()}. %% -%% Description: Connect to an ssl server. +%% Description: Connect to an TLS server. %%-------------------------------------------------------------------- -connect(Socket, SslOptions) when is_port(Socket) -> - connect(Socket, SslOptions, infinity). - -connect(Socket, SslOptions0, Timeout) when is_port(Socket) -> - {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0, - {gen_tcp, tcp, tcp_closed, tcp_error}), - EmulatedOptions = emulated_options(), - {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), - try handle_options(SslOptions0 ++ SocketValues, client) of - {ok, #config{cb = CbInfo, ssl = SslOptions, emulated = EmOpts}} -> - - ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()), - case ssl_socket:peername(Transport, Socket) of - {ok, {Address, Port}} -> - tls_connection:connect(Address, Port, Socket, - {SslOptions, EmOpts}, - self(), CbInfo, Timeout); - {error, Error} -> - {error, Error} - end - catch - _:{error, Reason} -> - {error, Reason} - end; +connect(Socket, Options) when is_port(Socket) -> + connect(Socket, Options, infinity). +connect(Socket, SslOptions, Timeout) when is_port(Socket) -> + TLSOpts = [{protocol, tls} | SslOptions], + ssl:connect(Socket, TLSOpts, Timeout); connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). - connect(Host, Port, Options, Timeout) -> - try handle_options(Options, client) of - {ok, Config} -> - do_connect(Host,Port,Config,Timeout) - catch - throw:Error -> - Error - end. + TLSOpts = [{protocol, tls} | Options], + ssl:connect(Host, Port, TLSOpts, Timeout). %%-------------------------------------------------------------------- -spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}. @@ -167,884 +59,44 @@ connect(Host, Port, Options, Timeout) -> %% %% Description: Creates an ssl listen socket. %%-------------------------------------------------------------------- -listen(_Port, []) -> - {error, nooptions}; -listen(Port, Options0) -> - try - {ok, Config} = handle_options(Options0, server), - #config{cb = {Transport, _, _, _}, inet_user = Options} = Config, - case Transport:listen(Port, Options) of - {ok, ListenSocket} -> - {ok, #sslsocket{pid = {ListenSocket, Config}}}; - Err = {error, _} -> - Err - end - catch - Error = {error, _} -> - Error - end. +listen(Port, Options) -> + TLSOpts = [{protocol, tls} | Options], + ssl:listen(Port, TLSOpts). + %%-------------------------------------------------------------------- --spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} | +-spec accept(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}. --spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | +-spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs transport accept on an ssl listen socket %%-------------------------------------------------------------------- -transport_accept(ListenSocket) -> - transport_accept(ListenSocket, infinity). - -transport_accept(#sslsocket{pid = {ListenSocket, #config{cb = CbInfo, ssl = SslOpts}}}, Timeout) -> - - %% The setopt could have been invoked on the listen socket - %% and options should be inherited. - EmOptions = emulated_options(), - {Transport,_,_, _} = CbInfo, - {ok, SocketValues} = ssl_socket:getopts(Transport, ListenSocket, EmOptions), - ok = ssl_socket:setopts(Transport, ListenSocket, internal_inet_values()), - case Transport:accept(ListenSocket, Timeout) of - {ok, Socket} -> - ok = ssl_socket:setopts(Transport, ListenSocket, SocketValues), - {ok, Port} = ssl_socket:port(Transport, Socket), - ConnArgs = [server, "localhost", Port, Socket, - {SslOpts, socket_options(SocketValues)}, self(), CbInfo], - case ssl_connection_sup:start_child(ConnArgs) of - {ok, Pid} -> - tls_connection:socket_control(Socket, Pid, Transport); - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end. +accept(ListenSocket) -> + accept(ListenSocket, infinity). +accept(Socket, Timeout) -> + ssl:transport_accept(Socket, Timeout). %%-------------------------------------------------------------------- --spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}. --spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option() +-spec handshake(#sslsocket{}) -> ok | {error, reason()}. +-spec handshake(#sslsocket{} | port(), timeout()| [ssl_option() | transport_option()]) -> ok | {ok, #sslsocket{}} | {error, reason()}. --spec ssl_accept(port(), [ssl_option()| transport_option()], timeout()) -> +-spec handshake(port(), [ssl_option()| transport_option()], timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- -ssl_accept(ListenSocket) -> - ssl_accept(ListenSocket, infinity). - -ssl_accept(#sslsocket{} = Socket, Timeout) -> - tls_connection:handshake(Socket, Timeout); - -ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> - ssl_accept(ListenSocket, SslOptions, infinity). - -ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> - {Transport,_,_,_} = - proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}), - EmulatedOptions = emulated_options(), - {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), - try handle_options(SslOptions ++ SocketValues, server) of - {ok, #config{cb = CbInfo, ssl = SslOpts, emulated = EmOpts}} -> - ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()), - {ok, Port} = ssl_socket:port(Transport, Socket), - tls_connection:ssl_accept(Port, Socket, - {SslOpts, EmOpts}, - self(), CbInfo, Timeout) - catch - Error = {error, _Reason} -> Error - end. - -%%-------------------------------------------------------------------- --spec close(#sslsocket{}) -> term(). -%% -%% Description: Close an ssl connection -%%-------------------------------------------------------------------- -close(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:close(Pid); -close(#sslsocket{pid = {ListenSocket, #config{cb={Transport,_, _, _}}}}) -> - Transport:close(ListenSocket). - -%%-------------------------------------------------------------------- --spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}. -%% -%% Description: Sends data over the ssl connection -%%-------------------------------------------------------------------- -send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) -> - tls_connection:send(Pid, Data); -send(#sslsocket{pid = {ListenSocket, #config{cb={Transport, _, _, _}}}}, Data) -> - Transport:send(ListenSocket, Data). %% {error,enotconn} - -%%-------------------------------------------------------------------- --spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}. --spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}. -%% -%% Description: Receives data when active = false -%%-------------------------------------------------------------------- -recv(Socket, Length) -> - recv(Socket, Length, infinity). -recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid) -> - tls_connection:recv(Pid, Length, Timeout); -recv(#sslsocket{pid = {Listen, - #config{cb={Transport, _, _, _}}}}, _,_) when is_port(Listen)-> - Transport:recv(Listen, 0). %% {error,enotconn} - -%%-------------------------------------------------------------------- --spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}. -%% -%% Description: Changes process that receives the messages when active = true -%% or once. -%%-------------------------------------------------------------------- -controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> - tls_connection:new_user(Pid, NewOwner); -controlling_process(#sslsocket{pid = {Listen, - #config{cb={Transport, _, _, _}}}}, - NewOwner) when is_port(Listen), - is_pid(NewOwner) -> - Transport:controlling_process(Listen, NewOwner). - -%%-------------------------------------------------------------------- --spec connection_info(#sslsocket{}) -> {ok, {tls_atom_version(), erl_cipher_suite()}} | - {error, reason()}. -%% -%% Description: Returns ssl protocol and cipher used for the connection -%%-------------------------------------------------------------------- -connection_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:info(Pid); -connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. -%% -%% Description: same as inet:peername/1. -%%-------------------------------------------------------------------- -peername(#sslsocket{pid = Pid, fd = {Transport, Socket}}) when is_pid(Pid)-> - ssl_socket:peername(Transport, Socket); -peername(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}) -> - ssl_socket:peername(Transport, ListenSocket). %% Will return {error, enotconn} - -%%-------------------------------------------------------------------- --spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}. -%% -%% Description: Returns the peercert. -%%-------------------------------------------------------------------- -peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> - case tls_connection:peer_certificate(Pid) of - {ok, undefined} -> - {error, no_peercert}; - Result -> - Result - end; -peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec suite_definition(cipher_suite()) -> erl_cipher_suite(). -%% -%% Description: Return erlang cipher suite definition. -%%-------------------------------------------------------------------- -suite_definition(S) -> - {KeyExchange, Cipher, Hash, _} = ssl_cipher:suite_definition(S), - {KeyExchange, Cipher, Hash}. - -%%-------------------------------------------------------------------- --spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the next protocol that has been negotiated. If no -%% protocol has been negotiated will return {error, next_protocol_not_negotiated} -%%-------------------------------------------------------------------- -negotiated_next_protocol(#sslsocket{pid = Pid}) -> - tls_connection:negotiated_next_protocol(Pid). - --spec cipher_suites() -> [erl_cipher_suite()]. --spec cipher_suites(erlang | openssl | all) -> [erl_cipher_suite()] | [string()]. - -%% Description: Returns all supported cipher suites. -%%-------------------------------------------------------------------- -cipher_suites() -> - cipher_suites(erlang). - -cipher_suites(erlang) -> - Version = tls_record:highest_protocol_version([]), - [suite_definition(S) || S <- ssl_cipher:suites(Version)]; - -cipher_suites(openssl) -> - Version = tls_record:highest_protocol_version([]), - [ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)]; -cipher_suites(all) -> - Version = tls_record:highest_protocol_version([]), - Supported = ssl_cipher:suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), - [suite_definition(S) || S <- Supported]. - -%%-------------------------------------------------------------------- --spec getopts(#sslsocket{}, [gen_tcp:option_name()]) -> - {ok, [gen_tcp:option()]} | {error, reason()}. -%% -%% Description: Gets options -%%-------------------------------------------------------------------- -getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> - tls_connection:get_opts(Pid, OptionTags); -getopts(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}, - OptionTags) when is_list(OptionTags) -> - try ssl_socket:getopts(Transport, ListenSocket, OptionTags) of - {ok, _} = Result -> - Result; - {error, InetError} -> - {error, {options, {socket_options, OptionTags, InetError}}} - catch - _:_ -> - {error, {options, {socket_options, OptionTags}}} - end; -getopts(#sslsocket{}, OptionTags) -> - {error, {options, {socket_options, OptionTags}}}. - -%%-------------------------------------------------------------------- --spec setopts(#sslsocket{}, [gen_tcp:option()]) -> ok | {error, reason()}. -%% -%% Description: Sets options -%%-------------------------------------------------------------------- -setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> - try proplists:expand([{binary, [{mode, binary}]}, - {list, [{mode, list}]}], Options0) of - Options -> - tls_connection:set_opts(Pid, Options) - catch - _:_ -> - {error, {options, {not_a_proplist, Options0}}} - end; -setopts(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}, Options) when is_list(Options) -> - try ssl_socket:setopts(Transport, ListenSocket, Options) of - ok -> - ok; - {error, InetError} -> - {error, {options, {socket_options, Options, InetError}}} - catch - _:Error -> - {error, {options, {socket_options, Options, Error}}} - end; -setopts(#sslsocket{}, Options) -> - {error, {options,{not_a_proplist, Options}}}. +handshake(ListenSocket) -> + handshake(ListenSocket, infinity). -%%--------------------------------------------------------------- --spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}. -%% -%% Description: Same as gen_tcp:shutdown/2 -%%-------------------------------------------------------------------- -shutdown(#sslsocket{pid = {Listen, #config{cb={Transport,_, _, _}}}}, - How) when is_port(Listen) -> - Transport:shutdown(Listen, How); -shutdown(#sslsocket{pid = Pid}, How) -> - tls_connection:shutdown(Pid, How). - -%%-------------------------------------------------------------------- --spec sockname(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. -%% -%% Description: Same as inet:sockname/1 -%%-------------------------------------------------------------------- -sockname(#sslsocket{pid = {Listen, #config{cb={Transport,_, _, _}}}}) when is_port(Listen) -> - ssl_socket:sockname(Transport, Listen); - -sockname(#sslsocket{pid = Pid, fd = {Transport, Socket}}) when is_pid(Pid) -> - ssl_socket:sockname(Transport, Socket). - -%%--------------------------------------------------------------- --spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns list of session info currently [{session_id, session_id(), -%% {cipher_suite, cipher_suite()}] -%%-------------------------------------------------------------------- -session_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:session_info(Pid); -session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> - {error, enotconn}. - -%%--------------------------------------------------------------- --spec versions() -> [{ssl_app, string()} | {supported, [tls_atom_version()]} | - {available, [tls_atom_version()]}]. -%% -%% Description: Returns a list of relevant versions. -%%-------------------------------------------------------------------- -versions() -> - Vsns = tls_record:supported_protocol_versions(), - SupportedVsns = [tls_record:protocol_version(Vsn) || Vsn <- Vsns], - AvailableVsns = ?ALL_SUPPORTED_VERSIONS, - [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}]. - - -%%--------------------------------------------------------------- --spec renegotiate(#sslsocket{}) -> ok | {error, reason()}. -%% -%% Description: Initiates a renegotiation. -%%-------------------------------------------------------------------- -renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:renegotiation(Pid); -renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec prf(#sslsocket{}, binary() | 'master_secret', binary(), - binary() | prf_random(), non_neg_integer()) -> - {ok, binary()} | {error, reason()}. -%% -%% Description: use a ssl sessions TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf(#sslsocket{pid = Pid}, - Secret, Label, Seed, WantedLength) when is_pid(Pid) -> - tls_connection:prf(Pid, Secret, Label, Seed, WantedLength); -prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec clear_pem_cache() -> ok. -%% -%% Description: Clear the PEM cache -%%-------------------------------------------------------------------- -clear_pem_cache() -> - ssl_manager:clear_pem_cache(). - -%%--------------------------------------------------------------- --spec format_error({error, term()}) -> list(). -%% -%% Description: Creates error string. -%%-------------------------------------------------------------------- -format_error({error, Reason}) -> - format_error(Reason); -format_error(Reason) when is_list(Reason) -> - Reason; -format_error(closed) -> - "TLS connection is closed"; -format_error({tls_alert, Description}) -> - "TLS Alert: " ++ Description; -format_error({options,{FileType, File, Reason}}) when FileType == cacertfile; - FileType == certfile; - FileType == keyfile; - FileType == dhfile -> - Error = file_error_format(Reason), - file_desc(FileType) ++ File ++ ": " ++ Error; -format_error({options, {socket_options, Option, Error}}) -> - lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, format_error(Error)])); -format_error({options, {socket_options, Option}}) -> - lists:flatten(io_lib:format("Invalid socket option: ~p", [Option])); -format_error({options, Options}) -> - lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options])); - -format_error(Error) -> - case inet:format_error(Error) of - "unknown POSIX" ++ _ -> - unexpected_format(Error); - Other -> - Other - end. - -%%-------------------------------------------------------------------- --spec random_bytes(integer()) -> binary(). - -%% -%% Description: Generates cryptographically secure random sequence if possible -%% fallbacks on pseudo random function -%%-------------------------------------------------------------------- -random_bytes(N) -> - try crypto:strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - crypto:rand_bytes(N) - end. - -%%%-------------------------------------------------------------- -%%% Internal functions -%%%-------------------------------------------------------------------- -do_connect(Address, Port, - #config{cb=CbInfo, inet_user=UserOpts, ssl=SslOpts, - emulated=EmOpts,inet_ssl=SocketOpts}, - Timeout) -> - {Transport, _, _, _} = CbInfo, - try Transport:connect(Address, Port, SocketOpts, Timeout) of - {ok, Socket} -> - tls_connection:connect(Address, Port, Socket, {SslOpts,EmOpts}, - self(), CbInfo, Timeout); - {error, Reason} -> - {error, Reason} - catch - exit:{function_clause, _} -> - {error, {options, {cb_info, CbInfo}}}; - exit:badarg -> - {error, {options, {socket_options, UserOpts}}}; - exit:{badarg, _} -> - {error, {options, {socket_options, UserOpts}}} - end. - -handle_options(Opts0, _Role) -> - Opts = proplists:expand([{binary, [{mode, binary}]}, - {list, [{mode, list}]}], Opts0), - ReuseSessionFun = fun(_, _, _, _) -> true end, - - DefaultVerifyNoneFun = - {fun(_,{bad_cert, _}, UserState) -> - {valid, UserState}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun), - UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false), - UserVerifyFun = handle_option(verify_fun, Opts, undefined), - CaCerts = handle_option(cacerts, Opts, undefined), - - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} = - %% Handle 0, 1, 2 for backwards compatibility - case proplists:get_value(verify, Opts, verify_none) of - 0 -> - {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun}; - 1 -> - {verify_peer, false, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; - 2 -> - {verify_peer, true, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; - verify_none -> - {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun}; - verify_peer -> - {verify_peer, UserFailIfNoPeerCert, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; - Value -> - throw({error, {options, {verify, Value}}}) - end, - - CertFile = handle_option(certfile, Opts, <<>>), - - Versions = case handle_option(versions, Opts, []) of - [] -> - tls_record:supported_protocol_versions(); - Vsns -> - [tls_record:protocol_version(Vsn) || Vsn <- Vsns] - end, - - SSLOptions = #ssl_options{ - versions = Versions, - verify = validate_option(verify, Verify), - verify_fun = VerifyFun, - fail_if_no_peer_cert = FailIfNoPeerCert, - verify_client_once = handle_option(verify_client_once, Opts, false), - depth = handle_option(depth, Opts, 1), - cert = handle_option(cert, Opts, undefined), - certfile = CertFile, - key = handle_option(key, Opts, undefined), - keyfile = handle_option(keyfile, Opts, CertFile), - password = handle_option(password, Opts, ""), - cacerts = CaCerts, - cacertfile = handle_option(cacertfile, Opts, CaCertDefault), - dh = handle_option(dh, Opts, undefined), - dhfile = handle_option(dhfile, Opts, undefined), - user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined), - psk_identity = handle_option(psk_identity, Opts, undefined), - srp_identity = handle_option(srp_identity, Opts, undefined), - ciphers = handle_option(ciphers, Opts, []), - %% Server side option - reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), - reuse_sessions = handle_option(reuse_sessions, Opts, true), - secure_renegotiate = handle_option(secure_renegotiate, Opts, false), - renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), - hibernate_after = handle_option(hibernate_after, Opts, undefined), - erl_dist = handle_option(erl_dist, Opts, false), - next_protocols_advertised = - handle_option(next_protocols_advertised, Opts, undefined), - next_protocol_selector = - make_next_protocol_selector( - handle_option(client_preferred_next_protocols, Opts, undefined)), - log_alert = handle_option(log_alert, Opts, true), - server_name_indication = handle_option(server_name_indication, Opts, undefined) - }, - - CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), - SslOptions = [versions, verify, verify_fun, - fail_if_no_peer_cert, verify_client_once, - depth, cert, certfile, key, keyfile, - password, cacerts, cacertfile, dh, dhfile, - user_lookup_fun, psk_identity, srp_identity, ciphers, - reuse_session, reuse_sessions, ssl_imp, - cb_info, renegotiate_at, secure_renegotiate, hibernate_after, - erl_dist, next_protocols_advertised, - client_preferred_next_protocols, log_alert], +handshake(#sslsocket{} = Socket, Timeout) -> + ssl:ssl_accept(Socket, Timeout); - SockOpts = lists:foldl(fun(Key, PropList) -> - proplists:delete(Key, PropList) - end, Opts, SslOptions), - - {SSLsock, Emulated} = emulated_options(SockOpts), - {ok, #config{ssl=SSLOptions, emulated=Emulated, inet_ssl=SSLsock, - inet_user=SockOpts, cb=CbInfo}}. - -handle_option(OptionName, Opts, Default) -> - validate_option(OptionName, - proplists:get_value(OptionName, Opts, Default)). - - -validate_option(versions, Versions) -> - validate_versions(Versions, Versions); -validate_option(verify, Value) - when Value == verify_none; Value == verify_peer -> - Value; -validate_option(verify_fun, undefined) -> - undefined; -%% Backwards compatibility -validate_option(verify_fun, Fun) when is_function(Fun) -> - {fun(_,{bad_cert, _} = Reason, OldFun) -> - case OldFun([Reason]) of - true -> - {valid, OldFun}; - false -> - {fail, Reason} - end; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, Fun}; -validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) -> - Value; -validate_option(fail_if_no_peer_cert, Value) - when Value == true; Value == false -> - Value; -validate_option(verify_client_once, Value) - when Value == true; Value == false -> - Value; -validate_option(depth, Value) when is_integer(Value), - Value >= 0, Value =< 255-> - Value; -validate_option(cert, Value) when Value == undefined; - is_binary(Value) -> - Value; -validate_option(certfile, undefined = Value) -> - Value; -validate_option(certfile, Value) when is_binary(Value) -> - Value; -validate_option(certfile, Value) when is_list(Value) -> - list_to_binary(Value); - -validate_option(key, undefined) -> - undefined; -validate_option(key, {KeyType, Value}) when is_binary(Value), - KeyType == rsa; %% Backwards compatibility - KeyType == dsa; %% Backwards compatibility - KeyType == 'RSAPrivateKey'; - KeyType == 'DSAPrivateKey'; - KeyType == 'PrivateKeyInfo' -> - {KeyType, Value}; - -validate_option(keyfile, undefined) -> - <<>>; -validate_option(keyfile, Value) when is_binary(Value) -> - Value; -validate_option(keyfile, Value) when is_list(Value), Value =/= "" -> - list_to_binary(Value); -validate_option(password, Value) when is_list(Value) -> - Value; - -validate_option(cacerts, Value) when Value == undefined; - is_list(Value) -> - Value; -%% certfile must be present in some cases otherwhise it can be set -%% to the empty string. -validate_option(cacertfile, undefined) -> - <<>>; -validate_option(cacertfile, Value) when is_binary(Value) -> - Value; -validate_option(cacertfile, Value) when is_list(Value), Value =/= ""-> - list_to_binary(Value); -validate_option(dh, Value) when Value == undefined; - is_binary(Value) -> - Value; -validate_option(dhfile, undefined = Value) -> - Value; -validate_option(dhfile, Value) when is_binary(Value) -> - Value; -validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> - list_to_binary(Value); -validate_option(psk_identity, undefined) -> - undefined; -validate_option(psk_identity, Identity) - when is_list(Identity), Identity =/= "", length(Identity) =< 65535 -> - list_to_binary(Identity); -validate_option(user_lookup_fun, undefined) -> - undefined; -validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) -> - Value; -validate_option(srp_identity, undefined) -> - undefined; -validate_option(srp_identity, {Username, Password}) - when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 -> - {list_to_binary(Username), list_to_binary(Password)}; - -validate_option(ciphers, Value) when is_list(Value) -> - Version = tls_record:highest_protocol_version([]), - try cipher_suites(Version, Value) - catch - exit:_ -> - throw({error, {options, {ciphers, Value}}}); - error:_-> - throw({error, {options, {ciphers, Value}}}) - end; -validate_option(reuse_session, Value) when is_function(Value) -> - Value; -validate_option(reuse_sessions, Value) when Value == true; - Value == false -> - Value; - -validate_option(secure_renegotiate, Value) when Value == true; - Value == false -> - Value; -validate_option(renegotiate_at, Value) when is_integer(Value) -> - erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT); - -validate_option(hibernate_after, undefined) -> - undefined; -validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 -> - Value; -validate_option(erl_dist,Value) when Value == true; - Value == false -> - Value; -validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value) - when is_list(PreferredProtocols) -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), - {Precedence, PreferredProtocols, ?NO_PROTOCOL} - end; -validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value) - when is_list(PreferredProtocols), is_binary(Default), - byte_size(Default) > 0, byte_size(Default) < 256 -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), - Value - end; - -validate_option(client_preferred_next_protocols, undefined) -> - undefined; -validate_option(log_alert, Value) when Value == true; - Value == false -> - Value; -validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(next_protocols_advertised, Value), - Value - end; - -validate_option(next_protocols_advertised, undefined) -> - undefined; -validate_option(server_name_indication, Value) when is_list(Value) -> - Value; -validate_option(server_name_indication, disable) -> - disable; -validate_option(server_name_indication, undefined) -> - undefined; -validate_option(Opt, Value) -> - throw({error, {options, {Opt, Value}}}). - -validate_npn_ordering(client) -> - ok; -validate_npn_ordering(server) -> - ok; -validate_npn_ordering(Value) -> - throw({error, {options, {client_preferred_next_protocols, {invalid_precedence, Value}}}}). - -validate_binary_list(Opt, List) -> - lists:foreach( - fun(Bin) when is_binary(Bin), - byte_size(Bin) > 0, - byte_size(Bin) < 256 -> - ok; - (Bin) -> - throw({error, {options, {Opt, {invalid_protocol, Bin}}}}) - end, List). - -validate_versions([], Versions) -> - Versions; -validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; - Version == 'tlsv1.1'; - Version == tlsv1; - Version == sslv3 -> - validate_versions(Rest, Versions); -validate_versions([Ver| _], Versions) -> - throw({error, {options, {Ver, {versions, Versions}}}}). - -validate_inet_option(mode, Value) - when Value =/= list, Value =/= binary -> - throw({error, {options, {mode,Value}}}); -validate_inet_option(packet, Value) - when not (is_atom(Value) orelse is_integer(Value)) -> - throw({error, {options, {packet,Value}}}); -validate_inet_option(packet_size, Value) - when not is_integer(Value) -> - throw({error, {options, {packet_size,Value}}}); -validate_inet_option(header, Value) - when not is_integer(Value) -> - throw({error, {options, {header,Value}}}); -validate_inet_option(active, Value) - when Value =/= true, Value =/= false, Value =/= once -> - throw({error, {options, {active,Value}}}); -validate_inet_option(_, _) -> - ok. - -%% The option cacerts overrides cacertsfile -ca_cert_default(_,_, [_|_]) -> - undefined; -ca_cert_default(verify_none, _, _) -> - undefined; -ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) -> - undefined; -%% Server that wants to verify_peer and has no verify_fun must have -%% some trusted certs. -ca_cert_default(verify_peer, undefined, _) -> - "". - -emulated_options() -> - [mode, packet, active, header, packet_size]. - -internal_inet_values() -> - [{packet_size,0},{packet, 0},{header, 0},{active, false},{mode,binary}]. - -socket_options(InetValues) -> - #socket_options{ - mode = proplists:get_value(mode, InetValues, lists), - header = proplists:get_value(header, InetValues, 0), - active = proplists:get_value(active, InetValues, active), - packet = proplists:get_value(packet, InetValues, 0), - packet_size = proplists:get_value(packet_size, InetValues) - }. - -emulated_options(Opts) -> - emulated_options(Opts, internal_inet_values(), #socket_options{}). - -emulated_options([{mode,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(mode,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{mode=Opt}); -emulated_options([{header,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(header,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{header=Opt}); -emulated_options([{active,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(active,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{active=Opt}); -emulated_options([{packet,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(packet,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{packet=Opt}); -emulated_options([{packet_size,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(packet_size,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{packet_size=Opt}); -emulated_options([Opt|Opts], Inet, Emulated) -> - emulated_options(Opts, [Opt|Inet], Emulated); -emulated_options([], Inet,Emulated) -> - {Inet, Emulated}. - -cipher_suites(Version, []) -> - ssl_cipher:suites(Version); -cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility - Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0], - cipher_suites(Version, Ciphers); -cipher_suites(Version, [{_,_,_}| _] = Ciphers0) -> - Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0], - cipher_suites(Version, Ciphers); - -cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> - Supported0 = ssl_cipher:suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), - Supported = ssl_cipher:filter_suites(Supported0), - case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of - [] -> - Supported; - Ciphers -> - Ciphers - end; -cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> - %% Format: ["RC4-SHA","RC4-MD5"] - Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0], - cipher_suites(Version, Ciphers); -cipher_suites(Version, Ciphers0) -> - %% Format: "RC4-SHA:RC4-MD5" - Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")], - cipher_suites(Version, Ciphers). - -unexpected_format(Error) -> - lists:flatten(io_lib:format("Unexpected error: ~p", [Error])). - -file_error_format({error, Error})-> - case file:format_error(Error) of - "unknown POSIX error" -> - "decoding error"; - Str -> - Str - end; -file_error_format(_) -> - "decoding error". - -file_desc(cacertfile) -> - "Invalid CA certificate file "; -file_desc(certfile) -> - "Invalid certificate file "; -file_desc(keyfile) -> - "Invalid key file "; -file_desc(dhfile) -> - "Invalid DH params file ". - -detect(_Pred, []) -> - undefined; -detect(Pred, [H|T]) -> - case Pred(H) of - true -> - H; - _ -> - detect(Pred, T) - end. - -make_next_protocol_selector(undefined) -> - undefined; -make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) -> - fun(AdvertisedProtocols) -> - case detect(fun(PreferredProtocol) -> - lists:member(PreferredProtocol, AdvertisedProtocols) - end, AllProtocols) of - undefined -> - DefaultProtocol; - PreferredProtocol -> - PreferredProtocol - end - end; +handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> + handshake(ListenSocket, SslOptions, infinity). -make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) -> - fun(AdvertisedProtocols) -> - case detect(fun(PreferredProtocol) -> - lists:member(PreferredProtocol, AllProtocols) - end, - AdvertisedProtocols) of - undefined -> - DefaultProtocol; - PreferredProtocol -> - PreferredProtocol - end - end. +handshake(Socket, SslOptions, Timeout) when is_port(Socket) -> + ssl:ssl_accept(Socket, SslOptions, Timeout). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index a8885201f8..e4201504c0 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -33,7 +33,8 @@ -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"). @@ -130,7 +131,7 @@ handshake(#sslsocket{pid = Pid}, Timeout) -> socket_control(Socket, Pid, Transport) -> case Transport:controlling_process(Socket, Pid) of ok -> - {ok, ssl_socket:socket(Pid, Transport, Socket)}; + {ok, ssl_socket:socket(Pid, Transport, Socket, ?MODULE)}; {error, Reason} -> {error, Reason} end. @@ -1139,7 +1140,7 @@ 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)). @@ -1147,7 +1148,7 @@ 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); @@ -1571,10 +1572,10 @@ 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) -> 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..b4f71ba776 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"). -- cgit v1.2.3 From bae8fb577c0c3d2f5a3db31a34e584c8d7ccab6e Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Mon, 18 Nov 2013 10:34:04 +0100 Subject: ssl: Test case enhancement Make sure that test cases does not fail due to timing circumstances, use selective receive even if it means we can not use test library functions in some cases. TCP does not have delivery guarantee to application layer, so sometimes a error message {error, Msg} or {error, closed} can be acceptable. --- lib/ssl/test/ssl_basic_SUITE.erl | 5 ++++- lib/ssl/test/ssl_certificate_verify_SUITE.erl | 24 ++++++++++++++++++------ lib/ssl/test/ssl_dist_SUITE.erl | 2 +- lib/ssl/test/ssl_to_openssl_SUITE.erl | 5 ++++- 4 files changed, 27 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index b4f71ba776..7a854afc80 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -2490,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. 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_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, -- cgit v1.2.3 From c46f856dda536d92537461115112b2021c08b52a Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Mon, 18 Nov 2013 16:07:24 +0100 Subject: ssl: Dialyzer fixes --- lib/ssl/src/ssl_internal.hrl | 10 +++++----- lib/ssl/src/tls_connection.erl | 7 ------- 2 files changed, 5 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 8e5662db31..0186f9fca2 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -82,15 +82,15 @@ %% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError} validate_extensions_fun, depth :: integer(), - certfile :: string(), + certfile :: binary(), cert :: der_encoded(), - keyfile :: string(), - key :: der_encoded(), + keyfile :: binary(), + key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', der_encoded()}, password :: string(), cacerts :: [der_encoded()], - cacertfile :: string(), + cacertfile :: binary(), dh :: der_encoded(), - dhfile :: string(), + dhfile :: binary(), user_lookup_fun, % server option, fun to lookup the user psk_identity :: binary(), srp_identity, % client option {User, Password} diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index e4201504c0..c8380c109c 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -877,13 +877,6 @@ init_private_key(DbHandle, undefined, KeyFile, Password, _) -> 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)). -- cgit v1.2.3 From b84f16e07336eecfb6dd30a4ef3824de76525265 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 19 Nov 2013 10:04:47 +0100 Subject: ssl: API and supervisor --- lib/ssl/src/Makefile | 3 +- lib/ssl/src/dtls_connection_sup.erl | 60 +++++++++++++++++++++++++++++++++ lib/ssl/src/ssl.app.src | 9 ++--- lib/ssl/src/ssl.erl | 38 +++++++++++++-------- lib/ssl/src/ssl_connection_sup.erl | 66 ------------------------------------- lib/ssl/src/ssl_dist_sup.erl | 4 +-- lib/ssl/src/ssl_sup.erl | 36 ++++++++++---------- lib/ssl/src/tls_connection.erl | 8 ++--- lib/ssl/src/tls_connection_sup.erl | 66 +++++++++++++++++++++++++++++++++++++ 9 files changed, 181 insertions(+), 109 deletions(-) create mode 100644 lib/ssl/src/dtls_connection_sup.erl delete mode 100644 lib/ssl/src/ssl_connection_sup.erl create mode 100644 lib/ssl/src/tls_connection_sup.erl (limited to 'lib') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 8f0b01d62f..1089809a33 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -56,7 +56,8 @@ MODULES= \ tls_connection \ dtls_connection \ ssl_connection \ - ssl_connection_sup \ + tls_connection_sup \ + dtls_connection_sup \ tls_handshake \ dtls_handshake\ ssl_handshake\ 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/ssl.app.src b/lib/ssl/src/ssl.app.src index 1d47aa9374..f255909365 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -15,9 +15,9 @@ 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_connection, @@ -40,7 +40,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 067c31d9e8..6df2f89436 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -140,7 +140,8 @@ listen(_Port, []) -> listen(Port, Options0) -> try {ok, Config} = handle_options(Options0, server), - #config{transport_info = {Transport, _, _, _}, inet_user = Options} = Config, + 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}}}; @@ -163,7 +164,9 @@ transport_accept(ListenSocket) -> transport_accept(ListenSocket, infinity). transport_accept(#sslsocket{pid = {ListenSocket, - #config{transport_info = CbInfo, ssl = SslOpts}}}, Timeout) -> + #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(), @@ -176,9 +179,10 @@ transport_accept(#sslsocket{pid = {ListenSocket, {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 + ConnectionSup = connection_sup(ConnectionCb), + case ConnectionSup:start_child(ConnArgs) of {ok, Pid} -> - tls_connection:socket_control(Socket, Pid, Transport); + ConnectionCb:socket_control(Socket, Pid, Transport); {error, Reason} -> {error, Reason} end; @@ -211,13 +215,14 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> 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), - tls_connection:ssl_accept(Port, Socket, - {SslOpts, EmOpts}, - self(), CbInfo, Timeout) + ConnetionCb:ssl_accept(Port, Socket, + {SslOpts, EmOpts}, + self(), CbInfo, Timeout) catch Error = {error, _Reason} -> Error end. @@ -654,13 +659,8 @@ handle_options(Opts0, _Role) -> end, Opts, SslOptions), {SSLsock, Emulated} = emulated_options(SockOpts), + ConnetionCb = connection_cb(Opts), - ConnetionCb = case proplists:get_value(protocol, Opts, tls) of - tls -> - tls_connection; - dtls -> - dtls_connection - end, {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = SSLsock, inet_user = SockOpts, transport_info = CbInfo, connection_cb = ConnetionCb }}. @@ -1020,3 +1020,15 @@ make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) -> 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_connection_sup.erl b/lib/ssl/src/ssl_connection_sup.erl deleted file mode 100644 index fb1c6e11a6..0000000000 --- a/lib/ssl/src/ssl_connection_sup.erl +++ /dev/null @@ -1,66 +0,0 @@ -%% -%% %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: The top supervisor for the ftp hangs under inets_sup. -%%---------------------------------------------------------------------- --module(ssl_connection_sup). - --behaviour(supervisor). - -%% API --export([start_link/0, start_link_dist/0]). --export([start_child/1, start_child_dist/1]). - -%% Supervisor callback --export([init/1]). - -%%%========================================================================= -%%% API -%%%========================================================================= -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -start_link_dist() -> - supervisor:start_link({local, ssl_connection_sup_dist}, ?MODULE, []). - -start_child(Args) -> - supervisor:start_child(?MODULE, Args). - -start_child_dist(Args) -> - supervisor:start_child(ssl_connection_sup_dist, Args). - -%%%========================================================================= -%%% Supervisor callback -%%%========================================================================= -init(_O) -> - RestartStrategy = simple_one_for_one, - MaxR = 0, - MaxT = 3600, - - Name = undefined, % As simple_one_for_one is used. - StartFunc = {tls_connection, start_link, []}, - Restart = temporary, % E.g. should not be restarted - Shutdown = 4000, - Modules = [tls_connection], - Type = worker, - - ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, - {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. 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_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_connection.erl b/lib/ssl/src/tls_connection.erl index c8380c109c..37d4928531 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -49,7 +49,7 @@ register_session/4 ]). -%% Called by ssl_connection_sup +%% Called by tls_connection_sup -export([start_link/7]). %% gen_fsm callbacks @@ -263,7 +263,7 @@ send_change_cipher(Msg, #state{connection_states = ConnectionStates0, State0#state{connection_states = ConnectionStates}. %%==================================================================== -%% ssl_connection_sup API +%% tls_connection_sup API %%==================================================================== %%-------------------------------------------------------------------- @@ -777,7 +777,7 @@ 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, + {ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket, Opts, User, CbInfo]), {ok, SslSocket} = socket_control(Socket, Pid, CbModule), ok = handshake(SslSocket, Timeout), @@ -791,7 +791,7 @@ 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, + {ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket, Opts, User, CbInfo]), {ok, SslSocket} = socket_control(Socket, Pid, CbModule), ok = handshake(SslSocket, Timeout), diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl new file mode 100644 index 0000000000..6f0d8a7262 --- /dev/null +++ b/lib/ssl/src/tls_connection_sup.erl @@ -0,0 +1,66 @@ +%% +%% %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 for a SSL/TLS connection +%%---------------------------------------------------------------------- +-module(tls_connection_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0, start_link_dist/0]). +-export([start_child/1, start_child_dist/1]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +start_link_dist() -> + supervisor:start_link({local, ssl_connection_sup_dist}, ?MODULE, []). + +start_child(Args) -> + supervisor:start_child(?MODULE, Args). + +start_child_dist(Args) -> + supervisor:start_child(ssl_connection_sup_dist, Args). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 0, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {tls_connection, start_link, []}, + Restart = temporary, % E.g. should not be restarted + Shutdown = 4000, + Modules = [tls_connection], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. -- cgit v1.2.3 From 1b6cb772ce5d1d1b7ce280c22e6a8d2ceb8165ed Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 20 Nov 2013 15:54:47 +0100 Subject: ssl: Refactor connetion handling --- lib/ssl/src/Makefile | 1 + lib/ssl/src/dtls_connection.erl | 7 +- lib/ssl/src/ssl.app.src | 1 + lib/ssl/src/ssl.erl | 65 +- lib/ssl/src/ssl_alert.erl | 13 +- lib/ssl/src/ssl_config.erl | 156 ++++ lib/ssl/src/ssl_connection.erl | 753 ++++++++++++++++++- lib/ssl/src/ssl_connection.hrl | 2 + lib/ssl/src/tls_connection.erl | 1529 ++++++++++---------------------------- lib/ssl/test/ssl_basic_SUITE.erl | 2 +- 10 files changed, 1329 insertions(+), 1200 deletions(-) create mode 100644 lib/ssl/src/ssl_config.erl (limited to 'lib') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 1089809a33..131b615277 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -55,6 +55,7 @@ MODULES= \ ssl_srp_primes \ tls_connection \ dtls_connection \ + ssl_config \ ssl_connection \ tls_connection_sup \ dtls_connection_sup \ diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 0271c4490a..da2e076856 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -18,11 +18,12 @@ %% -module(dtls_connection). +%% Internal application API +%%==================================================================== +%% Internal application API +%%==================================================================== -%% %%==================================================================== -%% %% Internal application API -%% %%==================================================================== %% %%==================================================================== diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index f255909365..68ebc49e4a 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -20,6 +20,7 @@ dtls, %% DTLS specific ssl_session_cache_api, %% Both TLS/SSL and DTLS + ssl_config, ssl_connection, ssl_handshake, ssl_record, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 6df2f89436..cff842cb2f 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -106,7 +106,7 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket) -> ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()), case ssl_socket:peername(Transport, Socket) of {ok, {Address, Port}} -> - ConnectionCb:connect(Address, Port, Socket, + ssl_connection:connect(ConnectionCb, Address, Port, Socket, {SslOptions, EmOpts}, self(), CbInfo, Timeout); {error, Error} -> @@ -182,7 +182,7 @@ transport_accept(#sslsocket{pid = {ListenSocket, ConnectionSup = connection_sup(ConnectionCb), case ConnectionSup:start_child(ConnArgs) of {ok, Pid} -> - ConnectionCb:socket_control(Socket, Pid, Transport); + ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport); {error, Reason} -> {error, Reason} end; @@ -204,8 +204,8 @@ transport_accept(#sslsocket{pid = {ListenSocket, ssl_accept(ListenSocket) -> ssl_accept(ListenSocket, infinity). -ssl_accept(#sslsocket{fd = {_,_, ConnetionCb}} = Socket, Timeout) -> - ConnetionCb:handshake(Socket, Timeout); +ssl_accept(#sslsocket{} = Socket, Timeout) -> + ssl_connection:handshake(Socket, Timeout); ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> ssl_accept(ListenSocket, SslOptions, infinity). @@ -220,7 +220,7 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> {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), - ConnetionCb:ssl_accept(Port, Socket, + ssl_connection:ssl_accept(ConnetionCb, Port, Socket, {SslOpts, EmOpts}, self(), CbInfo, Timeout) catch @@ -232,8 +232,8 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> %% %% Description: Close an ssl connection %%-------------------------------------------------------------------- -close(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> - ConnetionCb:close(Pid); +close(#sslsocket{pid = Pid}) when is_pid(Pid) -> + ssl_connection:close(Pid); close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}) -> Transport:close(ListenSocket). @@ -242,8 +242,8 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _} %% %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- -send(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}, Data) when is_pid(Pid) -> - ConnetionCb:send(Pid, Data); +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} @@ -255,8 +255,8 @@ send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _} %%-------------------------------------------------------------------- recv(Socket, Length) -> recv(Socket, Length, infinity). -recv(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}, Length, Timeout) when is_pid(Pid) -> - ConnetionCb:recv(Pid, Length, Timeout); +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} @@ -267,9 +267,8 @@ recv(#sslsocket{pid = {Listen, %% Description: Changes process that receives the messages when active = true %% or once. %%-------------------------------------------------------------------- -controlling_process(#sslsocket{pid = Pid, - fd = {_,_, ConnetionCb}}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> - ConnetionCb:new_user(Pid, NewOwner); +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), @@ -282,8 +281,8 @@ controlling_process(#sslsocket{pid = {Listen, %% %% Description: Returns ssl protocol and cipher used for the connection %%-------------------------------------------------------------------- -connection_info(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> - ConnetionCb:info(Pid); +connection_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> + ssl_connection:info(Pid); connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> {error, enotconn}. @@ -302,8 +301,8 @@ peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_, %% %% Description: Returns the peercert. %%-------------------------------------------------------------------- -peercert(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> - case ConnetionCb:peer_certificate(Pid) of +peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> + case ssl_connection:peer_certificate(Pid) of {ok, undefined} -> {error, no_peercert}; Result -> @@ -327,8 +326,8 @@ suite_definition(S) -> %% 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, fd = {_,_, ConnetionCb}}) -> - ConnetionCb:negotiated_next_protocol(Pid). +negotiated_next_protocol(#sslsocket{pid = Pid}) -> + ssl_connection:negotiated_next_protocol(Pid). %%-------------------------------------------------------------------- -spec cipher_suites() -> [erl_cipher_suite()]. @@ -360,8 +359,8 @@ cipher_suites(all) -> %% %% Description: Gets options %%-------------------------------------------------------------------- -getopts(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> - ConnetionCb:get_opts(Pid, OptionTags); +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 @@ -381,11 +380,11 @@ getopts(#sslsocket{}, OptionTags) -> %% %% Description: Sets options %%-------------------------------------------------------------------- -setopts(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}, Options0) when is_pid(Pid), is_list(Options0) -> +setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> try proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Options0) of Options -> - ConnetionCb:set_opts(Pid, Options) + ssl_connection:set_opts(Pid, Options) catch _:_ -> {error, {options, {not_a_proplist, Options0}}} @@ -412,8 +411,8 @@ setopts(#sslsocket{}, Options) -> shutdown(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}}, How) when is_port(Listen) -> Transport:shutdown(Listen, How); -shutdown(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}, How) -> - ConnetionCb:shutdown(Pid, How). +shutdown(#sslsocket{pid = Pid}, How) -> + ssl_connection:shutdown(Pid, How). %%-------------------------------------------------------------------- -spec sockname(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. @@ -432,8 +431,8 @@ sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid) -> %% Description: Returns list of session info currently [{session_id, session_id(), %% {cipher_suite, cipher_suite()}] %%-------------------------------------------------------------------- -session_info(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> - ConnetionCb:session_info(Pid); +session_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> + ssl_connection:session_info(Pid); session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> {error, enotconn}. @@ -456,8 +455,8 @@ versions() -> %% %% Description: Initiates a renegotiation. %%-------------------------------------------------------------------- -renegotiate(#sslsocket{pid = Pid, fd = {_,_, ConnetionCb}}) when is_pid(Pid) -> - ConnetionCb:renegotiation(Pid); +renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) -> + ssl_connection:renegotiation(Pid); renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> {error, enotconn}. @@ -468,9 +467,9 @@ renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> %% %% Description: use a ssl sessions TLS PRF to generate key material %%-------------------------------------------------------------------- -prf(#sslsocket{pid = Pid, fd = {_,_,ConnetionCb}}, +prf(#sslsocket{pid = Pid}, Secret, Label, Seed, WantedLength) when is_pid(Pid) -> - ConnetionCb:prf(Pid, Secret, Label, Seed, WantedLength); + ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength); prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> {error, enotconn}. @@ -542,7 +541,7 @@ do_connect(Address, Port, {Transport, _, _, _} = CbInfo, try Transport:connect(Address, Port, SocketOpts, Timeout) of {ok, Socket} -> - ConnetionCb:connect(Address, Port, Socket, {SslOpts,EmOpts}, + ssl_connection:connect(ConnetionCb, Address, Port, Socket, {SslOpts,EmOpts}, self(), CbInfo, Timeout); {error, Reason} -> {error, Reason} 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_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 index 2f5890ed31..8dac0a3f12 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -25,6 +25,7 @@ -module(ssl_connection). +-include("ssl_api.hrl"). -include("ssl_connection.hrl"). -include("ssl_handshake.hrl"). -include("ssl_alert.hrl"). @@ -34,8 +35,233 @@ -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) -> @@ -86,7 +312,7 @@ abbreviated(#finished{verify_data = Data} = Finished, ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), Connection:next_state_connection(abbreviated, - Connection:ack_connection( + ack_connection( State#state{connection_states = ConnectionStates})); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, abbreviated, State) @@ -107,7 +333,7 @@ abbreviated(#finished{verify_data = Data} = Finished, finalize_handshake(State0#state{connection_states = ConnectionStates1}, abbreviated, Connection), Connection:next_state_connection(abbreviated, - Connection:ack_connection(State)); + ack_connection(State)); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, abbreviated, State0) end; @@ -349,7 +575,7 @@ cipher(#finished{verify_data = Data} = Finished, get_current_prf(ConnectionStates0, read), MasterSecret, Handshake0) of verified -> - Session = Connection:register_session(Role, Host, Port, Session0), + Session = register_session(Role, Host, Port, Session0), cipher_role(Role, Data, Session, State, Connection); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, cipher, State) @@ -378,6 +604,291 @@ connection(timeout, State, _) -> 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}; + +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 %%-------------------------------------------------------------------- @@ -446,6 +957,9 @@ 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) -> @@ -1007,7 +1521,7 @@ cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} Connection) -> ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0), Connection:next_state_connection(cipher, - Connection:ack_connection( + ack_connection( State#state{session = Session, connection_states = ConnectionStates})); @@ -1017,7 +1531,7 @@ cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0 State = finalize_handshake(State0#state{connection_states = ConnectionStates1, session = Session}, cipher, Connection), - Connection:next_state_connection(cipher, Connection:ack_connection(State#state{session = Session})). + Connection:next_state_connection(cipher, ack_connection(State#state{session = Session})). negotiated_hashsign(undefined, Algo, Version) -> default_hashsign(Version, Algo); @@ -1104,3 +1618,232 @@ 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), + <>; +make_premaster_secret(_, _) -> + undefined. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index a444f2ae03..27489ca325 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -30,12 +30,14 @@ -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 diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 37d4928531..9621182948 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -40,15 +40,26 @@ -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, - send_handshake/2, send_alert/2, send_change_cipher/2, next_record/1, next_state/4, - handle_unexpected_message/3, ack_connection/1, handle_own_alert/4, next_state_connection/2, - register_session/4 + +%% 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]). @@ -60,178 +71,34 @@ %%==================================================================== %% 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, ?MODULE)}; - {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). - -%%-------------------------------------------------------------------- --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}). - - send_handshake(Handshake, #state{negotiated_version = Version, socket = Socket, transport_cb = Transport, @@ -249,7 +116,7 @@ send_alert(Alert, #state{negotiated_version = Version, transport_cb = Transport, connection_states = ConnectionStates0} = State0) -> {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), + ssl_alert:encode(Alert, Version, ConnectionStates0), Transport:send(Socket, BinMsg), State0#state{connection_states = ConnectionStates}. @@ -281,7 +148,7 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), 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{ @@ -353,45 +220,18 @@ hello(Hello = #client_hello{client_version = ClientVersion, #alert{} = Alert -> handle_own_alert(Alert, ClientVersion, hello, State) end; -hello(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression} = Hello, - #state{session = #session{session_id = OldId}, - connection_states = ConnectionStates0, - role = client, +hello(Hello, + #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, + role = client, renegotiation = {Renegotiation, _}, - ssl_options = SslOptions} = State0) -> + ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State0); + handle_own_alert(Alert, ReqVersion, hello, State); {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 + ssl_connection:handle_session(Hello, + Version, NewId, ConnectionStates, NextProtocol, State) end; hello(Msg, State) -> @@ -451,195 +291,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 @@ -647,7 +300,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 @@ -678,45 +331,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 @@ -724,44 +340,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} @@ -773,170 +354,6 @@ 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} = tls_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} = tls_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; - -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. - - encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> Frag = tls_handshake:encode_handshake(Handshake, Version), Hist = ssl_handshake:update_handshake_history(Hist0, Frag), @@ -944,27 +361,11 @@ encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> ssl_record:encode_handshake(Frag, Version, ConnectionStates0), {Encoded, ConnectionStates, Hist}. -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). -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. - -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 -> <> - end. decode_alerts(Bin) -> decode_alerts(Bin, []). @@ -975,230 +376,42 @@ decode_alerts(<>, Acc) -> decode_alerts(<<>>, Acc) -> lists:reverse(Acc, []). -passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> - case Buffer of - <<>> -> - {Record, State} = next_record(State0), - next_state(StateName, StateName, Record, State); - _ -> - case read_application_data(<<>>, State0) of - Stop = {stop, _, _} -> - Stop; - {Record, State} -> - next_state(StateName, StateName, Record, State) - end - end. - -read_application_data(Data, #state{user_application = {_Mon, Pid}, - socket = Socket, - transport_cb = Transport, - socket_options = SOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - timer = Timer, - user_data_buffer = Buffer0} = State0) -> - Buffer1 = if - Buffer0 =:= <<>> -> Data; - Data =:= <<>> -> Buffer0; - true -> <> - end, - case get_data(SOpts, BytesToRead, Buffer1) of - {ok, ClientData, Buffer} -> % Send data - SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom), - cancel_timer(Timer), - State = State0#state{user_data_buffer = Buffer, - start_or_recv_from = undefined, - timer = undefined, - bytes_to_read = undefined, - socket_options = SocketOpt - }, - if - SocketOpt#socket_options.active =:= false; Buffer =:= <<>> -> - %% Passive mode, wait for active once or recv - %% Active and empty, get more data - next_record_if_active(State); - true -> %% We have more data - read_application_data(<<>>, State) - end; - {more, Buffer} -> % no reply, we need more data - next_record(State0#state{user_data_buffer = Buffer}); - {passive, Buffer} -> - next_record_if_active(State0#state{user_data_buffer = Buffer}); - {error,_Reason} -> %% Invalid packet in packet mode - deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom), - {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), +initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, + {CbModule, DataTag, CloseTag, ErrorTag}) -> + ConnectionStates = ssl_record:init_connection_states(Role), - 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) -> + SessionCacheCb = case application:get_env(ssl, session_cb) of + {ok, Cb} when is_atom(Cb) -> + Cb; + _ -> + ssl_session_cache + 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). - -is_time_to_renegotiate(N, M) when N < M-> - false; -is_time_to_renegotiate(_,_) -> - true. - -%% Picks ClientData -get_data(_, _, <<>>) -> - {more, <<>>}; -%% Recv timed out save buffer data until next recv -get_data(#socket_options{active=false}, undefined, Buffer) -> - {passive, Buffer}; -get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) - when Raw =:= raw; Raw =:= 0 -> %% Raw Mode - if - Active =/= false orelse BytesToRead =:= 0 -> - %% Active true or once, or passive mode recv(0) - {ok, Buffer, <<>>}; - byte_size(Buffer) >= BytesToRead -> - %% Passive Mode, recv(Bytes) - <> = Buffer, - {ok, Data, Rest}; - true -> - %% Passive Mode not enough data - {more, Buffer} - end; -get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> - PacketOpts = [{packet_size, Size}], - case decode_packet(Type, Buffer, PacketOpts) of - {more, _} -> - {more, Buffer}; - Decoded -> - Decoded - end. - -decode_packet({http, headers}, Buffer, PacketOpts) -> - decode_packet(httph, Buffer, PacketOpts); -decode_packet({http_bin, headers}, Buffer, PacketOpts) -> - decode_packet(httph_bin, Buffer, PacketOpts); -decode_packet(Type, Buffer, PacketOpts) -> - erlang:decode_packet(Type, Buffer, PacketOpts). - -%% Just like with gen_tcp sockets, an ssl socket that has been configured with -%% {packet, http} (or {packet, http_bin}) will automatically switch to expect -%% HTTP headers after it sees a HTTP Request or HTTP Response line. We -%% represent the current state as follows: -%% #socket_options.packet =:= http: Expect a HTTP Request/Response line -%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers -%% Note that if the user has explicitly configured the socket to expect -%% HTTP headers using the {packet, httph} option, we don't do any automatic -%% switching of states. -deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, - Data, Pid, From) -> - send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data)), - SO = case Data of - {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), - ((Type =:= http) or (Type =:= http_bin)) -> - SOpts#socket_options{packet={Type, headers}}; - http_eoh when tuple_size(Type) =:= 2 -> - % End of headers - expect another Request/Response line - {Type1, headers} = Type, - SOpts#socket_options{packet=Type1}; - _ -> - SOpts - end, - case Active of - once -> - SO#socket_options{active=false}; - _ -> - SO - end. - -format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, - header = Header}, Data) -> - {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, ?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)). - -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, ?MODULE), {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. - -do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode - header(N, Data); -do_format_reply(binary, _, _, Data) -> - Data; -do_format_reply(list, Packet, _, Data) - when Packet == http; Packet == {http, headers}; - Packet == http_bin; Packet == {http_bin, headers}; - Packet == httph; Packet == httph_bin -> - Data; -do_format_reply(list, _,_, Data) -> - binary_to_list(Data). - -header(0, <<>>) -> - <<>>; -header(_, <<>>) -> - []; -header(0, Binary) -> - Binary; -header(N, Binary) -> - <> = Binary, - [ByteN | header(N-1, NewBinary)]. - -send_or_reply(false, _Pid, From, Data) when From =/= undefined -> - gen_fsm:reply(From, Data); -%% Can happen when handling own alert or tcp error/close and there is -%% no outstanding gen_fsm sync events -send_or_reply(false, no_pid, _, _) -> - ok; -send_or_reply(_, Pid, _From, Data) -> - send_user(Pid, Data). - - - -send_user(Pid, Msg) -> - Pid ! Msg. - -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); + Monitor = erlang:monitor(process, User), -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. + #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 + }. next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> handle_own_alert(Alert, Version, Current, State); @@ -1342,169 +555,272 @@ next_state_is_connection(StateName, State0) -> public_key_info = undefined, tls_handshake_history = ssl_handshake:init_handshake_history()}). - -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) +passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> + case Buffer of + <<>> -> + {Record, State} = next_record(State0), + next_state(StateName, StateName, Record, State); + _ -> + case read_application_data(<<>>, State0) of + Stop = {stop, _, _} -> + Stop; + {Record, State} -> + next_state(StateName, StateName, Record, State) + end end. +read_application_data(Data, #state{user_application = {_Mon, Pid}, + socket = Socket, + transport_cb = Transport, + socket_options = SOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + timer = Timer, + user_data_buffer = Buffer0} = State0) -> + Buffer1 = if + Buffer0 =:= <<>> -> Data; + Data =:= <<>> -> Buffer0; + true -> <> + end, + case get_data(SOpts, BytesToRead, Buffer1) of + {ok, ClientData, Buffer} -> % Send data + SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom), + cancel_timer(Timer), + State = State0#state{user_data_buffer = Buffer, + start_or_recv_from = undefined, + timer = undefined, + bytes_to_read = undefined, + socket_options = SocketOpt + }, + if + SocketOpt#socket_options.active =:= false; Buffer =:= <<>> -> + %% Passive mode, wait for active once or recv + %% Active and empty, get more data + next_record_if_active(State); + true -> %% We have more data + read_application_data(<<>>, State) + end; + {more, Buffer} -> % no reply, we need more data + next_record(State0#state{user_data_buffer = Buffer}); + {passive, Buffer} -> + next_record_if_active(State0#state{user_data_buffer = Buffer}); + {error,_Reason} -> %% Invalid packet in packet mode + deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom), + {stop, normal, State0} + end. -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 - -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, Port, Session) -> - ssl_manager:invalidate_session(Port, Session). - -initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, - {CbModule, DataTag, CloseTag, ErrorTag}) -> - ConnectionStates = ssl_record:init_connection_states(Role), - - SessionCacheCb = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - Cb; - _ -> - ssl_session_cache - end, - - Monitor = erlang:monitor(process, User), +get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> + infinity; +get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> + HibernateAfter. - #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() - }. - -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} +%% Picks ClientData +get_data(_, _, <<>>) -> + {more, <<>>}; +%% Recv timed out save buffer data until next recv +get_data(#socket_options{active=false}, undefined, Buffer) -> + {passive, Buffer}; +get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) + when Raw =:= raw; Raw =:= 0 -> %% Raw Mode + if + Active =/= false orelse BytesToRead =:= 0 -> + %% Active true or once, or passive mode recv(0) + {ok, Buffer, <<>>}; + byte_size(Buffer) >= BytesToRead -> + %% Passive Mode, recv(Bytes) + <> = Buffer, + {ok, Data, Rest}; + true -> + %% Passive Mode not enough data + {more, Buffer} end; +get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> + PacketOpts = [{packet_size, Size}], + case decode_packet(Type, Buffer, PacketOpts) of + {more, _} -> + {more, Buffer}; + Decoded -> + Decoded + end. + +decode_packet({http, headers}, Buffer, PacketOpts) -> + decode_packet(httph, Buffer, PacketOpts); +decode_packet({http_bin, headers}, Buffer, PacketOpts) -> + decode_packet(httph_bin, Buffer, PacketOpts); +decode_packet(Type, Buffer, PacketOpts) -> + erlang:decode_packet(Type, Buffer, PacketOpts). + +%% Just like with gen_tcp sockets, an ssl socket that has been configured with +%% {packet, http} (or {packet, http_bin}) will automatically switch to expect +%% HTTP headers after it sees a HTTP Request or HTTP Response line. We +%% represent the current state as follows: +%% #socket_options.packet =:= http: Expect a HTTP Request/Response line +%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers +%% Note that if the user has explicitly configured the socket to expect +%% HTTP headers using the {packet, httph} option, we don't do any automatic +%% switching of states. +deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, + Data, Pid, From) -> + send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data)), + SO = case Data of + {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), + ((Type =:= http) or (Type =:= http_bin)) -> + SOpts#socket_options{packet={Type, headers}}; + http_eoh when tuple_size(Type) =:= 2 -> + % End of headers - expect another Request/Response line + {Type1, headers} = Type, + SOpts#socket_options{packet=Type1}; + _ -> + SOpts + end, + case Active of + once -> + SO#socket_options{active=false}; + _ -> + SO + end. + +format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, + header = Header}, Data) -> + {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, ?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)). + +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, ?MODULE), + {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. + +do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode + header(N, Data); +do_format_reply(binary, _, _, Data) -> + Data; +do_format_reply(list, Packet, _, Data) + when Packet == http; Packet == {http, headers}; + Packet == http_bin; Packet == {http_bin, headers}; + Packet == httph; Packet == httph_bin -> + Data; +do_format_reply(list, _,_, Data) -> + binary_to_list(Data). + +header(0, <<>>) -> + <<>>; +header(_, <<>>) -> + []; +header(0, Binary) -> + Binary; +header(N, Binary) -> + <> = Binary, + [ByteN | header(N-1, NewBinary)]. + +send_or_reply(false, _Pid, From, Data) when From =/= undefined -> + gen_fsm:reply(From, Data); +%% Can happen when handling own alert or tcp error/close and there is +%% no outstanding gen_fsm sync events +send_or_reply(false, no_pid, _, _) -> + ok; +send_or_reply(_, Pid, _From, Data) -> + send_user(Pid, Data). + +send_user(Pid, Msg) -> + Pid ! Msg. + +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{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. +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. -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]). +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. + +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 -> <> + 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. +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; @@ -1515,7 +831,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), @@ -1565,10 +882,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, ?MODULE)}); + {ssl_closed, ssl_socket:socket(self(), + Transport, Socket, ?MODULE)}); ReasonCode -> send_or_reply(Active, Pid, From, - {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE), ReasonCode}) + {ssl_error, ssl_socket:socket(self(), + Transport, Socket, ?MODULE), ReasonCode}) end. log_alert(true, Info, Alert) -> @@ -1584,7 +903,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 @@ -1615,73 +934,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), - <>; -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 = 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}). - -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 @@ -1697,60 +969,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. - - -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. diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 7a854afc80..54029ebe6d 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -2649,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}, -- cgit v1.2.3 From 174b36ae2755b501e2b3152f6b00e9c59a90e848 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 27 Nov 2013 11:05:35 +0100 Subject: ssl: Trap exits --- lib/ssl/src/ssl_connection.erl | 7 +++++++ lib/ssl/src/tls_connection.erl | 1 + 2 files changed, 8 insertions(+) (limited to 'lib') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 8dac0a3f12..b7c1b9e8d0 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -822,6 +822,13 @@ 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)}; diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 9621182948..8e6f80da1e 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -145,6 +145,7 @@ 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 = ssl_handshake:init_handshake_history(), TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), -- cgit v1.2.3