diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ssl/src/ssl_certificate_db.erl | 28 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 10 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 10 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.hrl | 1 | ||||
-rw-r--r-- | lib/ssl/src/ssl_manager.erl | 86 | ||||
-rw-r--r-- | lib/ssl/src/ssl_session.erl | 30 | ||||
-rw-r--r-- | lib/ssl/test/ssl_basic_SUITE.erl | 197 | ||||
-rw-r--r-- | lib/ssl/test/ssl_test_lib.erl | 37 |
8 files changed, 339 insertions, 60 deletions
diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl index 2a5a7f3394..019f73fc80 100644 --- a/lib/ssl/src/ssl_certificate_db.erl +++ b/lib/ssl/src/ssl_certificate_db.erl @@ -27,7 +27,9 @@ -export([create/0, remove/1, add_trusted_certs/3, remove_trusted_certs/2, lookup_trusted_cert/3, issuer_candidate/1, - lookup_cached_certs/1, cache_pem_file/3]). + lookup_cached_certs/1, cache_pem_file/4, uncache_pem_file/2, ref_count/3]). + +-type time() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}. %%==================================================================== %% Internal application API @@ -98,17 +100,32 @@ add_trusted_certs(Pid, File, [CertsDb, FileToRefDb, PidToFileDb]) -> insert(Pid, File, PidToFileDb), {ok, Ref}. %%-------------------------------------------------------------------- --spec cache_pem_file(pid(), string(), certdb_ref()) -> term(). +-spec cache_pem_file(pid(), string(), time(), certdb_ref()) -> term(). %% %% Description: Cache file as binary in DB %%-------------------------------------------------------------------- -cache_pem_file(Pid, File, [CertsDb, _FileToRefDb, PidToFileDb]) -> +cache_pem_file(Pid, File, Time, [CertsDb, _FileToRefDb, PidToFileDb]) -> {ok, PemBin} = file:read_file(File), Content = public_key:pem_decode(PemBin), - insert({file, File}, Content, CertsDb), + insert({file, File}, {Time, Content}, CertsDb), insert(Pid, File, PidToFileDb), {ok, Content}. +%-------------------------------------------------------------------- +-spec uncache_pem_file(string(), certdb_ref()) -> no_return(). +%% +%% Description: If a cached file is no longer valid (changed on disk) +%% we must terminate the connections using the old file content, and +%% when those processes are finish the cache will be cleaned. It is +%% a rare but possible case a new ssl client/server is started with +%% a filename with the same name as previously started client/server +%% but with different content. +%% -------------------------------------------------------------------- +uncache_pem_file(File, [_CertsDb, _FileToRefDb, PidToFileDb]) -> + Pids = select(PidToFileDb, [{{'$1', File},[],['$$']}]), + lists:foreach(fun(Pid) -> + exit(Pid, shutdown) + end, Pids). %%-------------------------------------------------------------------- -spec remove_trusted_certs(pid(), certdb_ref()) -> term(). @@ -202,6 +219,9 @@ lookup(Key, Db) -> [Pick(Data) || Data <- Contents] end. +select(Db, MatchSpec)-> + ets:select(Db, MatchSpec). + remove_certs(Ref, CertsDb) -> ets:match_delete(CertsDb, {{Ref, '_', '_'}, '_'}). diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 6c9ac65b64..478f465705 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -70,7 +70,7 @@ %% {{md5_hash, sha_hash}, {prev_md5, prev_sha}} (binary()) tls_handshake_hashes, % see above tls_cipher_texts, % list() received but not deciphered yet - own_cert, % binary() + own_cert, % binary() | undefined session, % #session{} from ssl_handshake.hrl session_cache, % session_cache_cb, % @@ -304,8 +304,10 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, try ssl_init(SSLOpts0, Role) of {ok, Ref, CacheRef, OwnCert, Key, DHParams} -> + Session = State0#state.session, State = State0#state{tls_handshake_hashes = Hashes0, own_cert = OwnCert, + session = Session#session{own_certificate = OwnCert}, cert_db_ref = Ref, session_cache = CacheRef, private_key = Key, @@ -331,6 +333,7 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, %%-------------------------------------------------------------------- hello(start, #state{host = Host, port = Port, role = client, ssl_options = SslOpts, + own_cert = Cert, transport_cb = Transport, socket = Socket, connection_states = ConnectionStates, renegotiation = {Renegotiation, _}} @@ -338,7 +341,7 @@ hello(start, #state{host = Host, port = Port, role = client, Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, - SslOpts, Renegotiation), + SslOpts, Renegotiation, Cert), Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), @@ -678,6 +681,7 @@ cipher(Msg, State) -> %%-------------------------------------------------------------------- connection(#hello_request{}, #state{host = Host, port = Port, socket = Socket, + own_cert = Cert, ssl_options = SslOpts, negotiated_version = Version, transport_cb = Transport, @@ -686,7 +690,7 @@ connection(#hello_request{}, #state{host = Host, port = Port, tls_handshake_hashes = Hashes0} = State0) -> Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates0, - SslOpts, Renegotiation), + SslOpts, Renegotiation, Cert), {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index c7a1c4965d..125c28b373 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -30,7 +30,7 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/5, server_hello/4, hello/4, +-export([master_secret/4, client_hello/6, server_hello/4, hello/4, hello_request/0, certify/6, certificate/3, client_certificate_verify/5, certificate_verify/5, certificate_request/2, key_exchange/2, server_key_exchange_hash/2, @@ -49,13 +49,13 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(host(), port_num(), #connection_states{}, - #ssl_options{}, boolean()) -> #client_hello{}. + #ssl_options{}, boolean(), der_cert()) -> #client_hello{}. %% %% Description: Creates a client hello message. %%-------------------------------------------------------------------- client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, ciphers = UserSuites} - = SslOpts, Renegotiation) -> + = SslOpts, Renegotiation, OwnCert) -> Fun = fun(Version) -> ssl_record:protocol_version(Version) @@ -65,7 +65,7 @@ client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, SecParams = Pending#connection_state.security_parameters, Ciphers = available_suites(UserSuites, Version), - Id = ssl_manager:client_session_id(Host, Port, SslOpts), + Id = ssl_manager:client_session_id(Host, Port, SslOpts, OwnCert), #client_hello{session_id = Id, client_version = Version, @@ -571,7 +571,7 @@ select_session(Hello, Port, Session, Version, #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) -> SuggestedSessionId = Hello#client_hello.session_id, SessionId = ssl_manager:server_session_id(Port, SuggestedSessionId, - SslOpts), + SslOpts, Cert), Suites = available_suites(Cert, UserSuites, Version), case ssl_session:is_new(SuggestedSessionId, SessionId) of diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 68a7802ef2..8ae4d2332e 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -36,6 +36,7 @@ -record(session, { session_id, peer_certificate, + own_certificate, compression_method, cipher_suite, master_secret, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 3b02d96562..dc613eec11 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -29,13 +29,13 @@ %% Internal application API -export([start_link/1, connection_init/2, cache_pem_file/1, - lookup_trusted_cert/3, issuer_candidate/1, client_session_id/3, - server_session_id/3, + lookup_trusted_cert/3, issuer_candidate/1, client_session_id/4, + server_session_id/4, register_session/2, register_session/3, invalidate_session/2, invalidate_session/3]). % Spawn export --export([init_session_validator/1]). +-export([init_session_validator/1, recache_pem/4]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -43,6 +43,7 @@ -include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). +-include_lib("kernel/include/file.hrl"). -record(state, { session_cache, @@ -76,16 +77,17 @@ start_link(Opts) -> connection_init(Trustedcerts, Role) -> call({connection_init, Trustedcerts, Role}). %%-------------------------------------------------------------------- --spec cache_pem_file(string()) -> {ok, term()}. +-spec cache_pem_file(string()) -> {ok, term()} | {error, reason()}. %% -%% Description: Cach a pem file and +%% Description: Cach a pem file and return its content. %%-------------------------------------------------------------------- -cache_pem_file(File) -> - case ssl_certificate_db:lookup_cached_certs(File) of - [{_,Content}] -> - {ok, Content}; - [] -> - call({cache_pem, File}) +cache_pem_file(File) -> + try file:read_file_info(File) of + {ok, #file_info{mtime = LastWrite}} -> + cache_pem_file(File, LastWrite) + catch + _:Reason -> + {error, Reason} end. %%-------------------------------------------------------------------- -spec lookup_trusted_cert(reference(), serialnumber(), issuer()) -> @@ -106,20 +108,21 @@ lookup_trusted_cert(Ref, SerialNumber, Issuer) -> issuer_candidate(PrevCandidateKey) -> ssl_certificate_db:issuer_candidate(PrevCandidateKey). %%-------------------------------------------------------------------- --spec client_session_id(host(), port_num(), #ssl_options{}) -> session_id(). +-spec client_session_id(host(), port_num(), #ssl_options{}, + der_cert() | undefined) -> session_id(). %% %% Description: Select a session id for the client. %%-------------------------------------------------------------------- -client_session_id(Host, Port, SslOpts) -> - call({client_session_id, Host, Port, SslOpts}). +client_session_id(Host, Port, SslOpts, OwnCert) -> + call({client_session_id, Host, Port, SslOpts, OwnCert}). %%-------------------------------------------------------------------- --spec server_session_id(host(), port_num(), #ssl_options{}) -> session_id(). +-spec server_session_id(host(), port_num(), #ssl_options{}, der_cert()) -> session_id(). %% %% Description: Select a session id for the server. %%-------------------------------------------------------------------- -server_session_id(Port, SuggestedSessionId, SslOpts) -> - call({server_session_id, Port, SuggestedSessionId, SslOpts}). +server_session_id(Port, SuggestedSessionId, SslOpts, OwnCert) -> + call({server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}). %%-------------------------------------------------------------------- -spec register_session(port_num(), #session{}) -> ok. @@ -201,28 +204,35 @@ handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From, end, {reply, Result, State}; -handle_call({{client_session_id, Host, Port, SslOpts}, _}, _, +handle_call({{client_session_id, Host, Port, SslOpts, OwnCert}, _}, _, #state{session_cache = Cache, session_cache_cb = CacheCb} = State) -> - Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb), + Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), {reply, Id, State}; -handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts}, _}, +handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}, _}, _, #state{session_cache_cb = CacheCb, session_cache = Cache, session_lifetime = LifeTime} = State) -> Id = ssl_session:id(Port, SuggestedSessionId, SslOpts, - Cache, CacheCb, LifeTime), + Cache, CacheCb, LifeTime, OwnCert), {reply, Id, State}; -handle_call({{cache_pem, File},Pid}, _, State = #state{certificate_db = Db}) -> - try ssl_certificate_db:cache_pem_file(Pid,File,Db) of +handle_call({{cache_pem, File, LastWrite}, Pid}, _, + #state{certificate_db = Db} = State) -> + try ssl_certificate_db:cache_pem_file(Pid, File, LastWrite, Db) of Result -> {reply, Result, State} catch _:Reason -> {reply, {error, Reason}, State} - end. + end; +handle_call({{recache_pem, File, LastWrite}, Pid}, From, + #state{certificate_db = Db} = State) -> + ssl_certificate_db:uncache_pem_file(File, Pid, Db), + spawn_link(?MODULE, recache_pem, [File, Db, LastWrite, From]), + {noreply, State}. + %%-------------------------------------------------------------------- -spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. %% Possible return values not used now. @@ -286,12 +296,14 @@ handle_info({'EXIT', _, _}, State) -> handle_info({'DOWN', _Ref, _Type, _Pid, ecacertfile}, State) -> {noreply, State}; +handle_info({'DOWN', _Ref, _Type, Pid, shutdown}, State) -> + handle_info({remove_trusted_certs, Pid}, State); handle_info({'DOWN', _Ref, _Type, Pid, _Reason}, State) -> erlang:send_after(?CERTIFICATE_CACHE_CLEANUP, self(), {remove_trusted_certs, Pid}), {noreply, State}; handle_info({remove_trusted_certs, Pid}, - State = #state{certificate_db = Db}) -> + #state{certificate_db = Db} = State) -> ssl_certificate_db:remove_trusted_certs(Pid, Db), {noreply, State}; @@ -362,3 +374,27 @@ session_validation({{{Host, Port}, _}, Session}, LifeTime) -> session_validation({{Port, _}, Session}, LifeTime) -> validate_session(Port, Session, LifeTime), LifeTime. + +cache_pem_file(File, LastWrite) -> + case ssl_certificate_db:lookup_cached_certs(File) of + [{_, {Mtime, Content}}] -> + case LastWrite of + Mtime -> + {ok, Content}; + _ -> + call({recache_pem, File, LastWrite}) + end; + [] -> + call({cache_pem, File, LastWrite}) + end. + + +recache_pem(File, Db, LastWrite, From) -> + case ssl_certificate_db:ref_count(File, Db, 0) of + 0 -> + Result = call({cache_pem, File, LastWrite}), + gen_server:reply(From, Result); + _ -> + timer:sleep(1000), + recache_pem(File, Db, LastWrite, From) + end. diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index 25e7445180..dc4b7a711c 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -28,7 +28,7 @@ -include("ssl_internal.hrl"). %% Internal application API --export([is_new/2, id/3, id/6, valid_session/2]). +-export([is_new/2, id/4, id/7, valid_session/2]). -define(GEN_UNIQUE_ID_MAX_TRIES, 10). @@ -48,13 +48,14 @@ is_new(_ClientSuggestion, _ServerDecision) -> true. %%-------------------------------------------------------------------- --spec id({host(), port_num(), #ssl_options{}}, cache_ref(), atom()) -> binary(). +-spec id({host(), port_num(), #ssl_options{}}, cache_ref(), atom(), + undefined | binary()) -> binary(). %% %% Description: Should be called by the client side to get an id %% for the client hello message. %%-------------------------------------------------------------------- -id(ClientInfo, Cache, CacheCb) -> - case select_session(ClientInfo, Cache, CacheCb) of +id(ClientInfo, Cache, CacheCb, OwnCert) -> + case select_session(ClientInfo, Cache, CacheCb, OwnCert) of no_session -> <<>>; SessionId -> @@ -63,19 +64,19 @@ id(ClientInfo, Cache, CacheCb) -> %%-------------------------------------------------------------------- -spec id(port_num(), binary(), #ssl_options{}, cache_ref(), - atom(), seconds()) -> binary(). + atom(), seconds(), binary()) -> binary(). %% %% Description: Should be called by the server side to get an id %% for the server hello message. %%-------------------------------------------------------------------- -id(Port, <<>>, _, Cache, CacheCb, _) -> +id(Port, <<>>, _, Cache, CacheCb, _, _) -> new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb); id(Port, SuggestedSessionId, #ssl_options{reuse_sessions = ReuseEnabled, reuse_session = ReuseFun}, - Cache, CacheCb, SecondLifeTime) -> + Cache, CacheCb, SecondLifeTime, OwnCert) -> case is_resumable(SuggestedSessionId, Port, ReuseEnabled, - ReuseFun, Cache, CacheCb, SecondLifeTime) of + ReuseFun, Cache, CacheCb, SecondLifeTime, OwnCert) of true -> SuggestedSessionId; false -> @@ -93,19 +94,20 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -select_session({HostIP, Port, SslOpts}, Cache, CacheCb) -> +select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) -> Sessions = CacheCb:select_session(Cache, {HostIP, Port}), - select_session(Sessions, SslOpts). + select_session(Sessions, SslOpts, OwnCert). -select_session([], _) -> +select_session([], _, _) -> no_session; select_session(Sessions, #ssl_options{ciphers = Ciphers, - reuse_sessions = ReuseSession}) -> + reuse_sessions = ReuseSession}, OwnCert) -> IsResumable = fun(Session) -> ReuseSession andalso (Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) + andalso (OwnCert == Session#session.own_certificate) end, case [Id || [Id, Session] <- Sessions, IsResumable(Session)] of [] -> @@ -140,14 +142,16 @@ new_id(Port, Tries, Cache, CacheCb) -> end. is_resumable(SuggestedSessionId, Port, ReuseEnabled, ReuseFun, Cache, - CacheCb, SecondLifeTime) -> + CacheCb, SecondLifeTime, OwnCert) -> case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of #session{cipher_suite = CipherSuite, + own_certificate = SessionOwnCert, compression_method = Compression, is_resumable = Is_resumable, peer_certificate = PeerCert} = Session -> ReuseEnabled andalso Is_resumable + andalso (OwnCert == SessionOwnCert) andalso valid_session(Session, SecondLifeTime) andalso ReuseFun(SuggestedSessionId, PeerCert, Compression, CipherSuite); diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 8f9554f3ce..92de3fe070 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -125,6 +125,9 @@ init_per_testcase(empty_protocol_versions, Config) -> ssl:start(), Config; +init_per_testcase(different_ca_peer_sign, Config0) -> + ssl_test_lib:make_mix_cert(Config0); + init_per_testcase(_TestCase, Config0) -> Config = lists:keydelete(watchdog, 1, Config0), Dog = test_server:timetrap(?TIMEOUT), @@ -205,7 +208,9 @@ all(suite) -> invalid_signature_client, invalid_signature_server, cert_expired, client_with_cert_cipher_suites_handshake, unknown_server_ca_fail, der_input, unknown_server_ca_accept_verify_none, unknown_server_ca_accept_verify_peer, - unknown_server_ca_accept_backwardscompatibilty + unknown_server_ca_accept_backwardscompatibilty, + different_ca_peer_sign, no_reuses_session_server_restart_new_cert, + no_reuses_session_server_restart_new_cert_file ]. %% Test cases starts here. @@ -3052,6 +3057,196 @@ der_input_opts(Opts) -> {Cert, {rsa, Key}, CaCerts, DHParams}. %%-------------------------------------------------------------------- +different_ca_peer_sign(doc) -> + ["Check that a CA can have a different signature algorithm than the peer cert."]; + +different_ca_peer_sign(suite) -> + []; + +different_ca_peer_sign(Config) when is_list(Config) -> + ClientOpts = ?config(client_mix_opts, Config), + ServerOpts = ?config(server_mix_verify_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_once, []}}, + {options, [{active, once}, + {verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + send_recv_result_active_once, + []}}, + {options, [{active, once}, + {verify, verify_peer} + | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +no_reuses_session_server_restart_new_cert(doc) -> + ["Check that a session is not reused if the server is restarted with a new cert."]; + +no_reuses_session_server_restart_new_cert(suite) -> + []; + +no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> + + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + DsaServerOpts = ?config(server_dsa_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, session_info_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! listen, + + %% Make sure session is registered + test_server:sleep(?SLEEP), + ssl_test_lib:close(Server), + %% Make sure port is free + test_server:sleep(?SLEEP * 3), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {?MODULE, session_info_result, []}}, + {options, DsaServerOpts}]), + receive + {Server1, _} -> + ok + end, + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {?MODULE, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + test_server:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + process_flag(trap_exit, false). + +%%-------------------------------------------------------------------- +no_reuses_session_server_restart_new_cert_file(doc) -> + ["Check that a session is not reused if a server is restarted with a new " + "cert contained in a file with the same name as the old cert."]; + +no_reuses_session_server_restart_new_cert_file(suite) -> + []; + +no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + DsaServerOpts = ?config(server_dsa_opts, Config), + PrivDir = ?config(priv_dir, Config), + + NewServerOpts = new_config(PrivDir, ServerOpts), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, session_info_result, []}}, + {options, NewServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! listen, + + %% Make sure session is registered + test_server:sleep(?SLEEP), + ssl_test_lib:close(Server), + %% Make sure port is free + test_server:sleep(?SLEEP), + + NewServerOpts = new_config(PrivDir, DsaServerOpts), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {?MODULE, session_info_result, []}}, + {options, NewServerOpts}]), + + receive + {Server1, _} -> + ok + end, + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {?MODULE, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + test_server:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + process_flag(trap_exit, false). + +new_config(PrivDir, ServerOpts0) -> + CaCertFile = proplists:get_value(cacertfile, ServerOpts0), + CertFile = proplists:get_value(certfile, ServerOpts0), + KeyFile = proplists:get_value(keyfile, ServerOpts0), + NewCaCertFile = filename:join(PrivDir, "new_ca.pem"), + NewCertFile = filename:join(PrivDir, "new_cert.pem"), + NewKeyFile = filename:join(PrivDir, "new_key.pem"), + file:copy(CaCertFile, NewCaCertFile), + file:copy(CertFile, NewCertFile), + file:copy(KeyFile, NewKeyFile), + ServerOpts1 = proplists:delete(cacertfile, ServerOpts0), + ServerOpts2 = proplists:delete(certfile, ServerOpts1), + ServerOpts = proplists:delete(keyfile, ServerOpts2), + [{cacertfile, NewCaCertFile}, {certfile, NewCertFile}, + {keyfile, NewKeyFile} | ServerOpts]. + + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- erlang_ssl_receive(Socket, Data) -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index e1e8214ed6..bcefedf453 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -327,8 +327,8 @@ cert_options(Config) -> make_dsa_cert(Config) -> - {ServerCaCertFile, ServerCertFile, ServerKeyFile} = make_dsa_cert_files("server", Config), - {ClientCaCertFile, ClientCertFile, ClientKeyFile} = make_dsa_cert_files("client", Config), + {ServerCaCertFile, ServerCertFile, ServerKeyFile} = make_cert_files("server", Config, dsa, dsa, ""), + {ClientCaCertFile, ClientCertFile, ClientKeyFile} = make_cert_files("client", Config, dsa, dsa, ""), [{server_dsa_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, @@ -342,22 +342,41 @@ make_dsa_cert(Config) -> | Config]. - -make_dsa_cert_files(RoleStr, Config) -> - CaInfo = {CaCert, _} = erl_make_certs:make_cert([{key, dsa}]), - {Cert, CertKey} = erl_make_certs:make_cert([{key, dsa}, {issuer, CaInfo}]), +make_mix_cert(Config) -> + {ServerCaCertFile, ServerCertFile, ServerKeyFile} = make_cert_files("server", Config, dsa, + rsa, "mix"), + {ClientCaCertFile, ClientCertFile, ClientKeyFile} = make_cert_files("client", Config, dsa, + rsa, "mix"), + [{server_mix_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ServerCaCertFile}, + {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, + {server_mix_verify_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ClientCaCertFile}, + {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, + {verify, verify_peer}]}, + {client_mix_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ClientCaCertFile}, + {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]} + | Config]. + +make_cert_files(RoleStr, Config, Alg1, Alg2, Prefix) -> + Alg1Str = atom_to_list(Alg1), + Alg2Str = atom_to_list(Alg2), + CaInfo = {CaCert, _} = erl_make_certs:make_cert([{key, Alg1}]), + {Cert, CertKey} = erl_make_certs:make_cert([{key, Alg2}, {issuer, CaInfo}]), CaCertFile = filename:join([?config(priv_dir, Config), - RoleStr, "dsa_cacerts.pem"]), + RoleStr, Prefix ++ Alg1Str ++ "_cacerts.pem"]), CertFile = filename:join([?config(priv_dir, Config), - RoleStr, "dsa_cert.pem"]), + RoleStr, Alg2Str ++ "_cert.pem"]), KeyFile = filename:join([?config(priv_dir, Config), - RoleStr, "dsa_key.pem"]), + RoleStr, Prefix ++ Alg2Str ++ "_key.pem"]), der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]), der_to_pem(CertFile, [{'Certificate', Cert, not_encrypted}]), der_to_pem(KeyFile, [CertKey]), {CaCertFile, CertFile, KeyFile}. + start_upgrade_server(Args) -> Result = spawn_link(?MODULE, run_upgrade_server, [Args]), receive |