diff options
Diffstat (limited to 'lib/ssl')
27 files changed, 1122 insertions, 571 deletions
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 5df2632149..1e1fe0d119 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -30,7 +30,101 @@ </header> <p>This document describes the changes made to the SSL application.</p> - <section><title>SSL 4.1.6</title> + <section><title>SSL 5.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Invalidation handling of sessions could cause the + time_stamp field in the session record to be set to + undefined crashing the session clean up process. This did + not affect the connections but would result in that the + session table would grow.</p> + <p> + Own Id: OTP-9696 Aux Id: seq11947 </p> + </item> + <item> + <p> + Changed code to use ets:foldl and throw instead of + ets:next traversal, avoiding the need to explicitly call + ets:safe_fixtable. It was possible to get a badarg-crash + under special circumstances.</p> + <p> + Own Id: OTP-9703 Aux Id: seq11947 </p> + </item> + <item> + <p> + Send ssl_closed notification to active ssl user when a + tcp error occurs.</p> + <p> + Own Id: OTP-9734 Aux Id: seq11946 </p> + </item> + <item> + <p> + If a passive receive was ongoing during a renegotiation + the process evaluating ssl:recv could be left hanging for + ever.</p> + <p> + Own Id: OTP-9744</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Support for the old ssl implementation is dropped and the + code is removed.</p> + <p> + Own Id: OTP-7048</p> + </item> + <item> + <p> + The erlang distribution can now be run over the new ssl + implementation. All options can currently not be set but + it is enough to replace to old ssl implementation.</p> + <p> + Own Id: OTP-7053</p> + </item> + <item> + <p> + public_key, ssl and crypto now supports PKCS-8</p> + <p> + Own Id: OTP-9312</p> + </item> + <item> + <p> + Implements a CBC timing attack counter measure. Thanks to + Andreas Schultz for providing the patch.</p> + <p> + Own Id: OTP-9683</p> + </item> + <item> + <p> + Mitigates an SSL/TLS Computational DoS attack by + disallowing the client to renegotiate many times in a row + in a short time interval, thanks to Tuncer Ayaz for + alerting us about this.</p> + <p> + Own Id: OTP-9739</p> + </item> + <item> + <p> + Implements the 1/n-1 splitting countermeasure to the + Rizzo Duong BEAST attack, affects SSL 3.0 and TLS 1.0. + Thanks to Tuncer Ayaz for alerting us about this.</p> + <p> + Own Id: OTP-9750</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 4.1.6</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 70122e4393..50268ae206 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -71,7 +71,8 @@ {fail_if_no_peer_cert, boolean()} {depth, integer()} | {cert, der_encoded()}| {certfile, path()} | - {key, der_encoded()} | {keyfile, path()} | {password, string()} | + {key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'PrivateKeyInfo', der_encoded()}} | + {keyfile, path()} | {password, string()} | {cacerts, [der_encoded()]} | {cacertfile, path()} | |{dh, der_encoded()} | {dhfile, path()} | {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()} @@ -139,7 +140,7 @@ <tag>{certfile, path()}</tag> <item>Path to a file containing the user's certificate.</item> - <tag>{key, der_encoded()}</tag> + <tag>{key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'PrivateKeyInfo', der_encoded()}}</tag> <item> The DER encoded users private key. If this option is supplied it will override the keyfile option.</item> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 29674f30da..1b07e76d6a 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,6 +1,7 @@ %% -*- erlang -*- {"%VSN%", [ + {"4.1.6", [{restart_application, ssl}]}, {"4.1.5", [{restart_application, ssl}]}, {"4.1.4", [{restart_application, ssl}]}, {"4.1.3", [{restart_application, ssl}]}, @@ -10,6 +11,7 @@ {"4.0.1", [{restart_application, ssl}]} ], [ + {"4.1.6", [{restart_application, ssl}]}, {"4.1.5", [{restart_application, ssl}]}, {"4.1.4", [{restart_application, ssl}]}, {"4.1.3", [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 35f9410562..d0693445e0 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -608,8 +608,11 @@ validate_option(certfile, Value) when Value == undefined; is_list(Value) -> validate_option(key, undefined) -> undefined; validate_option(key, {KeyType, Value}) when is_binary(Value), - KeyType == rsa; - KeyType == dsa -> + KeyType == rsa; %% Backwards compatibility + KeyType == dsa; %% Backwards compatibility + KeyType == 'RSAPrivateKey'; + KeyType == 'DSAPrivateKey'; + KeyType == 'PrivateKeyInfo' -> {KeyType, Value}; validate_option(keyfile, Value) when is_list(Value) -> Value; diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl index c9f81726b9..0c475a6d01 100644 --- a/lib/ssl/src/ssl_app.erl +++ b/lib/ssl/src/ssl_app.erl @@ -27,16 +27,9 @@ -export([start/2, stop/1]). -%%-------------------------------------------------------------------- --spec start(normal | {takeover, node()} | {failover, node()}, list()) -> - ignore | {ok, pid()} | {error, term()}. -%%-------------------------------------------------------------------- start(_Type, _StartArgs) -> ssl_sup:start_link(). -%-------------------------------------------------------------------- --spec stop(term())-> ok. -%%-------------------------------------------------------------------- stop(_State) -> ok. diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 422ea6404b..61876e1158 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -66,7 +66,7 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef) -> {ok, IssuerId} -> {other, IssuerId}; {error, issuer_not_found} -> - case find_issuer(OtpCert, no_candidate, CertDbHandle) of + case find_issuer(OtpCert, CertDbHandle) of {ok, IssuerId} -> {other, IssuerId}; Other -> @@ -193,7 +193,7 @@ certificate_chain(OtpCert, _Cert, CertDbHandle, CertsDbRef, Chain) -> {_, true = SelfSigned} -> certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned); {{error, issuer_not_found}, SelfSigned} -> - case find_issuer(OtpCert, no_candidate, CertDbHandle) of + case find_issuer(OtpCert, CertDbHandle) of {ok, {SerialNr, Issuer}} -> certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned); @@ -227,17 +227,24 @@ certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned {ok, lists:reverse(Chain)} end. -find_issuer(OtpCert, PrevCandidateKey, CertDbHandle) -> - case ssl_manager:issuer_candidate(PrevCandidateKey, CertDbHandle) of - no_more_candidates -> - {error, issuer_not_found}; - {Key, {_Cert, ErlCertCandidate}} -> - case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of - true -> - public_key:pkix_issuer_id(ErlCertCandidate, self); - false -> - find_issuer(OtpCert, Key, CertDbHandle) - end +find_issuer(OtpCert, CertDbHandle) -> + IsIssuerFun = fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> + case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of + true -> + throw(public_key:pkix_issuer_id(ErlCertCandidate, self)); + false -> + Acc + end; + (_, Acc) -> + Acc + end, + + try ssl_certificate_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _IssuerId} = Return -> + Return end. is_valid_extkey_usage(KeyUse, client) -> diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl index 0560a02110..cb2473576a 100644 --- a/lib/ssl/src/ssl_certificate_db.erl +++ b/lib/ssl/src/ssl_certificate_db.erl @@ -26,7 +26,7 @@ -include_lib("public_key/include/public_key.hrl"). -export([create/0, remove/1, add_trusted_certs/3, - remove_trusted_certs/2, lookup_trusted_cert/4, issuer_candidate/2, + remove_trusted_certs/2, lookup_trusted_cert/4, foldl/3, lookup_cached_certs/2, cache_pem_file/4, uncache_pem_file/2, lookup/2]). -type time() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}. @@ -127,8 +127,6 @@ uncache_pem_file(File, [_CertsDb, _FileToRefDb, PidToFileDb]) -> exit(Pid, shutdown) end, Pids). - - %%-------------------------------------------------------------------- -spec remove_trusted_certs(pid(), [db_handle()]) -> term(). @@ -161,37 +159,6 @@ remove_trusted_certs(Pid, [CertsDb, FileToRefDb, PidToFileDb]) -> end. %%-------------------------------------------------------------------- --spec issuer_candidate(no_candidate | cert_key() | {file, term()}, term()) -> - {cert_key(),{der_cert(), #'OTPCertificate'{}}} | no_more_candidates. -%% -%% Description: If a certificat does not define its issuer through -%% the extension 'ce-authorityKeyIdentifier' we can -%% try to find the issuer in the database over known -%% certificates. -%%-------------------------------------------------------------------- -issuer_candidate(no_candidate, Db) -> - case ets:first(Db) of - '$end_of_table' -> - no_more_candidates; - {file, _} = Key -> - issuer_candidate(Key, Db); - Key -> - [Cert] = lookup(Key, Db), - {Key, Cert} - end; - -issuer_candidate(PrevCandidateKey, Db) -> - case ets:next(Db, PrevCandidateKey) of - '$end_of_table' -> - no_more_candidates; - {file, _} = Key -> - issuer_candidate(Key, Db); - Key -> - [Cert] = lookup(Key, Db), - {Key, Cert} - end. - -%%-------------------------------------------------------------------- -spec lookup(term(), db_handle()) -> term() | undefined. %% %% Description: Looks up an element in a certificat <Db>. @@ -206,7 +173,18 @@ lookup(Key, Db) -> end, [Pick(Data) || Data <- Contents] end. - +%%-------------------------------------------------------------------- +-spec foldl(fun(), term(), db_handle()) -> term(). +%% +%% Description: Calls Fun(Elem, AccIn) on successive elements of the +%% cache, starting with AccIn == Acc0. Fun/2 must return a new +%% accumulator which is passed to the next call. The function returns +%% the final value of the accumulator. Acc0 is returned if the certifate +%% db is empty. +%%-------------------------------------------------------------------- +foldl(Fun, Acc0, Cache) -> + ets:foldl(Fun, Acc0, Cache). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 95a5efd6d0..d43d312be8 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -166,7 +166,7 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, false -> %% decryption failed or invalid padding, %% intentionally break Content to make - %% sure a packet with a an invalid padding + %% sure a packet with invalid padding %% but otherwise correct data will fail %% the MAC test later {<<16#F0, Content/binary>>, Mac, CipherState1} @@ -523,7 +523,7 @@ hash_size(sha) -> %% %% implementation note: %% We return the original (possibly invalid) PadLength in any case. -%% A invalid PadLength will be cought by is_correct_padding/2 +%% An invalid PadLength will be caught by is_correct_padding/2 %% generic_block_cipher_from_bin(T, HashSize) -> Sz1 = byte_size(T) - 1, diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index c772697f1d..28dd0c85d0 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -87,15 +87,17 @@ bytes_to_read, % integer(), # bytes to read in passive mode user_data_buffer, % binary() log_alert, % boolean() - renegotiation, % {boolean(), From | internal | peer} - recv_during_renegotiation, %boolean() - send_queue, % queue() - terminated = false % + renegotiation, % {boolean(), From | internal | peer} + recv_from, % + send_queue, % queue() + terminated = false, % + allow_renegotiate = true }). -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, - #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, + #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). +-define(WAIT_TO_ALLOW_RENEGOTIATION, 12000). -type state_name() :: hello | abbreviated | certify | cipher | connection. -type gen_fsm_state_return() :: {next_state, state_name(), #state{}} | @@ -291,10 +293,6 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> %% gen_fsm callbacks %%==================================================================== %%-------------------------------------------------------------------- --spec init(list()) -> {ok, state_name(), #state{}, timeout()} | {stop, term()}. -%% Possible return values not used now. -%% | {ok, state_name(), #state{}} | -%% ignore %% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or %% gen_fsm:start_link/3,4, this function is called by the new process to %% initialize. @@ -303,12 +301,13 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), Hashes0 = ssl_handshake:init_hashes(), - + TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), try ssl_init(SSLOpts0, Role) of {ok, Ref, CertDbHandle, CacheHandle, OwnCert, Key, DHParams} -> Session = State0#state.session, State = State0#state{tls_handshake_hashes = Hashes0, - session = Session#session{own_certificate = OwnCert}, + session = Session#session{own_certificate = OwnCert, + time_stamp = TimeStamp}, cert_db_ref = Ref, cert_db = CertDbHandle, session_cache = CacheHandle, @@ -321,8 +320,6 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, end. %%-------------------------------------------------------------------- -%% -spec state_name(event(), #state{}) -> gen_fsm_state_return() -%% %% Description:There should be one instance of this function for each %% possible state name. Whenever a gen_fsm receives an event sent %% using gen_fsm:send_event/2, the instance of this function with the @@ -351,19 +348,18 @@ hello(start, #state{host = Host, port = Port, role = client, State1 = State0#state{connection_states = CS2, negotiated_version = Version, %% Requested version session = - Session0#session{session_id = Hello#client_hello.session_id, - is_resumable = false}, + Session0#session{session_id = Hello#client_hello.session_id}, tls_handshake_hashes = Hashes1}, {Record, State} = next_record(State1), - next_state(hello, Record, State); + next_state(hello, hello, Record, State); hello(start, #state{role = server} = State0) -> {Record, State} = next_record(State0), - next_state(hello, Record, State); + next_state(hello, hello, Record, State); hello(#hello_request{}, #state{role = client} = State0) -> {Record, State} = next_record(State0), - next_state(hello, Record, State); + next_state(hello, hello, Record, State); hello(#server_hello{cipher_suite = CipherSuite, compression_method = Compression} = Hello, @@ -426,7 +422,7 @@ hello(Msg, State) -> %%-------------------------------------------------------------------- abbreviated(#hello_request{}, State0) -> {Record, State} = next_record(State0), - next_state(hello, Record, State); + next_state(abbreviated, hello, Record, State); abbreviated(#finished{verify_data = Data} = Finished, #state{role = server, @@ -479,7 +475,7 @@ abbreviated(Msg, State) -> %%-------------------------------------------------------------------- certify(#hello_request{}, State0) -> {Record, State} = next_record(State0), - next_state(hello, Record, State); + next_state(certify, hello, Record, State); certify(#certificate{asn1_certificates = []}, #state{role = server, negotiated_version = Version, @@ -487,7 +483,7 @@ certify(#certificate{asn1_certificates = []}, fail_if_no_peer_cert = true}} = State) -> Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), - handle_own_alert(Alert, Version, certify_certificate, State), + handle_own_alert(Alert, Version, certify, State), {stop, normal, State}; certify(#certificate{asn1_certificates = []}, @@ -496,7 +492,7 @@ certify(#certificate{asn1_certificates = []}, fail_if_no_peer_cert = false}} = State0) -> {Record, State} = next_record(State0#state{client_certificate_requested = false}), - next_state(certify, Record, State); + next_state(certify, certify, Record, State); certify(#certificate{} = Cert, #state{negotiated_version = Version, @@ -511,7 +507,7 @@ certify(#certificate{} = Cert, handle_peer_cert(PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}); #alert{} = Alert -> - handle_own_alert(Alert, Version, certify_certificate, State), + handle_own_alert(Alert, Version, certify, State), {stop, normal, State} end; @@ -522,10 +518,9 @@ certify(#server_key_exchange{} = KeyExchangeMsg, case handle_server_key(KeyExchangeMsg, State0) of #state{} = State1 -> {Record, State} = next_record(State1), - next_state(certify, Record, State); + next_state(certify, certify, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, certify_server_keyexchange, - State0), + handle_own_alert(Alert, Version, certify, State0), {stop, normal, State0} end; @@ -535,7 +530,7 @@ certify(#server_key_exchange{} = Msg, certify(#certificate_request{}, State0) -> {Record, State} = next_record(State0#state{client_certificate_requested = true}), - next_state(certify, Record, State); + next_state(certify, certify, Record, State); %% Master secret was determined with help of server-key exchange msg certify(#server_hello_done{}, @@ -550,8 +545,7 @@ certify(#server_hello_done{}, State = State0#state{connection_states = ConnectionStates1}, client_certify_and_key_exchange(State); #alert{} = Alert -> - handle_own_alert(Alert, Version, - certify_server_hello_done, State0), + handle_own_alert(Alert, Version, certify, State0), {stop, normal, State0} end; @@ -570,8 +564,7 @@ certify(#server_hello_done{}, session = Session}, client_certify_and_key_exchange(State); #alert{} = Alert -> - handle_own_alert(Alert, Version, - certify_server_hello_done, State0), + handle_own_alert(Alert, Version, certify, State0), {stop, normal, State0} end; @@ -588,7 +581,7 @@ certify(#client_key_exchange{exchange_keys = Keys}, certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), State) catch #alert{} = Alert -> - handle_own_alert(Alert, Version, certify_client_key_exchange, State), + handle_own_alert(Alert, Version, certify, State), {stop, normal, State} end; @@ -611,10 +604,9 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS State1 = State0#state{connection_states = ConnectionStates, session = Session}, {Record, State} = next_record(State1), - next_state(cipher, Record, State); + next_state(certify, cipher, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, - certify_client_key_exchange, State0), + handle_own_alert(Alert, Version, certify, State0), {stop, normal, State0} end; @@ -626,10 +618,9 @@ certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPubl case dh_master_secret(crypto:mpint(P), crypto:mpint(G), ClientPublicDhKey, ServerDhPrivateKey, State0) of #state{} = State1 -> {Record, State} = next_record(State1), - next_state(cipher, Record, State); + next_state(certify, cipher, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, - certify_client_key_exchange, State0), + handle_own_alert(Alert, Version, certify, State0), {stop, normal, State0} end. @@ -639,7 +630,7 @@ certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPubl %%-------------------------------------------------------------------- cipher(#hello_request{}, State0) -> {Record, State} = next_record(State0), - next_state(hello, Record, State); + next_state(cipher, hello, Record, State); cipher(#certificate_verify{signature = Signature}, #state{role = server, @@ -652,7 +643,7 @@ cipher(#certificate_verify{signature = Signature}, Version, MasterSecret, Hashes) of valid -> {Record, State} = next_record(State0), - next_state(cipher, Record, State); + next_state(cipher, cipher, Record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, cipher, State0), {stop, normal, State0} @@ -705,20 +696,32 @@ connection(#hello_request{}, #state{host = Host, port = Port, {Record, State} = next_record(State0#state{connection_states = ConnectionStates1, tls_handshake_hashes = Hashes1}), - next_state(hello, Record, State); -connection(#client_hello{} = Hello, #state{role = server} = State) -> - hello(Hello, State); - + 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 + %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client + %% initiated renegotiation we will disallow many client initiated + %% renegotiations immediately after each other. + 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) -> + 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}); + connection(timeout, State) -> {next_state, connection, State, hibernate}; connection(Msg, State) -> handle_unexpected_message(Msg, connection, State). %%-------------------------------------------------------------------- --spec handle_event(term(), state_name(), #state{}) -> term(). -%% As it is not currently used gen_fsm_state_return() makes -%% dialyzer unhappy! -%% %% Description: Whenever a gen_fsm receives an event sent using %% gen_fsm:send_all_state_event/2, this function is called to handle %% the event. Not currently used! @@ -727,47 +730,16 @@ handle_event(_Event, StateName, State) -> {next_state, StateName, State, get_timeout(State)}. %%-------------------------------------------------------------------- --spec handle_sync_event(term(), from(), state_name(), #state{}) -> - gen_fsm_state_return() | - {reply, reply(), state_name(), #state{}} | - {reply, reply(), state_name(), #state{}, timeout()} | - {stop, reason(), reply(), #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, Data0}, From, connection, - #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) -> +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 - Data = encode_packet(Data0, SockOpts), - case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of - {Msgs, [], ConnectionStates} -> - Result = Transport:send(Socket, Msgs), - {reply, Result, - connection, State#state{connection_states = ConnectionStates}, - get_timeout(State)}; - {Msgs, RestData, ConnectionStates} -> - if - Msgs =/= [] -> - Transport:send(Socket, Msgs); - true -> - ok - end, - renegotiate(State#state{connection_states = ConnectionStates, - send_queue = queue:in_r({From, RestData}, SendQueue), - renegotiation = {true, internal}}) - end + write_application_data(Data, From, State) catch throw:Error -> {reply, Error, connection, State, get_timeout(State)} end; @@ -824,14 +796,12 @@ handle_sync_event({shutdown, How0}, _, StateName, end; handle_sync_event({recv, N}, From, connection = StateName, State0) -> - passive_receive(State0#state{bytes_to_read = N, from = From}, StateName); + passive_receive(State0#state{bytes_to_read = N, recv_from = From}, StateName); %% Doing renegotiate wait with handling request until renegotiate is -%% finished. Will be handled by next_state_connection/2. +%% finished. Will be handled by next_state_is_connection/2. handle_sync_event({recv, N}, From, StateName, State) -> - {next_state, StateName, - State#state{bytes_to_read = N, from = From, - recv_during_renegotiation = true}, + {next_state, StateName, State#state{bytes_to_read = N, recv_from = From}, get_timeout(State)}; handle_sync_event({new_user, User}, _From, StateName, @@ -869,7 +839,7 @@ handle_sync_event({set_opts, Opts0}, _From, StateName, Buffer =:= <<>>, Opts1#socket_options.active =:= false -> %% Need data, set active once {Record, State2} = next_record_if_active(State1), - case next_state(StateName, Record, State2) of + case next_state(StateName, StateName, Record, State2) of {next_state, StateName, State, Timeout} -> {reply, Reply, StateName, State, Timeout}; {stop, Reason, State} -> @@ -879,11 +849,11 @@ handle_sync_event({set_opts, Opts0}, _From, StateName, %% Active once already set {reply, Reply, StateName, State1, get_timeout(State1)}; true -> - case application_data(<<>>, State1) of + case read_application_data(<<>>, State1) of Stop = {stop,_,_} -> Stop; {Record, State2} -> - case next_state(StateName, Record, State2) of + case next_state(StateName, StateName, Record, State2) of {next_state, StateName, State, Timeout} -> {reply, Reply, StateName, State, Timeout}; {stop, Reason, State} -> @@ -919,11 +889,6 @@ handle_sync_event(peer_certificate, _, StateName, {reply, {ok, Cert}, StateName, State, get_timeout(State)}. %%-------------------------------------------------------------------- --spec handle_info(msg(),state_name(), #state{}) -> - {next_state, state_name(), #state{}}| - {next_state, state_name(), #state{}, timeout()} | - {stop, reason(), #state{}}. -%% %% Description: This function is called by a gen_fsm when it receives any %% other message than a synchronous or asynchronous event %% (or a system message). @@ -931,22 +896,18 @@ handle_sync_event(peer_certificate, _, StateName, %% raw data from TCP, unpack records handle_info({Protocol, _, Data}, StateName, - #state{data_tag = Protocol, - negotiated_version = Version} = State0) -> + #state{data_tag = Protocol} = State0) -> case next_tls_record(Data, State0) of {Record, State} -> - next_state(StateName, Record, State); + next_state(StateName, StateName, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0), + handle_normal_shutdown(Alert, StateName, State0), {stop, normal, State0} end; -handle_info({CloseTag, Socket}, _StateName, +handle_info({CloseTag, Socket}, StateName, #state{socket = Socket, close_tag = CloseTag, - negotiated_version = Version, - socket_options = Opts, - user_application = {_Mon,Pid}, from = From, - role = Role} = State) -> + negotiated_version = Version} = State) -> %% Note that as of TLS 1.1, %% failure to properly close a connection no longer requires that a %% session not be resumed. This is a change from TLS 1.0 to conform @@ -961,8 +922,7 @@ handle_info({CloseTag, Socket}, _StateName, %%invalidate_session(Role, Host, Port, Session) ok end, - alert_user(Opts#socket_options.active, Pid, From, - ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), Role), + handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), {stop, normal, State}; handle_info({ErrorTag, Socket, econnaborted}, StateName, @@ -971,26 +931,26 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName, alert_user(User, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Role), {stop, normal, State}; -handle_info({ErrorTag, Socket, Reason}, _, - #state{socket = Socket, from = User, - role = Role, error_tag = ErrorTag} = 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), - alert_user(User, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), + 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(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)}. %%-------------------------------------------------------------------- --spec terminate(reason(), state_name(), #state{}) -> term(). -%% %% Description:This function is called by a gen_fsm when it is about %% to terminate. It should be the opposite of Module:init/1 and do any %% necessary cleaning up. When it returns, the gen_fsm terminates with @@ -1021,8 +981,6 @@ terminate(Reason, _StateName, #state{transport_cb = Transport, Transport:close(Socket). %%-------------------------------------------------------------------- --spec code_change(term(), state_name(), #state{}, list()) -> {ok, state_name(), #state{}}. -%% %% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- @@ -1126,18 +1084,38 @@ init_private_key(DbHandle, undefined, KeyFile, Password, _) -> {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle), [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List, PKey =:= 'RSAPrivateKey' orelse - PKey =:= 'DSAPrivateKey'], - public_key:pem_entry_decode(PemEntry, Password) + PKey =:= 'DSAPrivateKey' orelse + PKey =:= 'PrivateKeyInfo' + ], + private_key(public_key:pem_entry_decode(PemEntry, Password)) catch Error:Reason -> handle_file_error(?LINE, Error, Reason, KeyFile, ekeyfile, erlang:get_stacktrace()) end; +%% First two clauses are for backwards compatibility init_private_key(_,{rsa, PrivateKey}, _, _,_) -> - public_key:der_decode('RSAPrivateKey', PrivateKey); + init_private_key('RSAPrivateKey', PrivateKey); init_private_key(_,{dsa, PrivateKey},_,_,_) -> - public_key:der_decode('DSAPrivateKey', PrivateKey). + init_private_key('DSAPrivateKey', PrivateKey); +init_private_key(_,{Asn1Type, PrivateKey},_,_,_) -> + private_key(init_private_key(Asn1Type, PrivateKey)). + +init_private_key(Asn1Type, PrivateKey) -> + public_key:der_decode(Asn1Type, PrivateKey). + +private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'}, + privateKey = Key}) -> + public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key)); + +private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, + privateKey = Key}) -> + public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); +private_key(Key) -> + Key. -spec(handle_file_error(_,_,_,_,_,_) -> no_return()). handle_file_error(Line, Error, {badmatch, Reason}, File, Throw, Stack) -> @@ -1198,7 +1176,7 @@ handle_peer_cert(PeerCert, PublicKeyInfo, Session#session{peer_certificate = PeerCert}, public_key_info = PublicKeyInfo}, {Record, State} = next_record(State1), - next_state(certify, Record, State). + next_state(certify, certify, Record, State). certify_client(#state{client_certificate_requested = true, role = client, connection_states = ConnectionStates0, @@ -1240,8 +1218,7 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, ignore -> State; #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) - + throw(Alert) end; verify_client_cert(#state{client_certificate_requested = false} = State) -> State. @@ -1273,7 +1250,7 @@ do_server_hello(Type, #state{negotiated_version = Version, ConnectionStates, tls_handshake_hashes = Hashes}, {Record, State} = next_record(State3), - next_state(abbreviated, Record, State); + next_state(hello, abbreviated, Record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State1), {stop, normal, State1} @@ -1293,7 +1270,7 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, cipher_suite = CipherSuite, compression_method = Compression}, {Record, State} = next_record(State2#state{session = Session}), - next_state(certify, Record, State) + next_state(hello, certify, Record, State) catch #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State0), @@ -1305,7 +1282,7 @@ handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0} = cipher_suite = CipherSuite, compression_method = Compression}, {Record, State} = next_record(State0#state{session = Session}), - next_state(certify, Record, State). + next_state(hello, certify, Record, State). handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, negotiated_version = Version, @@ -1320,7 +1297,7 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, next_record(State0#state{ connection_states = ConnectionStates1, session = Session}), - next_state(abbreviated, Record, State); + next_state(hello, abbreviated, Record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State0), {stop, normal, State0} @@ -1337,10 +1314,10 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} = client_certificate_requested = false, tls_handshake_hashes = Hashes}, {Record, State} = next_record(State2), - next_state(cipher, Record, State) + next_state(certify, cipher, Record, State) catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, client_certify_and_key_exchange, State0), + throw:#alert{} = Alert -> + handle_own_alert(Alert, Version, certify, State0), {stop, normal, State0} end. @@ -1651,15 +1628,12 @@ encode_packet(Data, #socket_options{packet=Packet}) -> end. encode_size_packet(Bin, Size, Max) -> - Len = byte_size(Bin), + Len = erlang:byte_size(Bin), case Len > Max of true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); false -> <<Len:Size, Bin/binary>> end. -encode_data(Data, Version, ConnectionStates, RenegotiateAt) -> - ssl_record:encode_data(Data, Version, ConnectionStates, RenegotiateAt). - decode_alerts(Bin) -> decode_alerts(Bin, []). @@ -1673,20 +1647,20 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> case Buffer of <<>> -> {Record, State} = next_record(State0), - next_state(StateName, Record, State); + next_state(StateName, StateName, Record, State); _ -> - case application_data(<<>>, State0) of + case read_application_data(<<>>, State0) of Stop = {stop, _, _} -> Stop; {Record, State} -> - next_state(StateName, Record, State) + next_state(StateName, StateName, Record, State) end end. -application_data(Data, #state{user_application = {_Mon, Pid}, +read_application_data(Data, #state{user_application = {_Mon, Pid}, socket_options = SOpts, bytes_to_read = BytesToRead, - from = From, + recv_from = From, user_data_buffer = Buffer0} = State0) -> Buffer1 = if Buffer0 =:= <<>> -> Data; @@ -1697,7 +1671,7 @@ application_data(Data, #state{user_application = {_Mon, Pid}, {ok, ClientData, Buffer} -> % Send data SocketOpt = deliver_app_data(SOpts, ClientData, Pid, From), State = State0#state{user_data_buffer = Buffer, - from = undefined, + recv_from = undefined, bytes_to_read = 0, socket_options = SocketOpt }, @@ -1707,7 +1681,7 @@ application_data(Data, #state{user_application = {_Mon, Pid}, %% Active and empty, get more data next_record_if_active(State); true -> %% We have more data - application_data(<<>>, State) + read_application_data(<<>>, State) end; {more, Buffer} -> % no reply, we need more data next_record(State0#state{user_data_buffer = Buffer}); @@ -1716,6 +1690,39 @@ application_data(Data, #state{user_application = {_Mon, Pid}, {stop, normal, State0} end. +write_application_data(Data0, From, #state{socket = Socket, + negotiated_version = Version, + transport_cb = Transport, + connection_states = ConnectionStates0, + send_queue = SendQueue, + socket_options = SockOpts, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> + Data = encode_packet(Data0, SockOpts), + + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue), + renegotiation = {true, internal}}); + false -> + {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0), + Result = Transport:send(Socket, Msgs), + {reply, Result, + connection, State#state{connection_states = ConnectionStates}, get_timeout(State)} + end. + +time_to_renegotiate(_Data, #connection_states{current_write = + #connection_state{sequence_number = Num}}, RenegotiateAt) -> + + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), + %% but we chose to have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). + +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. + %% Picks ClientData get_data(_, _, <<>>) -> {more, <<>>}; @@ -1817,6 +1824,10 @@ header(N, Binary) -> 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). @@ -1841,18 +1852,18 @@ handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet | Packets]} Stop end. -next_state(_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> - handle_own_alert(Alert, Version, decipher_error, State), +next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> + handle_own_alert(Alert, Version, Current, State), {stop, normal, State}; -next_state(Next, no_record, State) -> +next_state(_,Next, no_record, State) -> {next_state, Next, State, get_timeout(State)}; -next_state(Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> +next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> Alerts = decode_alerts(EncAlerts), handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)}); -next_state(StateName, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, +next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, State0 = #state{tls_handshake_buffer = Buf0, negotiated_version = Version}) -> Handle = fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> @@ -1878,30 +1889,30 @@ next_state(StateName, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, try {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0), State = State0#state{tls_packets = Packets, tls_handshake_buffer = Buf}, - handle_tls_handshake(Handle, StateName, State) + handle_tls_handshake(Handle, Next, State) catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0), + handle_own_alert(Alert, Version, Current, State0), {stop, normal, State0} end; -next_state(StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> - case application_data(Data, State0) of +next_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> + case read_application_data(Data, State0) of Stop = {stop,_,_} -> Stop; {Record, State} -> - next_state(StateName, Record, State) + next_state(StateName, StateName, Record, State) end; -next_state(StateName, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = +next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = _ChangeCipher, #state{connection_states = ConnectionStates0} = State0) -> ConnectionStates1 = ssl_record:activate_pending_connection_state(ConnectionStates0, read), {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), - next_state(StateName, Record, State); -next_state(StateName, #ssl_tls{type = _Unknown}, State0) -> + next_state(Current, Next, Record, State); +next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> %% Ignore unknown type {Record, State} = next_record(State0), - next_state(StateName, Record, State). + next_state(Current, Next, Record, State). next_tls_record(Data, #state{tls_record_buffer = Buf0, tls_cipher_texts = CT0} = State0) -> @@ -1940,63 +1951,49 @@ next_state_connection(StateName, #state{send_queue = Queue0, negotiated_version = Version, socket = Socket, transport_cb = Transport, - connection_states = ConnectionStates0, - ssl_options = #ssl_options{renegotiate_at = RenegotiateAt} + connection_states = ConnectionStates0 } = State) -> - %% Send queued up data + %% Send queued up data that was queued while renegotiating case queue:out(Queue0) of {{value, {From, Data}}, Queue} -> - case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of - {Msgs, [], ConnectionStates} -> - Result = Transport:send(Socket, Msgs), - gen_fsm:reply(From, Result), - next_state_connection(StateName, - State#state{connection_states = ConnectionStates, - send_queue = Queue}); - %% This is unlikely to happen. User configuration of the - %% undocumented test option renegotiation_at can make it more likely. - {Msgs, RestData, ConnectionStates} -> - if - Msgs =/= [] -> - Transport:send(Socket, Msgs); - true -> - ok - end, - renegotiate(State#state{connection_states = ConnectionStates, - send_queue = queue:in_r({From, RestData}, Queue), - renegotiation = {true, internal}}) - end; + {Msgs, ConnectionStates} = + ssl_record:encode_data(Data, Version, ConnectionStates0), + Result = Transport:send(Socket, Msgs), + gen_fsm:reply(From, Result), + next_state_connection(StateName, + State#state{connection_states = ConnectionStates, + send_queue = Queue}); {empty, Queue0} -> - next_state_is_connection(State) + next_state_is_connection(StateName, State) end. %% In next_state_is_connection/1: clear tls_handshake_hashes, %% premaster_secret and public_key_info (only needed during handshake) %% to reduce memory foot print of a connection. -next_state_is_connection(State = - #state{recv_during_renegotiation = true, socket_options = - #socket_options{active = false}}) -> - passive_receive(State#state{recv_during_renegotiation = false, - premaster_secret = undefined, +next_state_is_connection(_, State = + #state{recv_from = From, + socket_options = + #socket_options{active = false}}) when From =/= undefined -> + passive_receive(State#state{premaster_secret = undefined, public_key_info = undefined, tls_handshake_hashes = {<<>>, <<>>}}, connection); -next_state_is_connection(State0) -> +next_state_is_connection(StateName, State0) -> {Record, State} = next_record_if_active(State0), - next_state(connection, Record, State#state{premaster_secret = undefined, + next_state(StateName, connection, Record, State#state{premaster_secret = undefined, public_key_info = undefined, tls_handshake_hashes = {<<>>, <<>>}}). -register_session(_, _, _, #session{is_resumable = true} = Session) -> - Session; %% Already registered -register_session(client, Host, Port, Session0) -> +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, Session0) -> +register_session(server, _, Port, #session{is_resumable = new} = Session0) -> Session = Session0#session{is_resumable = true}, ssl_manager:register_session(Port, Session), - Session. + Session; +register_session(_, _, _, Session) -> + Session. %% Already registered invalidate_session(client, Host, Port, Session) -> ssl_manager:invalidate_session(Host, Port, Session); @@ -2020,7 +2017,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, %% We do not want to save the password in the state so that %% could be written in the clear into error logs. ssl_options = SSLOptions#ssl_options{password = undefined}, - session = #session{is_resumable = false}, + session = #session{is_resumable = new}, transport_cb = CbModule, data_tag = DataTag, close_tag = CloseTag, @@ -2039,7 +2036,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, log_alert = true, session_cache_cb = SessionCacheCb, renegotiation = {false, first}, - recv_during_renegotiation = false, + recv_from = undefined, send_queue = queue:new() }. @@ -2152,16 +2149,14 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName, {stop, normal, State}; handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - StateName, #state{from = From, role = Role, - user_application = {_Mon, Pid}, socket_options = Opts} = State) -> - alert_user(StateName, Opts, Pid, From, Alert, Role), + StateName, State) -> + handle_normal_shutdown(Alert, StateName, State), {stop, normal, State}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{log_alert = Log, renegotiation = {true, internal}, from = From, - role = Role} = State) -> + #state{log_alert = Log, renegotiation = {true, internal}} = State) -> log_alert(Log, StateName, Alert), - alert_user(From, Alert, Role), + handle_normal_shutdown(Alert, StateName, State), {stop, normal, State}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, @@ -2169,13 +2164,13 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, log_alert(Log, StateName, Alert), gen_fsm:reply(From, {error, renegotiation_rejected}), {Record, State} = next_record(State0), - next_state(connection, Record, State); + next_state(StateName, connection, Record, State); handle_alert(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName, #state{log_alert = Log} = State0) -> log_alert(Log, StateName, Alert), {Record, State} = next_record(State0), - next_state(StateName, Record, State). + next_state(StateName, StateName, Record, State). alert_user(connection, Opts, Pid, From, Alert, Role) -> alert_user(Opts#socket_options.active, Pid, From, Alert, Role); @@ -2207,13 +2202,11 @@ log_alert(true, Info, Alert) -> log_alert(false, _, _) -> ok. -handle_own_alert(Alert, Version, Info, +handle_own_alert(Alert, Version, StateName, #state{transport_cb = Transport, socket = Socket, - from = User, - role = Role, connection_states = ConnectionStates, - log_alert = Log}) -> + log_alert = Log} = State) -> try %% Try to tell the other side {BinMsg, _} = encode_alert(Alert, Version, ConnectionStates), @@ -2223,12 +2216,20 @@ handle_own_alert(Alert, Version, Info, ignore end, try %% Try to tell the local user - log_alert(Log, Info, Alert), - alert_user(User, Alert, Role) + log_alert(Log, StateName, Alert), + handle_normal_shutdown(Alert,StateName, State) catch _:_ -> ok end. +handle_normal_shutdown(Alert, _, #state{from = User, role = Role, renegotiation = {false, first}}) -> + alert_user(User, Alert, Role); + +handle_normal_shutdown(Alert, StateName, #state{socket_options = Opts, + user_application = {_Mon, Pid}, + from = User, role = Role}) -> + alert_user(StateName, Opts, Pid, User, Alert, Role). + handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), handle_own_alert(Alert, Version, {Info, Msg}, State), @@ -2241,7 +2242,7 @@ make_premaster_secret(_, _) -> undefined. mpint_binary(Binary) -> - Size = byte_size(Binary), + Size = erlang:byte_size(Binary), <<?UINT32(Size), Binary/binary>>. @@ -2278,7 +2279,7 @@ renegotiate(#state{role = server, {Record, State} = next_record(State0#state{connection_states = ConnectionStates, tls_handshake_hashes = Hs0}), - next_state(hello, Record, State). + next_state(connection, hello, Record, State#state{allow_renegotiate = true}). notify_senders(SendQueue) -> lists:foreach(fun({From, _}) -> diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl index c1912401d7..9d9afb7707 100644 --- a/lib/ssl/src/ssl_dist_sup.erl +++ b/lib/ssl/src/ssl_dist_sup.erl @@ -41,7 +41,6 @@ start_link() -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init([]) -> {ok, {SupFlags :: tuple(), [ChildSpec :: tuple()]}}. init([]) -> SessionCertManager = session_and_cert_manager_child_spec(), diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index f873a6a913..542033e6ce 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2011. All Rights Reserved. +%% Copyright Ericsson AB 2007-2012. 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 @@ -188,14 +188,14 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, ValidationFunAndState = case VerifyFunAndState of undefined -> - {fun(OtpCert, ExtensionOrError, SslState) -> + {fun(OtpCert, ExtensionOrVerifyResult, SslState) -> ssl_certificate:validate_extension(OtpCert, - ExtensionOrError, SslState) + ExtensionOrVerifyResult, SslState) end, Role}; {Fun, UserState0} -> - {fun(OtpCert, ExtensionOrError, {SslState, UserState}) -> + {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> case ssl_certificate:validate_extension(OtpCert, - ExtensionOrError, + Extension, SslState) of {valid, NewSslState} -> {valid, {NewSslState, UserState}}; @@ -204,8 +204,11 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, SslState); {unknown, _} -> apply_user_fun(Fun, OtpCert, - ExtensionOrError, UserState, SslState) - end + Extension, UserState, SslState) + end; + (OtpCert, VerifyResult, {SslState, UserState}) -> + apply_user_fun(Fun, OtpCert, VerifyResult, UserState, + SslState) end, {Role, UserState0}} end, @@ -447,7 +450,7 @@ server_hello_done() -> -spec encode_handshake(tls_handshake(), tls_version()) -> iolist(). %% %% Description: Encode a handshake packet to binary -%%-------------------------------------------------------------------- +%%--------------------------------------------------------------------x encode_handshake(Package, Version) -> {MsgType, Bin} = enc_hs(Package, Version), Len = byte_size(Bin), @@ -1092,18 +1095,12 @@ certificate_authorities(CertDbHandle, CertDbRef) -> list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). certificate_authorities_from_db(CertDbHandle, CertDbRef) -> - certificate_authorities_from_db(CertDbHandle, CertDbRef, no_candidate, []). - -certificate_authorities_from_db(CertDbHandle,CertDbRef, PrevKey, Acc) -> - case ssl_manager:issuer_candidate(PrevKey, CertDbHandle) of - no_more_candidates -> - lists:reverse(Acc); - {{CertDbRef, _, _} = Key, Cert} -> - certificate_authorities_from_db(CertDbHandle, CertDbRef, Key, [Cert|Acc]); - {Key, _Cert} -> - %% skip certs not from this ssl connection - certificate_authorities_from_db(CertDbHandle, CertDbRef, Key, Acc) - end. + ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> + [Cert | Acc]; + (_, Acc) -> + Acc + end, + ssl_certificate_db:foldl(ConnectionCerts, [], CertDbHandle). digitally_signed(Hash, #'RSAPrivateKey'{} = Key) -> public_key:encrypt_private(Hash, Key, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index dcf310c535..6389ff03f5 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -29,8 +29,8 @@ %% Internal application API -export([start_link/1, start_link_dist/1, connection_init/2, cache_pem_file/2, - lookup_trusted_cert/4, issuer_candidate/2, client_session_id/4, - server_session_id/4, + lookup_trusted_cert/4, + client_session_id/4, server_session_id/4, register_session/2, register_session/3, invalidate_session/2, invalidate_session/3]). @@ -51,7 +51,7 @@ session_lifetime, certificate_db, session_validation_timer, - last_delay_timer %% Keep for testing purposes + last_delay_timer = {undefined, undefined}%% Keep for testing purposes }). -define('24H_in_msec', 8640000). @@ -112,16 +112,7 @@ cache_pem_file(File, DbHandle) -> %% -------------------------------------------------------------------- lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> ssl_certificate_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer). -%%-------------------------------------------------------------------- --spec issuer_candidate(cert_key() | no_candidate, term()) -> - {cert_key(), - {der_cert(), - #'OTPCertificate'{}}} | no_more_candidates. -%% -%% Description: Return next issuer candidate. -%%-------------------------------------------------------------------- -issuer_candidate(PrevCandidateKey, DbHandle) -> - ssl_certificate_db:issuer_candidate(PrevCandidateKey, DbHandle). + %%-------------------------------------------------------------------- -spec client_session_id(host(), inet:port_number(), #ssl_options{}, der_cert() | undefined) -> session_id(). @@ -278,25 +269,16 @@ handle_cast({register_session, Port, Session}, CacheCb:update(Cache, {Port, NewSession#session.session_id}, NewSession), {noreply, State}; -%%% When a session is invalidated we need to wait a while before deleting -%%% it as there might be pending connections that rightfully needs to look -%%% up the session data but new connections should not get to use this session. handle_cast({invalidate_session, Host, Port, #session{session_id = ID} = Session}, #state{session_cache = Cache, session_cache_cb = CacheCb} = State) -> - CacheCb:update(Cache, {{Host, Port}, ID}, Session#session{is_resumable = false}), - TRef = - erlang:send_after(delay_time(), self(), {delayed_clean_session, {{Host, Port}, ID}}), - {noreply, State#state{last_delay_timer = TRef}}; + invalidate_session(Cache, CacheCb, {{Host, Port}, ID}, Session, State); handle_cast({invalidate_session, Port, #session{session_id = ID} = Session}, #state{session_cache = Cache, session_cache_cb = CacheCb} = State) -> - CacheCb:update(Cache, {Port, ID}, Session#session{is_resumable = false}), - TRef = - erlang:send_after(delay_time(), self(), {delayed_clean_session, {Port, ID}}), - {noreply, State#state{last_delay_timer = TRef}}; + invalidate_session(Cache, CacheCb, {Port, ID}, Session, State); handle_cast({recache_pem, File, LastWrite, Pid, From}, #state{certificate_db = [_, FileToRefDb, _]} = State0) -> @@ -320,7 +302,7 @@ handle_cast({recache_pem, File, LastWrite, Pid, From}, %% {stop, reason(), #state{}}. %% %% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- +%%------------------------------------------------------------------- handle_info(validate_sessions, #state{session_cache_cb = CacheCb, session_cache = Cache, session_lifetime = LifeTime @@ -444,3 +426,25 @@ delay_time() -> _ -> ?CLEAN_SESSION_DB end. + +invalidate_session(Cache, CacheCb, Key, Session, #state{last_delay_timer = LastTimer} = State) -> + case CacheCb:lookup(Cache, Key) of + undefined -> %% Session is already invalidated + {noreply, State}; + #session{is_resumable = new} -> + CacheCb:delete(Cache, Key), + {noreply, State}; + _ -> + %% When a registered session is invalidated we need to wait a while before deleting + %% it as there might be pending connections that rightfully needs to look + %% up the session data but new connections should not get to use this session. + CacheCb:update(Cache, Key, Session#session{is_resumable = false}), + TRef = + erlang:send_after(delay_time(), self(), {delayed_clean_session, Key}), + {noreply, State#state{last_delay_timer = last_delay_timer(Key, TRef, LastTimer)}} + end. + +last_delay_timer({{_,_},_}, TRef, {LastServer, _}) -> + {LastServer, TRef}; +last_delay_timer({_,_}, TRef, {_, LastClient}) -> + {TRef, LastClient}. diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 72091fdd5f..830026c825 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -48,7 +48,7 @@ %% Encoding records -export([encode_handshake/3, encode_alert_record/3, - encode_change_cipher_spec/2, encode_data/4]). + encode_change_cipher_spec/2, encode_data/3]). %% Decoding -export([decode_cipher_text/2]). @@ -503,36 +503,18 @@ decode_cipher_text(CipherText, ConnnectionStates0) -> Alert end. %%-------------------------------------------------------------------- --spec encode_data(iolist(), tls_version(), #connection_states{}, integer()) -> - {iolist(), iolist(), #connection_states{}}. +-spec encode_data(binary(), tls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. %% %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- -encode_data(Frag, Version, ConnectionStates, RenegotiateAt) - when byte_size(Frag) < (?MAX_PLAIN_TEXT_LENGTH - 2048) -> - case encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates, RenegotiateAt) of - {renegotiate, Data} -> - {[], Data, ConnectionStates}; - {Msg, CS} -> - {Msg, [], CS} - end; - -encode_data(Frag, Version, ConnectionStates, RenegotiateAt) when is_binary(Frag) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH - 2048), - encode_data(Data, Version, ConnectionStates, RenegotiateAt); - -encode_data(Data, Version, ConnectionStates0, RenegotiateAt) when is_list(Data) -> - {ConnectionStates, EncodedMsg, NotEncdedData} = - lists:foldl(fun(B, {CS0, Encoded, Rest}) -> - case encode_plain_text(?APPLICATION_DATA, - Version, B, CS0, RenegotiateAt) of - {renegotiate, NotEnc} -> - {CS0, Encoded, [NotEnc | Rest]}; - {Enc, CS1} -> - {CS1, [Enc | Encoded], Rest} - end - end, {ConnectionStates0, [], []}, Data), - {lists:reverse(EncodedMsg), lists:reverse(NotEncdedData), ConnectionStates}. +encode_data(Frag, Version, + #connection_states{current_write = #connection_state{ + security_parameters = + #security_parameters{bulk_cipher_algorithm = BCA}}} = + ConnectionStates) -> + Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA), + encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). %%-------------------------------------------------------------------- -spec encode_handshake(iolist(), tls_version(), #connection_states{}) -> @@ -566,6 +548,14 @@ encode_change_cipher_spec(Version, ConnectionStates) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +encode_iolist(Type, Data, Version, ConnectionStates0) -> + {ConnectionStates, EncodedMsg} = + lists:foldl(fun(Text, {CS0, Encoded}) -> + {Enc, CS1} = encode_plain_text(Type, Version, Text, CS0), + {CS1, [Enc | Encoded]} + end, {ConnectionStates0, []}, Data), + {lists:reverse(EncodedMsg), ConnectionStates}. + highest_protocol_version() -> highest_protocol_version(supported_protocol_versions()). @@ -602,29 +592,23 @@ record_protocol_role(client) -> record_protocol_role(server) -> ?SERVER. -split_bin(Bin, ChunkSize) -> - split_bin(Bin, ChunkSize, []). +%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are not vulnerable to this attack. +split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + do_split_bin(Rest, ChunkSize, [[FirstByte]]); +split_bin(Bin, ChunkSize, _, _) -> + do_split_bin(Bin, ChunkSize, []). -split_bin(<<>>, _, Acc) -> +do_split_bin(<<>>, _, Acc) -> lists:reverse(Acc); -split_bin(Bin, ChunkSize, Acc) -> +do_split_bin(Bin, ChunkSize, Acc) -> case Bin of <<Chunk:ChunkSize/binary, Rest/binary>> -> - split_bin(Rest, ChunkSize, [Chunk | Acc]); + do_split_bin(Rest, ChunkSize, [Chunk | Acc]); _ -> lists:reverse(Acc, [Bin]) end. -encode_plain_text(Type, Version, Data, ConnectionStates, RenegotiateAt) -> - #connection_states{current_write = - #connection_state{sequence_number = Num}} = ConnectionStates, - case renegotiate(Num, RenegotiateAt) of - false -> - encode_plain_text(Type, Version, Data, ConnectionStates); - true -> - {renegotiate, Data} - end. - encode_plain_text(Type, Version, Data, ConnectionStates) -> #connection_states{current_write=#connection_state{ compression_state=CompS0, @@ -637,11 +621,6 @@ encode_plain_text(Type, Version, Data, ConnectionStates) -> CTBin = encode_tls_cipher_text(Type, Version, CipherText), {CTBin, ConnectionStates#connection_states{current_write = CS2}}. -renegotiate(N, M) when N < M-> - false; -renegotiate(_,_) -> - true. - encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) -> Length = erlang:iolist_size(Fragment), [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment]. diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 5fb0070b91..282d642138 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -70,9 +70,10 @@ -define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19 %% Sequence numbers can not wrap so when max is about to be reached we should renegotiate. %% We will renegotiate a little before so that there will be sequence numbers left -%% for the rehandshake and a little data. --define(MARGIN, 100). --define(DEFAULT_RENEGOTIATE_AT, ?MAX_SEQENCE_NUMBER - ?MARGIN). +%% for the rehandshake and a little data. Currently we decided to renegotiate a little more +%% often as we can have a cheaper test to check if it is time to renegotiate. It will still +%% be fairly seldom. +-define(DEFAULT_RENEGOTIATE_AT, 268435456). %% math:pow(2, 28) %% ConnectionEnd -define(SERVER, 0). diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index bf738649f6..df5d7e0146 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -103,9 +103,9 @@ select_session([], _, _) -> select_session(Sessions, #ssl_options{ciphers = Ciphers, reuse_sessions = ReuseSession}, OwnCert) -> - IsResumable = - fun(Session) -> - ReuseSession andalso (Session#session.is_resumable) andalso + IsResumable = + fun(Session) -> + ReuseSession andalso resumable(Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) andalso (OwnCert == Session#session.own_certificate) end, @@ -147,10 +147,10 @@ is_resumable(SuggestedSessionId, Port, ReuseEnabled, ReuseFun, Cache, #session{cipher_suite = CipherSuite, own_certificate = SessionOwnCert, compression_method = Compression, - is_resumable = Is_resumable, + is_resumable = IsResumable, peer_certificate = PeerCert} = Session -> ReuseEnabled - andalso Is_resumable + andalso resumable(IsResumable) andalso (OwnCert == SessionOwnCert) andalso valid_session(Session, SecondLifeTime) andalso ReuseFun(SuggestedSessionId, PeerCert, @@ -158,3 +158,8 @@ is_resumable(SuggestedSessionId, Port, ReuseEnabled, ReuseFun, Cache, undefined -> false end. + +resumable(new) -> + false; +resumable(IsResumable) -> + IsResumable. diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl index 93969f628f..f9bbf905e1 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_session_cache.erl @@ -28,27 +28,19 @@ -export([init/1, terminate/1, lookup/2, update/3, delete/2, foldl/3, select_session/2]). --type key() :: {{host(), inet:port_number()}, session_id()} | {inet:port_number(), session_id()}. - %%-------------------------------------------------------------------- --spec init(list()) -> db_handle(). %% Returns reference to the cache (opaque) -%% %% Description: Return table reference. Called by ssl_manager process. %%-------------------------------------------------------------------- init(_) -> ets:new(cache_name(), [set, protected]). %%-------------------------------------------------------------------- --spec terminate(db_handle()) -> any(). -%% %% Description: Handles cache table at termination of ssl manager. %%-------------------------------------------------------------------- terminate(Cache) -> ets:delete(Cache). %%-------------------------------------------------------------------- --spec lookup(db_handle(), key()) -> #session{} | undefined. -%% %% Description: Looks up a cach entry. Should be callable from any %% process. %%-------------------------------------------------------------------- @@ -61,8 +53,6 @@ lookup(Cache, Key) -> end. %%-------------------------------------------------------------------- --spec update(db_handle(), key(), #session{}) -> any(). -%% %% Description: Caches a new session or updates a already cached one. %% Will only be called from the ssl_manager process. %%-------------------------------------------------------------------- @@ -70,8 +60,6 @@ update(Cache, Key, Session) -> ets:insert(Cache, {Key, Session}). %%-------------------------------------------------------------------- --spec delete(db_handle(), key()) -> any(). -%% %% Description: Delets a cache entry. %% Will only be called from the ssl_manager process. %%-------------------------------------------------------------------- @@ -79,8 +67,6 @@ delete(Cache, Key) -> ets:delete(Cache, Key). %%-------------------------------------------------------------------- --spec foldl(fun(), term(), db_handle()) -> term(). -%% %% Description: Calls Fun(Elem, AccIn) on successive elements of the %% cache, starting with AccIn == Acc0. Fun/2 must return a new %% accumulator which is passed to the next call. The function returns @@ -91,8 +77,6 @@ foldl(Fun, Acc0, Cache) -> ets:foldl(Fun, Acc0, Cache). %%-------------------------------------------------------------------- --spec select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}]. -%% %% Description: Selects a session that could be reused. Should be callable %% from any process. %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl index f8416bf327..f2b22b0f1b 100644 --- a/lib/ssl/src/ssl_session_cache_api.erl +++ b/lib/ssl/src/ssl_session_cache_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. 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 @@ -20,18 +20,15 @@ %% -module(ssl_session_cache_api). +-include("ssl_handshake.hrl"). +-include("ssl_internal.hrl"). --export([behaviour_info/1]). +-type key() :: {{host(), inet:port_number()}, session_id()} | {inet:port_number(), session_id()}. -behaviour_info(callbacks) -> - [ - {init, 1}, - {terminate, 1}, - {lookup, 2}, - {update, 3}, - {delete, 2}, - {foldl, 3}, - {select_session, 2} - ]; -behaviour_info(_) -> - undefined. +-callback init(list()) -> db_handle(). +-callback terminate(db_handle()) -> any(). +-callback lookup(db_handle(), key()) -> #session{} | undefined. +-callback update(db_handle(), key(), #session{}) -> any(). +-callback delete(db_handle(), key()) -> any(). +-callback foldl(fun(), term(), db_handle()) -> term(). +-callback select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}]. diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl index cb10b1362a..59039a6e0a 100644 --- a/lib/ssl/src/ssl_sup.erl +++ b/lib/ssl/src/ssl_sup.erl @@ -41,7 +41,6 @@ start_link() -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init([]) -> {ok, {SupFlags :: tuple(), [ChildSpec :: tuple()]}}. init([]) -> %% OLD ssl - moved start to ssl.erl only if old diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl index 8b01ca3ad4..254aa6d2f9 100644 --- a/lib/ssl/test/erl_make_certs.erl +++ b/lib/ssl/test/erl_make_certs.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010. All Rights Reserved. +%% Copyright Ericsson AB 2011. 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 @@ -175,7 +175,7 @@ issuer(true, Opts, SubjectKey) -> issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) -> {issuer_der(Issuer), decode_key(IssuerKey)}; issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) -> - {ok, [{cert, Cert, _}|_]} = public_key:pem_to_der(File), + {ok, [{cert, Cert, _}|_]} = pem_to_der(File), {issuer_der(Cert), decode_key(IssuerKey)}. issuer_der(Issuer) -> @@ -185,7 +185,7 @@ issuer_der(Issuer) -> Subject. subject(undefined, IsRootCA) -> - User = if IsRootCA -> "RootCA"; true -> os:getenv("USER") end, + User = if IsRootCA -> "RootCA"; true -> user() end, Opts = [{email, User ++ "@erlang.org"}, {name, User}, {city, "Stockholm"}, @@ -196,6 +196,14 @@ subject(undefined, IsRootCA) -> subject(Opts, _) -> subject(Opts). +user() -> + case os:getenv("USER") of + false -> + "test_user"; + User -> + User + end. + subject(SubjectOpts) when is_list(SubjectOpts) -> Encode = fun(Opt) -> {Type,Value} = subject_enc(Opt), diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index a9109c5a6e..0059a1e1ec 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2011. All Rights Reserved. +%% Copyright Ericsson AB 2007-2012. 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 @@ -36,6 +36,7 @@ -define(LONG_TIMEOUT, 600000). -define(EXPIRE, 10). -define(SLEEP, 500). +-define(RENEGOTIATION_DISABLE_TIME, 12000). %% Test server callback functions %%-------------------------------------------------------------------- @@ -249,6 +250,8 @@ all() -> no_authority_key_identifier, invalid_signature_client, invalid_signature_server, cert_expired, client_with_cert_cipher_suites_handshake, + verify_fun_always_run_client, + verify_fun_always_run_server, unknown_server_ca_fail, der_input, unknown_server_ca_accept_verify_none, unknown_server_ca_accept_verify_peer, @@ -256,7 +259,9 @@ all() -> %%different_ca_peer_sign, no_reuses_session_server_restart_new_cert, no_reuses_session_server_restart_new_cert_file, reuseaddr, - hibernate, connect_twice + hibernate, connect_twice, renegotiate_dos_mitigate_active, + renegotiate_dos_mitigate_passive, + tcp_error_propagation_in_active_mode, rizzo, no_rizzo_rc4 ]. groups() -> @@ -393,8 +398,8 @@ controlling_process(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientMsg = "Hello server", - ServerMsg = "Hello client", + ClientMsg = "Server hello", + ServerMsg = "Client hello", Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -415,11 +420,15 @@ controlling_process(Config) when is_list(Config) -> [self(), Client, Server]), receive + {ssl, _, "S"} -> + receive_s_rizzo_duong_beast(); {ssl, _, ServerMsg} -> receive {ssl, _, ClientMsg} -> ok end; + {ssl, _, "C"} -> + receive_c_rizzo_duong_beast(); {ssl, _, ClientMsg} -> receive {ssl, _, ServerMsg} -> @@ -440,6 +449,28 @@ controlling_process_result(Socket, Pid, Msg) -> ssl:send(Socket, Msg), no_result_msg. +receive_s_rizzo_duong_beast() -> + receive + {ssl, _, "erver hello"} -> + receive + {ssl, _, "C"} -> + receive + {ssl, _, "lient hello"} -> + ok + end + end + end. +receive_c_rizzo_duong_beast() -> + receive + {ssl, _, "lient hello"} -> + receive + {ssl, _, "S"} -> + receive + {ssl, _, "erver hello"} -> + ok + end + end + end. %%-------------------------------------------------------------------- controller_dies(doc) -> ["Test that the socket is closed after controlling process dies"]; @@ -1231,6 +1262,11 @@ upgrade_result(Socket) -> %% Make sure binary is inherited from tcp socket and that we do %% not get the list default! receive + {ssl, _, <<"H">>} -> + receive + {ssl, _, <<"ello world">>} -> + ok + end; {ssl, _, <<"Hello world">>} -> ok end. @@ -1532,14 +1568,14 @@ eoptions(Config) when is_list(Config) -> {cacertfile, ""}, {dhfile,'dh.pem' }, {ciphers, [{foo, bar, sha, ignore}]}, - {reuse_session, foo}, - {reuse_sessions, 0}, + {reuse_session, foo}, + {reuse_sessions, 0}, {renegotiate_at, "10"}, - {debug, 1}, + {debug, 1}, {mode, depech}, - {packet, 8.0}, - {packet_size, "2"}, - {header, a}, + {packet, 8.0}, + {packet_size, "2"}, + {header, a}, {active, trice}, {key, 'key.pem' }], @@ -2313,8 +2349,8 @@ server_verify_client_once_passive(Config) when is_list(Config) -> {options, [{active, false} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client0, ok), - ssl_test_lib:close(Client0), Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + ssl_test_lib:close(Client0), Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -2340,7 +2376,7 @@ server_verify_client_once_active(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, send_recv_result_active, []}}, - {options, [{active, once}, {verify, verify_peer}, + {options, [{active, true}, {verify, verify_peer}, {verify_client_once, true} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), @@ -2351,8 +2387,8 @@ server_verify_client_once_active(Config) when is_list(Config) -> {options, [{active, true} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client0, ok), - ssl_test_lib:close(Client0), Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + ssl_test_lib:close(Client0), Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -2389,8 +2425,8 @@ server_verify_client_once_active_once(Config) when is_list(Config) -> {options, [{active, once} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client0, ok), - ssl_test_lib:close(Client0), Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + ssl_test_lib:close(Client0), Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -2592,7 +2628,7 @@ client_renegotiate(Config) when is_list(Config) -> {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {?MODULE, @@ -2724,17 +2760,28 @@ client_no_wrap_sequence_number(Config) when is_list(Config) -> {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), + Version = ssl_record:highest_protocol_version(ssl_record:supported_protocol_versions()), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, - trigger_renegotiate, [[ErlData, N+2]]}}, + trigger_renegotiate, [[ErlData, treashold(N, Version)]]}}, {options, [{reuse_sessions, false}, {renegotiate_at, N} | ClientOpts]}]), ssl_test_lib:check_result(Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). + + %% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast +treashold(N, {3,0}) -> + (N div 2) + 1; +treashold(N, {3,1}) -> + (N div 2) + 1; +treashold(N, _) -> + N + 1. + %%-------------------------------------------------------------------- server_no_wrap_sequence_number(doc) -> ["Test that erlang server will renegotiate session when", @@ -2784,7 +2831,7 @@ extended_key_usage_verify_peer(Config) when is_list(Config) -> KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = public_key:pem_entry_decode(KeyEntry), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), ServerCertFile = proplists:get_value(certfile, ServerOpts), NewServerCertFile = filename:join(PrivDir, "server/new_cert.pem"), @@ -2846,7 +2893,7 @@ extended_key_usage_verify_none(Config) when is_list(Config) -> KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = public_key:pem_entry_decode(KeyEntry), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), ServerCertFile = proplists:get_value(certfile, ServerOpts), NewServerCertFile = filename:join(PrivDir, "server/new_cert.pem"), @@ -2908,7 +2955,7 @@ no_authority_key_identifier(Config) when is_list(Config) -> KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = public_key:pem_entry_decode(KeyEntry), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), CertFile = proplists:get_value(certfile, ServerOpts), NewCertFile = filename:join(PrivDir, "server/new_cert.pem"), @@ -2966,7 +3013,7 @@ invalid_signature_server(Config) when is_list(Config) -> KeyFile = filename:join(PrivDir, "server/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = public_key:pem_entry_decode(KeyEntry), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), ServerCertFile = proplists:get_value(certfile, ServerOpts), NewServerCertFile = filename:join(PrivDir, "server/invalid_cert.pem"), @@ -2988,8 +3035,8 @@ invalid_signature_server(Config) when is_list(Config) -> {from, self()}, {options, [{verify, verify_peer} | ClientOpts]}]), - ssl_test_lib:check_result(Server, {error, "bad certificate"}, - Client, {error,"bad certificate"}). + tcp_delivery_workaround(Server, {error, "bad certificate"}, + Client, {error,"bad certificate"}). %%-------------------------------------------------------------------- @@ -3006,7 +3053,7 @@ invalid_signature_client(Config) when is_list(Config) -> KeyFile = filename:join(PrivDir, "client/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = public_key:pem_entry_decode(KeyEntry), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), ClientCertFile = proplists:get_value(certfile, ClientOpts), NewClientCertFile = filename:join(PrivDir, "client/invalid_cert.pem"), @@ -3034,41 +3081,47 @@ invalid_signature_client(Config) when is_list(Config) -> tcp_delivery_workaround(Server, ServerMsg, Client, ClientMsg) -> receive {Server, ServerMsg} -> - receive - {Client, ClientMsg} -> - ok; - {Client, {error,closed}} -> - test_server:format("client got close"); - Unexpected -> - test_server:fail(Unexpected) - end; + client_msg(Client, ClientMsg); {Client, ClientMsg} -> - receive - {Server, ServerMsg} -> - ok; - Unexpected -> - test_server:fail(Unexpected) - end; + server_msg(Server, ServerMsg); {Client, {error,closed}} -> - receive - {Server, ServerMsg} -> - ok; - Unexpected -> - test_server:fail(Unexpected) - end; + server_msg(Server, ServerMsg); {Server, {error,closed}} -> - receive - {Client, ClientMsg} -> - ok; - {Client, {error,closed}} -> - test_server:format("client got close"), - ok; - Unexpected -> - test_server:fail(Unexpected) - end; + client_msg(Client, ClientMsg); + {Client, {error, esslconnect}} -> + server_msg(Server, ServerMsg); + {Server, {error, esslaccept}} -> + client_msg(Client, ClientMsg) + end. + +client_msg(Client, ClientMsg) -> + receive + {Client, ClientMsg} -> + ok; + {Client, {error,closed}} -> + test_server:format("client got close"), + ok; + {Client, {error, esslconnect}} -> + test_server:format("client got econnaborted"), + ok; Unexpected -> test_server:fail(Unexpected) end. + +server_msg(Server, ServerMsg) -> + receive + {Server, ServerMsg} -> + ok; + {Server, {error,closed}} -> + test_server:format("server got close"), + ok; + {Server, {error, esslaccept}} -> + test_server:format("server got econnaborted"), + ok; + Unexpected -> + test_server:fail(Unexpected) + end. + %%-------------------------------------------------------------------- cert_expired(doc) -> ["Test server with invalid signature"]; @@ -3083,7 +3136,7 @@ cert_expired(Config) when is_list(Config) -> KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = public_key:pem_entry_decode(KeyEntry), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), ServerCertFile = proplists:get_value(certfile, ServerOpts), NewServerCertFile = filename:join(PrivDir, "server/expired_cert.pem"), @@ -3166,6 +3219,105 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +verify_fun_always_run_client(doc) -> + ["Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"]; +verify_fun_always_run_client(suite) -> + []; +verify_fun_always_run_client(Config) when is_list(Config) -> + ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we can not tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [2]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} + | ClientOpts]}]), + %% Server error may be esslaccept or closed depending on timing + %% this is not a bug it is a circumstance of how tcp works! + receive + {Server, ServerError} -> + test_server:format("Server Error ~p~n", [ServerError]) + end, + + ssl_test_lib:check_result(Client, {error, esslconnect}). + +%%-------------------------------------------------------------------- +verify_fun_always_run_server(doc) -> + ["Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"]; +verify_fun_always_run_server(suite) -> + []; +verify_fun_always_run_server(Config) when is_list(Config) -> + ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we can not tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [2]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer} + | ClientOpts]}]), + + %% Client error may be esslconnect or closed depending on timing + %% this is not a bug it is a circumstance of how tcp works! + receive + {Client, ClientError} -> + test_server:format("Client Error ~p~n", [ClientError]) + end, + + ssl_test_lib:check_result(Server, {error, esslaccept}). + %%-------------------------------------------------------------------- unknown_server_ca_fail(doc) -> ["Test that the client fails if the ca is unknown in verify_peer mode"]; @@ -3358,14 +3510,14 @@ der_input_opts(Opts) -> Keyfile = proplists:get_value(keyfile, Opts), Dhfile = proplists:get_value(dhfile, Opts), [{_, Cert, _}] = ssl_test_lib:pem_to_der(Certfile), - [{_, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), + [{Asn1Type, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), [{_, DHParams, _}] = ssl_test_lib:pem_to_der(Dhfile), CaCerts = lists:map(fun(Entry) -> {_, CaCert, _} = Entry, CaCert end, ssl_test_lib:pem_to_der(CaCertsfile)), - {Cert, {rsa, Key}, CaCerts, DHParams}. + {Cert, {Asn1Type, Key}, CaCerts, DHParams}. %%-------------------------------------------------------------------- %% different_ca_peer_sign(doc) -> @@ -3588,14 +3740,13 @@ hibernate(Config) -> {from, self()}, {mfa, {?MODULE, send_recv_result_active, []}}, {options, [{hibernate_after, 1000}|ClientOpts]}]), - - { current_function, { _M, _F, _A } } = + {current_function, _} = process_info(Pid, current_function), timer:sleep(1100), - { current_function, { erlang, hibernate, 3} } = - process_info(Pid, current_function), + {current_function, {erlang, hibernate, 3}} = + process_info(Pid, current_function), ssl_test_lib:close(Server), ssl_test_lib:close(Client). @@ -3647,6 +3798,166 @@ connect_twice(Config) when is_list(Config) -> ssl_test_lib:close(Client), ssl_test_lib:close(Client1). +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_active(doc) -> + ["Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"]; + +renegotiate_dos_mitigate_active(suite) -> + []; + +renegotiate_dos_mitigate_active(Config) when is_list(Config) -> + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, [ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_passive(doc) -> + ["Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"]; + +renegotiate_dos_mitigate_passive(suite) -> + []; + +renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tcp_error_propagation_in_active_mode(doc) -> + ["Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"]; +tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, #sslsocket{pid=Pid} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, receive_msg, []}}, + {options, ClientOpts}]), + + {status, _, _, StatusInfo} = sys:get_status(Pid), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + Socket = element(10, State), + + %% Fake tcp error + Pid ! {tcp_error, Socket, etimedout}, + + ssl_test_lib:check_result(Client, {ssl_closed, SslSocket}). +%%-------------------------------------------------------------------- + +rizzo(doc) -> ["Test that there is a 1/n-1-split for non RC4 in 'TLS < 1.1' as it is + vunrable to Rizzo/Dungon attack"]; + +rizzo(Config) when is_list(Config) -> + Ciphers = [X || X ={_,Y,_} <- ssl:cipher_suites(), Y =/= rc4_128], + run_send_recv_rizzo(Ciphers, Config, sslv3, + {?MODULE, send_recv_result_active_rizzo, []}), + run_send_recv_rizzo(Ciphers, Config, tlsv1, + {?MODULE, send_recv_result_active_rizzo, []}). + +no_rizzo_rc4(doc) -> + ["Test that there is no 1/n-1-split for RC4 as it is not vunrable to Rizzo/Dungon attack"]; + +no_rizzo_rc4(Config) when is_list(Config) -> + Ciphers = [X || X ={_,Y,_} <- ssl:cipher_suites(),Y == rc4_128], + run_send_recv_rizzo(Ciphers, Config, sslv3, + {?MODULE, send_recv_result_active_no_rizzo, []}), + run_send_recv_rizzo(Ciphers, Config, tlsv1, + {?MODULE, send_recv_result_active_no_rizzo, []}). + +run_send_recv_rizzo(Ciphers, Config, Version, Mfa) -> + Result = lists:map(fun(Cipher) -> + rizzo_test(Cipher, Config, Version, Mfa) end, + Ciphers), + case lists:flatten(Result) of + [] -> + ok; + Error -> + test_server:format("Cipher suite errors: ~p~n", [Error]), + test_server:fail(cipher_suite_failed_see_test_case_log) + end. + +rizzo_test(Cipher, Config, Version, Mfa) -> + {ClientOpts, ServerOpts} = client_server_opts(Cipher, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, Mfa}, + {options, [{active, true}, {ciphers, [Cipher]}, + {versions, [Version]} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, Mfa}, + {options, [{active, true} | ClientOpts]}]), + + Result = ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + case Result of + ok -> + []; + Error -> + [{Cipher, Error}] + end. + +client_server_opts({KeyAlgo,_,_}, Config) when KeyAlgo == rsa orelse KeyAlgo == dhe_rsa -> + {?config(client_opts, Config), + ?config(server_opts, Config)}; +client_server_opts({KeyAlgo,_,_}, Config) when KeyAlgo == dss orelse KeyAlgo == dhe_dss -> + {?config(client_dsa_opts, Config), + ?config(server_dsa_opts, Config)}. %%-------------------------------------------------------------------- %%% Internal functions @@ -3659,6 +3970,28 @@ send_recv_result(Socket) -> send_recv_result_active(Socket) -> ssl:send(Socket, "Hello world"), receive + {ssl, Socket, "H"} -> + receive + {ssl, Socket, "ello world"} -> + ok + end; + {ssl, Socket, "Hello world"} -> + ok + end. + +send_recv_result_active_rizzo(Socket) -> + ssl:send(Socket, "Hello world"), + receive + {ssl, Socket, "H"} -> + receive + {ssl, Socket, "ello world"} -> + ok + end + end. + +send_recv_result_active_no_rizzo(Socket) -> + ssl:send(Socket, "Hello world"), + receive {ssl, Socket, "Hello world"} -> ok end. @@ -3666,6 +3999,12 @@ send_recv_result_active(Socket) -> send_recv_result_active_once(Socket) -> ssl:send(Socket, "Hello world"), receive + {ssl, Socket, "H"} -> + ssl:setopts(Socket, [{active, once}]), + receive + {ssl, Socket, "ello world"} -> + ok + end; {ssl, Socket, "Hello world"} -> ok end. @@ -3690,6 +4029,25 @@ renegotiate_reuse_session(Socket, Data) -> test_server:sleep(?SLEEP), renegotiate(Socket, Data). +renegotiate_immediately(Socket) -> + receive + {ssl, Socket, "Hello world"} -> + ok; + %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast + {ssl, Socket, "H"} -> + receive + {ssl, Socket, "ello world"} -> + ok + end + end, + ok = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + test_server:sleep(?RENEGOTIATION_DISABLE_TIME +1), + ok = ssl:renegotiate(Socket), + test_server:format("Renegotiated again"), + ssl:send(Socket, "Hello world"), + ok. + new_config(PrivDir, ServerOpts0) -> CaCertFile = proplists:get_value(cacertfile, ServerOpts0), CertFile = proplists:get_value(certfile, ServerOpts0), @@ -3863,8 +4221,17 @@ erlang_ssl_receive(Socket, Data) -> {ssl, Socket, Data} -> io:format("Received ~p~n",[Data]), ok; + {ssl, Socket, Byte} when length(Byte) == 1 -> %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast + io:format("Received ~p~n",[Byte]), + erlang_ssl_receive(Socket, tl(Data)); Other -> test_server:fail({unexpected_message, Other}) after ?SLEEP * 3 -> test_server:fail({did_not_get, Data}) end. + +receive_msg(_) -> + receive + Msg -> + Msg + end. diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl index 87478e13bc..99bc21e820 100644 --- a/lib/ssl/test/ssl_cipher_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_SUITE.erl @@ -134,7 +134,7 @@ aes_decipher_good(Config) when is_list(Config) -> Version = {3,3}, Content = <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56,72,69,76,76,79,10>>, Mac = <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>, - {Content, Mac, CipherState1} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), + {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), ok. %%-------------------------------------------------------------------- @@ -155,7 +155,7 @@ aes_decipher_fail(Config) when is_list(Config) -> 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122, 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>, Version = {3,3}, - {Content, Mac, CipherState1} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), + {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), 32 = byte_size(Content), 32 = byte_size(Mac), ok. diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 23e9268f9b..8fe55ee7a4 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -54,9 +54,14 @@ end_per_group(_GroupName, Config) -> init_per_suite(Config0) -> try crypto:start() of ok -> - Config = add_ssl_opts_config(Config0), - setup_certs(Config), - Config + case test_server:is_cover() of + false -> + Config = add_ssl_opts_config(Config0), + setup_certs(Config), + Config; + true -> + {skip, "Can not be covered"} + end catch _:_ -> {skip, "Crypto did not start"} end. @@ -65,11 +70,31 @@ end_per_suite(Config) -> application:stop(crypto), Config. +init_per_testcase(plain_verify_options = Case, Config) when is_list(Config) -> + SslFlags = setup_dist_opts([{many_verify_opts, true} | Config]), + Flags = case os:getenv("ERL_FLAGS") of + false -> + os:putenv("ERL_FLAGS", SslFlags), + ""; + OldFlags -> + os:putenv("ERL_FLAGS", OldFlags ++ "" ++ SslFlags), + OldFlags + end, + common_init(Case, [{old_flags, Flags} | Config]); + init_per_testcase(Case, Config) when is_list(Config) -> + common_init(Case, Config). + +common_init(Case, Config) -> Dog = ?t:timetrap(?t:seconds(?DEFAULT_TIMETRAP_SECS)), [{watchdog, Dog},{testcase, Case}|Config]. -end_per_testcase(_Case, Config) when is_list(Config) -> +end_per_testcase(Case, Config) when is_list(Config) -> + Flags = proplists:get_value(old_flags, Config), + os:putenv("ERL_FLAGS", Flags), + common_end(Case, Config). + +common_end(_, Config) -> Dog = ?config(watchdog, Config), ?t:timetrap_cancel(Dog), ok. @@ -205,9 +230,9 @@ plain_verify_options(Config) when is_list(Config) -> "server_reuse_sessions true client_reuse_sessions true " "server_hibernate_after 500 client_hibernate_after 500", - NH1 = start_ssl_node([{additional_dist_opts, DistOpts}, {many_verify_opts, true} | Config]), + NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node([{additional_dist_opts, DistOpts}, {many_verify_opts, true} | Config]), + NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), @@ -467,8 +492,10 @@ cnct2tstsrvr([Host, Port]) when is_list(Host), is_list(Port) -> ets:insert(test_server_info, {test_server_handler, self()}), ssl_node_con_loop(Socket); - _Error -> - halt("Failed to connect to test server") + Error -> + halt("Failed to connect to test server " ++ + lists:flatten(io_lib:format("Host:~p ~n Port:~p~n Error:~p~n", + [Host, Port, Error]))) end end), spawn(fun () -> @@ -476,9 +503,8 @@ cnct2tstsrvr([Host, Port]) when is_list(Host), is_list(Port) -> receive {'DOWN', Mon, process, ConnHandler, Reason} -> receive after 1000 -> ok end, - halt("test server connection handler terminated: " - ++ - lists:flatten(io_lib:format("~p", [Reason]))) + halt("test server connection handler terminated: " ++ + lists:flatten(io_lib:format("~p", [Reason]))) end end). @@ -613,19 +639,34 @@ setup_dist_opts(Config) -> ++ "-ssl_dist_opt server_certfile " ++ SKC ++ " " ++ "-ssl_dist_opt client_certfile " ++ CKC ++ " "; true -> - "-proto_dist inet_tls " - ++ "-ssl_dist_opt server_certfile " ++ SC ++ " " - ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " - ++ "-ssl_dist_opt server_cacertfile " ++ SCA ++ " " - ++ "-ssl_dist_opt server_verify verify_peer " - ++ "-ssl_dist_opt server_fail_if_no_peer_cert true " - ++ "-ssl_dist_opt server_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA " - ++ "-ssl_dist_opt server_dhfile " ++ Dhfile ++ " " - ++ "-ssl_dist_opt client_certfile " ++ CC ++ " " - ++ "-ssl_dist_opt client_keyfile " ++ CK ++ " " - ++ "-ssl_dist_opt client_cacertfile " ++ CCA ++ " " - ++ "-ssl_dist_opt client_verify verify_peer " - ++ "-ssl_dist_opt client_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA " + case os:type() of + {win32, _} -> + "-proto_dist inet_tls " + ++ "-ssl_dist_opt server_certfile " ++ SKC ++ " " + ++ "-ssl_dist_opt server_cacertfile " ++ SCA ++ " " + ++ "-ssl_dist_opt server_verify verify_peer " + ++ "-ssl_dist_opt server_fail_if_no_peer_cert true " + ++ "-ssl_dist_opt server_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA " + ++ "-ssl_dist_opt server_dhfile " ++ Dhfile ++ " " + ++ "-ssl_dist_opt client_certfile " ++ CKC ++ " " + ++ "-ssl_dist_opt client_cacertfile " ++ CCA ++ " " + ++ "-ssl_dist_opt client_verify verify_peer " + ++ "-ssl_dist_opt client_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA "; + _ -> + "-proto_dist inet_tls " + ++ "-ssl_dist_opt server_certfile " ++ SC ++ " " + ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " + ++ "-ssl_dist_opt server_cacertfile " ++ SCA ++ " " + ++ "-ssl_dist_opt server_verify verify_peer " + ++ "-ssl_dist_opt server_fail_if_no_peer_cert true " + ++ "-ssl_dist_opt server_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA " + ++ "-ssl_dist_opt server_dhfile " ++ Dhfile ++ " " + ++ "-ssl_dist_opt client_certfile " ++ CC ++ " " + ++ "-ssl_dist_opt client_keyfile " ++ CK ++ " " + ++ "-ssl_dist_opt client_cacertfile " ++ CCA ++ " " + ++ "-ssl_dist_opt client_verify verify_peer " + ++ "-ssl_dist_opt client_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA " + end end, MoreOpts = proplists:get_value(additional_dist_opts, Config, []), DistOpts ++ MoreOpts. diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index 9d2599b778..4b74f57a60 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -158,14 +158,24 @@ all() -> packet_asn1_decode, packet_asn1_decode_list, packet_tpkt_decode, packet_tpkt_decode_list, packet_sunrm_decode, packet_sunrm_decode_list, - header_decode_one_byte, header_decode_two_bytes, - header_decode_two_bytes_one_sent, - header_decode_two_bytes_two_sent]. + {group, header} + ]. groups() -> - []. + [{header, [], [ header_decode_one_byte, + header_decode_two_bytes, + header_decode_two_bytes_one_sent, + header_decode_two_bytes_two_sent]}]. + +init_per_group(header, Config) -> + case ssl_record:highest_protocol_version(ssl_record:supported_protocol_versions()) of + {3, N} when N < 2 -> + {skip, ""}; + _ -> + Config + end; -init_per_group(_GroupName, Config) -> +init_per_group(_, Config) -> Config. end_per_group(_GroupName, Config) -> @@ -2626,6 +2636,13 @@ active_once_raw(_, _, 0, _) -> ok; active_once_raw(Socket, Data, N, Acc) -> receive + {ssl, Socket, Byte} when length(Byte) == 1 -> + ssl:setopts(Socket, [{active, once}]), + receive + {ssl, Socket, _} -> + ssl:setopts(Socket, [{active, once}]), + active_once_raw(Socket, Data, N-1, []) + end; {ssl, Socket, Data} -> ssl:setopts(Socket, [{active, once}]), active_once_raw(Socket, Data, N-1, []); @@ -2648,7 +2665,14 @@ active_once_packet(Socket,_, 0) -> {other, Other, ssl:session_info(Socket), 0} end; active_once_packet(Socket, Data, N) -> - receive + receive + {ssl, Socket, Byte} when length(Byte) == 1 -> + ssl:setopts(Socket, [{active, once}]), + receive + {ssl, Socket, _} -> + ssl:setopts(Socket, [{active, once}]), + active_once_packet(Socket, Data, N-1) + end; {ssl, Socket, Data} -> ok end, @@ -2662,6 +2686,11 @@ active_raw(_Socket, _, 0, _) -> ok; active_raw(Socket, Data, N, Acc) -> receive + {ssl, Socket, Byte} when length(Byte) == 1 -> + receive + {ssl, Socket, _} -> + active_raw(Socket, Data, N -1) + end; {ssl, Socket, Data} -> active_raw(Socket, Data, N-1, []); {ssl, Socket, Other} -> @@ -2682,6 +2711,11 @@ active_packet(Socket, _, 0) -> end; active_packet(Socket, Data, N) -> receive + {ssl, Socket, Byte} when length(Byte) == 1 -> + receive + {ssl, Socket, _} -> + active_packet(Socket, Data, N -1) + end; {ssl, Socket, Data} -> active_packet(Socket, Data, N -1); Other -> diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index 5ea45018e6..491aa893c2 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -210,7 +210,7 @@ session_cleanup(Config)when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, - State = state(Prop), + State = ssl_test_lib:state(Prop), Cache = element(2, State), SessionTimer = element(6, State), @@ -225,11 +225,12 @@ session_cleanup(Config)when is_list(Config) -> check_timer(SessionTimer), test_server:sleep(?DELAY *2), %% Delay time + some extra time - DelayTimer = get_delay_timer(), + {ServerDelayTimer, ClientDelayTimer} = get_delay_timers(), - check_timer(DelayTimer), + check_timer(ServerDelayTimer), + check_timer(ClientDelayTimer), - test_server:sleep(?SLEEP), %% Make sure clean has had to run + test_server:sleep(?SLEEP), %% Make sure clean has had time to run undefined = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}), undefined = ssl_session_cache:lookup(Cache, {Port, Id}), @@ -238,31 +239,34 @@ session_cleanup(Config)when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). -state([{data,[{"State", State}]} | _]) -> - State; -state([_ | Rest]) -> - state(Rest). - check_timer(Timer) -> case erlang:read_timer(Timer) of false -> {status, _, _, _} = sys:get_status(whereis(ssl_manager)), + timer:sleep(?SLEEP), + {status, _, _, _} = sys:get_status(whereis(ssl_manager)), ok; Int -> test_server:sleep(Int), check_timer(Timer) end. -get_delay_timer() -> +get_delay_timers() -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, - State = state(Prop), + State = ssl_test_lib:state(Prop), case element(7, State) of - undefined -> + {undefined, undefined} -> + test_server:sleep(?SLEEP), + get_delay_timers(); + {undefined, _} -> + test_server:sleep(?SLEEP), + get_delay_timers(); + {_, undefined} -> test_server:sleep(?SLEEP), - get_delay_timer(); - DelayTimer -> - DelayTimer + get_delay_timers(); + DelayTimers -> + DelayTimers end. %%-------------------------------------------------------------------- session_cache_process_list(doc) -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index b7916b96eb..fa8a1826f2 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -22,6 +22,7 @@ -include("test_server.hrl"). -include("test_server_line.hrl"). +-include_lib("public_key/include/public_key.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -661,6 +662,9 @@ cipher_result(Socket, Result) -> %% to properly test "cipher state" handling ssl:send(Socket, "Hello\n"), receive + {ssl, Socket, "H"} -> + ssl:send(Socket, " world\n"), + receive_rizzo_duong_beast(); {ssl, Socket, "Hello\n"} -> ssl:send(Socket, " world\n"), receive @@ -673,3 +677,34 @@ cipher_result(Socket, Result) -> session_info_result(Socket) -> ssl:session_info(Socket). + + +public_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?rsaEncryption}, + privateKey = Key}) -> + public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key)); + +public_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, + privateKey = Key}) -> + public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); +public_key(Key) -> + Key. +receive_rizzo_duong_beast() -> + receive + {ssl, _, "ello\n"} -> + receive + {ssl, _, " "} -> + receive + {ssl, _, "world\n"} -> + ok + end + end + end. + +state([{data,[{"State", State}]} | _]) -> + State; +state([{data,[{"StateData", State}]} | _]) -> + State; +state([_ | Rest]) -> + state(Rest). diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 64a6a9eaf8..01fca1f166 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2011. All Rights Reserved. +%% Copyright Ericsson AB 2008-2012. 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 @@ -109,6 +109,9 @@ special_init(TestCase, Config) TestCase == erlang_server_openssl_client_no_wrap_sequence_number -> check_sane_openssl_renegotaite(Config); +special_init(ssl2_erlang_server_openssl_client, Config) -> + check_sane_openssl_sslv2(Config); + special_init(_, Config) -> Config. @@ -168,7 +171,8 @@ all() -> tls1_erlang_server_openssl_client_client_cert, tls1_erlang_server_erlang_client_client_cert, ciphers_rsa_signed_certs, ciphers_dsa_signed_certs, - erlang_client_bad_openssl_server, expired_session, + erlang_client_bad_openssl_server, + expired_session, ssl2_erlang_server_openssl_client]. groups() -> @@ -222,7 +226,6 @@ erlang_client_openssl_server(Config) when is_list(Config) -> %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), - ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -256,9 +259,9 @@ erlang_server_openssl_client(Config) when is_list(Config) -> port_command(OpenSslPort, Data), ssl_test_lib:check_result(Server, ok), - - ssl_test_lib:close(Server), + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -306,7 +309,6 @@ tls1_erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), - ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -346,8 +348,8 @@ tls1_erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok), + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -395,7 +397,6 @@ ssl3_erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), - ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -435,8 +436,8 @@ ssl3_erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok), + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -475,8 +476,8 @@ erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok), + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -525,7 +526,6 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), - ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -574,7 +574,6 @@ erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), - ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -615,8 +614,8 @@ erlang_server_openssl_client_no_wrap_sequence_number(Config) when is_list(Config ssl_test_lib:check_result(Server, ok), + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -663,7 +662,6 @@ erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), - ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -674,6 +672,7 @@ ssl3_erlang_client_openssl_server(doc) -> ssl3_erlang_client_openssl_server(suite) -> []; ssl3_erlang_client_openssl_server(Config) when is_list(Config) -> + process_flag(trap_exit, true), ServerOpts = ?config(server_opts, Config), ClientOpts = ?config(client_opts, Config), @@ -700,11 +699,11 @@ ssl3_erlang_client_openssl_server(Config) when is_list(Config) -> {options, [{versions, [sslv3]} | ClientOpts]}]), ssl_test_lib:check_result(Client, ok), - - ssl_test_lib:close(Client), - %% Clean close down! + + %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), - test_server:sleep(?SLEEP), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), ok. %%-------------------------------------------------------------------- @@ -714,6 +713,7 @@ ssl3_erlang_server_openssl_client(doc) -> ssl3_erlang_server_openssl_client(suite) -> []; ssl3_erlang_server_openssl_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), ServerOpts = ?config(server_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -734,10 +734,10 @@ ssl3_erlang_server_openssl_client(Config) when is_list(Config) -> OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), ssl_test_lib:check_result(Server, ok), - - close_port(OpenSslPort), %% openssl server first + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - test_server:sleep(?SLEEP), + close_port(OpenSslPort), + process_flag(trap_exit, false), ok. %%-------------------------------------------------------------------- @@ -779,7 +779,7 @@ ssl3_erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), - %% Clean close down! + %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false), @@ -824,9 +824,9 @@ ssl3_erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok), - close_port(OpenSslPort), %% openssl server first + %% Clean close down! Server needs to be closed first !! + close_port(OpenSslPort), ssl_test_lib:close(Server), - %% Clean close down! process_flag(trap_exit, false), ok. @@ -849,7 +849,9 @@ ssl3_erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, + erlang_ssl_receive, + %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast + [Data]}}, {options, [{verify , verify_peer} | ServerOpts]}]), @@ -858,6 +860,7 @@ ssl3_erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, + %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast {mfa, {ssl, send, [Data]}}, {options, [{versions, [sslv3]} | ClientOpts]}]), @@ -869,6 +872,7 @@ ssl3_erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> process_flag(trap_exit, false), ok. + %%-------------------------------------------------------------------- tls1_erlang_client_openssl_server(doc) -> @@ -907,10 +911,10 @@ tls1_erlang_client_openssl_server(Config) when is_list(Config) -> [{versions, [tlsv1]} | ClientOpts]}]), ssl_test_lib:check_result(Client, ok), - - ssl_test_lib:close(Client), - %% Clean close down! + + %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), + ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -943,9 +947,9 @@ tls1_erlang_server_openssl_client(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok), - %% Clean close down! - close_port(OpenSslPort), + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), + close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -989,7 +993,7 @@ tls1_erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), - %% Clean close down! + %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false), @@ -1034,9 +1038,9 @@ tls1_erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok), - %% Clean close down! - close_port(OpenSslPort), + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), + close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -1071,9 +1075,7 @@ tls1_erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> [{versions, [tlsv1]} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - %% Clean close down! process_flag(trap_exit, false), ok. %%-------------------------------------------------------------------- @@ -1136,7 +1138,7 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", test_server:format("openssl cmd: ~p~n", [Cmd]), @@ -1171,8 +1173,8 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> Result = ssl_test_lib:wait_for_result(Client, ok), + %% Clean close down! Server needs to be closed first !! close_port(OpenSslPort), - %% Clean close down! ssl_test_lib:close(Client), Return = case Result of @@ -1184,6 +1186,12 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> process_flag(trap_exit, false), Return. + +version_flag(tlsv1) -> + " -tls1 "; +version_flag(sslv3) -> + " -ssl3 ". + %%-------------------------------------------------------------------- erlang_client_bad_openssl_server(doc) -> [""]; @@ -1199,26 +1207,26 @@ erlang_client_bad_openssl_server(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", - + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", + test_server:format("openssl cmd: ~p~n", [Cmd]), OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - + wait_for_openssl_server(), Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, server_sent_garbage, []}}, - {options, - [{versions, [tlsv1]} | ClientOpts]}]), + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, server_sent_garbage, []}}, + {options, + [{versions, [tlsv1]} | ClientOpts]}]), %% Send garbage port_command(OpensslPort, ?OPENSSL_GARBAGE), - + test_server:sleep(?SLEEP), Client0 ! server_sent_garbage, @@ -1228,17 +1236,16 @@ erlang_client_bad_openssl_server(Config) when is_list(Config) -> ssl_test_lib:close(Client0), %% Make sure openssl does not hang and leave zombie process - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result_msg, []}}, - {options, - [{versions, [tlsv1]} | ClientOpts]}]), - - ssl_test_lib:close(Client1), - - %% Clean close down! + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result_msg, []}}, + {options, + [{versions, [tlsv1]} | ClientOpts]}]), + + %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), + ssl_test_lib:close(Client1), process_flag(trap_exit, false), ok. @@ -1297,6 +1304,7 @@ expired_session(Config) when is_list(Config) -> {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), + %% Clean close down! Server needs to be closed first !! close_port(OpensslPort), ssl_test_lib:close(Client2), process_flag(trap_exit, false). @@ -1329,8 +1337,8 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, {error,"protocol version"}), + %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -1346,6 +1354,8 @@ erlang_ssl_receive(Socket, Data) -> %% open_ssl server sometimes hangs waiting in blocking read ssl:send(Socket, "Got it"), ok; + {ssl, Socket, Byte} when length(Byte) == 1 -> + erlang_ssl_receive(Socket, tl(Data)); {Port, {data,Debug}} when is_port(Port) -> io:format("openssl ~s~n",[Debug]), erlang_ssl_receive(Socket,Data); @@ -1433,3 +1443,11 @@ check_sane_openssl_renegotaite(Config) -> _ -> Config end. + +check_sane_openssl_sslv2(Config) -> + case os:cmd("openssl version") of + "OpenSSL 1.0.0" ++ _ -> + {skip, "sslv2 by default turned of in 1.*"}; + _ -> + Config + end. diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 8286201df4..2255798f1d 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 4.1.6 +SSL_VSN = 5.0 |