From f838670414d60c556cf436eaa9fe3a583d716e23 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 8 May 2012 11:05:16 +0200 Subject: ssl: Avoid supervior bottleneck This is done by using proc_lib and gen_fsm:enter_loop so that supervisor will not have to wait for the relative long initialization of an ssl connection process before starting another connection process. --- lib/ssl/src/ssl_connection.erl | 49 ++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 23 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 6c06baff98..df0af45a3b 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -47,8 +47,8 @@ -export([start_link/7]). %% gen_fsm callbacks --export([init/1, hello/2, certify/2, cipher/2, connection/2, - abbreviated/2, handle_event/3, +-export([init/1, hello/2, certify/2, cipher/2, + abbreviated/2, connection/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -record(state, { @@ -297,19 +297,10 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> %% does not return until Module:init/1 has returned. %%-------------------------------------------------------------------- start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - gen_fsm:start_link(?MODULE, [Role, Host, Port, Socket, Options, - User, CbInfo], []). + proc_lib:start_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]]). -%%==================================================================== -%% gen_fsm callbacks -%%==================================================================== -%%-------------------------------------------------------------------- -%% 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. -%%-------------------------------------------------------------------- -init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, - User, CbInfo]) -> +init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> + proc_lib:init_ack({ok, self()}), State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), Hashes0 = ssl_handshake:init_hashes(), TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), @@ -324,10 +315,10 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, session_cache = CacheHandle, private_key = Key, diffie_hellman_params = DHParams}, - {ok, hello, State, get_timeout(State)} - catch + gen_fsm:enter_loop(?MODULE, [], hello, State, get_timeout(State)) + catch throw:Error -> - {stop, Error} + gen_fsm:enter_loop(?MODULE, [], error, {Error,State0}, get_timeout(State0)) end. %%-------------------------------------------------------------------- @@ -337,6 +328,14 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, %% same name as the current state name StateName is called to handle %% the event. It is also called if a timeout occurs. %% +%%-------------------------------------------------------------------- +%% 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 +%% same name as the current state name StateName is called to handle +%% the event. It is also called if a timeout occurs. +%% + %%-------------------------------------------------------------------- -spec hello(start | #hello_request{} | #client_hello{} | #server_hello{} | term(), #state{}) -> gen_fsm_state_return(). @@ -344,12 +343,12 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, hello(start, #state{host = Host, port = Port, role = client, ssl_options = SslOpts, session = #session{own_certificate = Cert} = Session0, + session_cache = Cache, session_cache_cb = CacheCb, transport_cb = Transport, socket = Socket, connection_states = ConnectionStates, renegotiation = {Renegotiation, _}} = State0) -> - Hello = ssl_handshake:client_hello(Host, Port, - ConnectionStates, - SslOpts, Renegotiation, Cert), + Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, + Renegotiation, Cert), Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), @@ -357,7 +356,7 @@ hello(start, #state{host = Host, port = Port, role = client, encode_handshake(Hello, Version, ConnectionStates, Hashes0), Transport:send(Socket, BinMsg), State1 = State0#state{connection_states = CS2, - negotiated_version = Version, %% Requested version + negotiated_version = Version, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}, tls_handshake_hashes = Hashes1}, @@ -692,14 +691,15 @@ cipher(Msg, State) -> connection(#hello_request{}, #state{host = Host, port = Port, socket = Socket, session = #session{own_certificate = Cert}, + session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts, negotiated_version = Version, transport_cb = Transport, connection_states = ConnectionStates0, renegotiation = {Renegotiation, _}, tls_handshake_hashes = Hashes0} = State0) -> - Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates0, - SslOpts, Renegotiation, Cert), + Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Renegotiation, Cert), {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), @@ -732,6 +732,7 @@ connection(timeout, State) -> connection(Msg, State) -> handle_unexpected_message(Msg, connection, State). + %%-------------------------------------------------------------------- %% Description: Whenever a gen_fsm receives an event sent using %% gen_fsm:send_all_state_event/2, this function is called to handle @@ -774,6 +775,8 @@ handle_sync_event(start, From, hello, State) -> %% they upgrade a active socket. handle_sync_event(start, _, connection, State) -> {reply, connected, connection, State, get_timeout(State)}; +handle_sync_event(start, _From, error, {Error, State = #state{}}) -> + {stop, {shutdown, Error}, {error, Error}, State}; handle_sync_event(start, From, StateName, State) -> {next_state, StateName, State#state{from = From}, get_timeout(State)}; -- cgit v1.2.3 From a0981bf885a99cce1dee1775378d56eb661eec27 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 8 May 2012 14:11:55 +0200 Subject: ssl: Reuse session check optimization --- lib/ssl/src/ssl_session.erl | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index df5d7e0146..b10263a5f2 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.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 @@ -94,26 +94,24 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +select_session({_, _, #ssl_options{reuse_sessions=false}}, _Cache, _CacheCb, _OwnCert) -> + no_session; select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) -> Sessions = CacheCb:select_session(Cache, {HostIP, Port}), select_session(Sessions, SslOpts, OwnCert). select_session([], _, _) -> no_session; - -select_session(Sessions, #ssl_options{ciphers = Ciphers, - reuse_sessions = ReuseSession}, OwnCert) -> - IsResumable = - fun(Session) -> - ReuseSession andalso resumable(Session#session.is_resumable) andalso - lists:member(Session#session.cipher_suite, Ciphers) - andalso (OwnCert == Session#session.own_certificate) +select_session(Sessions, #ssl_options{ciphers = Ciphers}, OwnCert) -> + IsNotResumable = + fun([_Id, Session]) -> + not (resumable(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 - [] -> - no_session; - List -> - hd(List) + case lists:dropwhile(IsNotResumable, Sessions) of + [] -> no_session; + [[Id, _]|_] -> Id end. %% If we can not generate a not allready in use session ID in @@ -141,7 +139,9 @@ new_id(Port, Tries, Cache, CacheCb) -> new_id(Port, Tries - 1, Cache, CacheCb) end. -is_resumable(SuggestedSessionId, Port, ReuseEnabled, ReuseFun, Cache, +is_resumable(_, _, false, _, _, _, _, _) -> + false; +is_resumable(SuggestedSessionId, Port, true, ReuseFun, Cache, CacheCb, SecondLifeTime, OwnCert) -> case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of #session{cipher_suite = CipherSuite, @@ -149,11 +149,10 @@ is_resumable(SuggestedSessionId, Port, ReuseEnabled, ReuseFun, Cache, compression_method = Compression, is_resumable = IsResumable, peer_certificate = PeerCert} = Session -> - ReuseEnabled - andalso resumable(IsResumable) + resumable(IsResumable) andalso (OwnCert == SessionOwnCert) - andalso valid_session(Session, SecondLifeTime) - andalso ReuseFun(SuggestedSessionId, PeerCert, + andalso valid_session(Session, SecondLifeTime) + andalso ReuseFun(SuggestedSessionId, PeerCert, Compression, CipherSuite); undefined -> false -- cgit v1.2.3 From c7cdcb85f8116dda47c2960df1c8a3f3caf56d64 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 8 May 2012 15:36:21 +0200 Subject: ssl: Move and avoid ets:select bottleneck in client Do not use ssl_manager process for selecting an id. It's unnecessary to involve the manager process at all on the client side. --- lib/ssl/src/ssl_connection.erl | 6 +++--- lib/ssl/src/ssl_handshake.erl | 23 +++++++++++++---------- lib/ssl/src/ssl_manager.erl | 19 ++----------------- 3 files changed, 18 insertions(+), 30 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index df0af45a3b..431e6a3eaf 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -348,7 +348,7 @@ hello(start, #state{host = Host, port = Port, role = client, connection_states = ConnectionStates, renegotiation = {Renegotiation, _}} = State0) -> Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, - Renegotiation, Cert), + Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), @@ -393,7 +393,7 @@ hello(#server_hello{cipher_suite = CipherSuite, case ssl_session:is_new(OldId, NewId) of true -> - handle_new_session(NewId, CipherSuite, Compression, State); + handle_new_session(NewId, CipherSuite, Compression, State#state{connection_states = ConnectionStates}); false -> handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) end; @@ -699,7 +699,7 @@ connection(#hello_request{}, #state{host = Host, port = Port, renegotiation = {Renegotiation, _}, tls_handshake_hashes = Hashes0} = State0) -> Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Renegotiation, Cert), + Cache, CacheCb, 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 2e0a3de182..baeceb9bba 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/6, server_hello/4, hello/4, +-export([master_secret/4, client_hello/8, server_hello/4, hello/4, hello_request/0, certify/7, certificate/4, client_certificate_verify/5, certificate_verify/5, certificate_request/3, key_exchange/2, server_key_exchange_hash/2, @@ -51,14 +51,17 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(host(), inet:port_number(), #connection_states{}, - #ssl_options{}, boolean(), der_cert()) -> #client_hello{}. + #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> + #client_hello{}. %% %% Description: Creates a client hello message. %%-------------------------------------------------------------------- -client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, - ciphers = UserSuites} - = SslOpts, Renegotiation, OwnCert) -> - +client_hello(Host, Port, ConnectionStates, + #ssl_options{versions = Versions, + ciphers = UserSuites + } = SslOpts, + Cache, CacheCb, Renegotiation, OwnCert) -> + Fun = fun(Version) -> ssl_record:protocol_version(Version) end, @@ -67,15 +70,15 @@ 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, OwnCert), + Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), - #client_hello{session_id = Id, + #client_hello{session_id = Id, client_version = Version, cipher_suites = cipher_suites(Ciphers, Renegotiation), compression_methods = ssl_record:compressions(), random = SecParams#security_parameters.client_random, - renegotiation_info = - renegotiation_info(client, ConnectionStates, Renegotiation) + renegotiation_info = + renegotiation_info(client, ConnectionStates, Renegotiation) }. %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 6389ff03f5..6d0d010e10 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.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 @@ -30,7 +30,7 @@ -export([start_link/1, start_link_dist/1, connection_init/2, cache_pem_file/2, lookup_trusted_cert/4, - client_session_id/4, server_session_id/4, + server_session_id/4, register_session/2, register_session/3, invalidate_session/2, invalidate_session/3]). @@ -113,15 +113,6 @@ cache_pem_file(File, DbHandle) -> lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> ssl_certificate_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer). -%%-------------------------------------------------------------------- --spec client_session_id(host(), inet:port_number(), #ssl_options{}, - der_cert() | undefined) -> session_id(). -%% -%% Description: Select a session id for the client. -%%-------------------------------------------------------------------- -client_session_id(Host, Port, SslOpts, OwnCert) -> - call({client_session_id, Host, Port, SslOpts, OwnCert}). - %%-------------------------------------------------------------------- -spec server_session_id(host(), inet:port_number(), #ssl_options{}, der_cert()) -> session_id(). @@ -215,12 +206,6 @@ handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From, end, {reply, Result, State}; -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, OwnCert), - {reply, Id, State}; - handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}, _}, _, #state{session_cache_cb = CacheCb, session_cache = Cache, -- cgit v1.2.3 From 35ffd19df295f5ff73f9968b65dc8ad957c943e5 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Mon, 16 Apr 2012 11:56:57 +0200 Subject: ssl: Use ordered_set in cache So we can use partial bound keys for matching --- lib/ssl/src/ssl_session_cache.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl index f9bbf905e1..5c6ee3c54c 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_session_cache.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 @@ -32,7 +32,7 @@ %% Description: Return table reference. Called by ssl_manager process. %%-------------------------------------------------------------------- init(_) -> - ets:new(cache_name(), [set, protected]). + ets:new(cache_name(), [ordered_set, protected]). %%-------------------------------------------------------------------- %% Description: Handles cache table at termination of ssl manager. -- cgit v1.2.3 From e9a2612bb3b40e85b571f2b6b6962229b02434c2 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 9 May 2012 09:43:56 +0200 Subject: ssl: Renegotiate updates session id in gen_fsm state The session id keept in the connection processes state must be updated to be the id selected by ssl_handshake:client_hello, failing to do so will cause a crash if the session is not reused. --- lib/ssl/src/ssl_connection.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 431e6a3eaf..cadc7f4185 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -690,7 +690,7 @@ cipher(Msg, State) -> %%-------------------------------------------------------------------- connection(#hello_request{}, #state{host = Host, port = Port, socket = Socket, - session = #session{own_certificate = Cert}, + session = #session{own_certificate = Cert} = Session0, session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts, negotiated_version = Version, @@ -706,6 +706,7 @@ connection(#hello_request{}, #state{host = Host, port = Port, Transport:send(Socket, BinMsg), {Record, State} = next_record(State0#state{connection_states = ConnectionStates1, + session = Session0#session{session_id = Hello#client_hello.session_id}, tls_handshake_hashes = Hashes1}), next_state(connection, hello, Record, State); connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> -- cgit v1.2.3 From eaa2564532d6ac87fda2aa9a1d6bce0ac9d35829 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Fri, 20 Apr 2012 13:03:52 +0200 Subject: ssl: Move ets:select bottleneck in server Only use ssl_manager for selecting new ids to guarantee uniqueness, but reuse check does not need to be performed by the manager. --- lib/ssl/src/ssl_handshake.erl | 23 ++++++------ lib/ssl/src/ssl_manager.erl | 53 ++++++++++++++++++-------- lib/ssl/src/ssl_session.erl | 87 ++++++++++++++++--------------------------- 3 files changed, 80 insertions(+), 83 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index baeceb9bba..06d45966c1 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -70,7 +70,7 @@ client_hello(Host, Port, ConnectionStates, SecParams = Pending#connection_state.security_parameters, Ciphers = available_suites(UserSuites, Version), - Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), + Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, client_version = Version, @@ -587,24 +587,23 @@ path_validation_alert({bad_cert, unknown_ca}) -> path_validation_alert(_) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). -select_session(Hello, Port, Session, Version, +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, Cert), - - Suites = available_suites(Cert, UserSuites, Version), - case ssl_session:is_new(SuggestedSessionId, SessionId) of - true -> - CipherSuite = - select_cipher_suite(Hello#client_hello.cipher_suites, Suites), + {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId, + SslOpts, Cert, + Cache, CacheCb), + Suites = available_suites(Cert, UserSuites, Version), + case Resumed of + undefined -> + CipherSuite = select_cipher_suite(Hello#client_hello.cipher_suites, Suites), Compressions = Hello#client_hello.compression_methods, Compression = select_compression(Compressions), {new, Session#session{session_id = SessionId, cipher_suite = CipherSuite, compression_method = Compression}}; - false -> - {resumed, CacheCb:lookup(Cache, {Port, SessionId})} + _ -> + {resumed, Resumed} end. available_suites(UserSuites, Version) -> diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 6d0d010e10..7ee8f6e9d6 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -30,7 +30,7 @@ -export([start_link/1, start_link_dist/1, connection_init/2, cache_pem_file/2, lookup_trusted_cert/4, - server_session_id/4, + new_session_id/1, register_session/2, register_session/3, invalidate_session/2, invalidate_session/3]). @@ -56,6 +56,7 @@ -define('24H_in_msec', 8640000). -define('24H_in_sec', 8640). +-define(GEN_UNIQUE_ID_MAX_TRIES, 10). -define(SESSION_VALIDATION_INTERVAL, 60000). -define(CERTIFICATE_CACHE_CLEANUP, 30000). -define(CLEAN_SESSION_DB, 60000). @@ -95,12 +96,10 @@ connection_init(Trustedcerts, Role) -> %% Description: Cach a pem file and return its content. %%-------------------------------------------------------------------- cache_pem_file(File, DbHandle) -> - try file:read_file_info(File) of + case file:read_file_info(File) of {ok, #file_info{mtime = LastWrite}} -> - cache_pem_file(File, LastWrite, DbHandle) - catch - _:Reason -> - {error, Reason} + cache_pem_file(File, LastWrite, DbHandle); + Error -> Error end. %%-------------------------------------------------------------------- -spec lookup_trusted_cert(term(), reference(), serialnumber(), issuer()) -> @@ -114,13 +113,12 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> ssl_certificate_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer). %%-------------------------------------------------------------------- --spec server_session_id(host(), inet:port_number(), #ssl_options{}, - der_cert()) -> session_id(). +-spec new_session_id(integer()) -> session_id(). %% -%% Description: Select a session id for the server. +%% Description: Creates a session id for the server. %%-------------------------------------------------------------------- -server_session_id(Port, SuggestedSessionId, SslOpts, OwnCert) -> - call({server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}). +new_session_id(Port) -> + call({new_session_id, Port}). %%-------------------------------------------------------------------- -spec register_session(inet:port_number(), #session{}) -> ok. @@ -206,12 +204,10 @@ handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From, end, {reply, Result, State}; -handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}, _}, +handle_call({{new_session_id,Port}, _}, _, #state{session_cache_cb = CacheCb, - session_cache = Cache, - session_lifetime = LifeTime} = State) -> - Id = ssl_session:id(Port, SuggestedSessionId, SslOpts, - Cache, CacheCb, LifeTime, OwnCert), + session_cache = Cache} = State) -> + Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), {reply, Id, State}; handle_call({{cache_pem, File, LastWrite}, Pid}, _, @@ -433,3 +429,28 @@ last_delay_timer({{_,_},_}, TRef, {LastServer, _}) -> {LastServer, TRef}; last_delay_timer({_,_}, TRef, {_, LastClient}) -> {TRef, LastClient}. + +%% If we can not generate a not allready in use session ID in +%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The +%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which +%% states : "If we can not find a session id in +%% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone +%% is trying to open roughly very close to 2^128 (or 2^256) SSL +%% sessions to our server" +new_id(_, 0, _, _) -> + <<>>; +new_id(Port, Tries, Cache, CacheCb) -> + Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES), + case CacheCb:lookup(Cache, {Port, Id}) of + undefined -> + Now = calendar:datetime_to_gregorian_seconds({date(), time()}), + %% New sessions can not be set to resumable + %% until handshake is compleate and the + %% other session values are set. + CacheCb:update(Cache, {Port, Id}, #session{session_id = Id, + is_resumable = false, + time_stamp = Now}), + Id; + _ -> + new_id(Port, Tries - 1, Cache, CacheCb) + end. diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index b10263a5f2..2ad422fc03 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -28,9 +28,9 @@ -include("ssl_internal.hrl"). %% Internal application API --export([is_new/2, id/4, id/7, valid_session/2]). +-export([is_new/2, client_id/4, server_id/6, valid_session/2]). --define(GEN_UNIQUE_ID_MAX_TRIES, 10). +-define('24H_in_sec', 8640). -type seconds() :: integer(). @@ -48,13 +48,13 @@ is_new(_ClientSuggestion, _ServerDecision) -> true. %%-------------------------------------------------------------------- --spec id({host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(), +-spec client_id({host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(), undefined | binary()) -> binary(). %% -%% Description: Should be called by the client side to get an id +%% Description: Should be called by the client side to get an id %% for the client hello message. %%-------------------------------------------------------------------- -id(ClientInfo, Cache, CacheCb, OwnCert) -> +client_id(ClientInfo, Cache, CacheCb, OwnCert) -> case select_session(ClientInfo, Cache, CacheCb, OwnCert) of no_session -> <<>>; @@ -62,27 +62,6 @@ id(ClientInfo, Cache, CacheCb, OwnCert) -> SessionId end. -%%-------------------------------------------------------------------- --spec id(inet:port_number(), binary(), #ssl_options{}, db_handle(), - 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, _, _) -> - new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb); - -id(Port, SuggestedSessionId, #ssl_options{reuse_sessions = ReuseEnabled, - reuse_session = ReuseFun}, - Cache, CacheCb, SecondLifeTime, OwnCert) -> - case is_resumable(SuggestedSessionId, Port, ReuseEnabled, - ReuseFun, Cache, CacheCb, SecondLifeTime, OwnCert) of - true -> - SuggestedSessionId; - false -> - new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb) - end. -%%-------------------------------------------------------------------- -spec valid_session(#session{}, seconds()) -> boolean(). %% %% Description: Check that the session has not expired @@ -91,6 +70,25 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> Now = calendar:datetime_to_gregorian_seconds({date(), time()}), Now - TimeStamp < LifeTime. +server_id(Port, <<>>, _SslOpts, _Cert, _, _) -> + {ssl_manager:new_session_id(Port), undefined}; +server_id(Port, SuggestedId, + #ssl_options{reuse_sessions = ReuseEnabled, + reuse_session = ReuseFun}, + Cert, Cache, CacheCb) -> + LifeTime = case application:get_env(ssl, session_lifetime) of + {ok, Time} when is_integer(Time) -> Time; + _ -> ?'24H_in_sec' + end, + case is_resumable(SuggestedId, Port, ReuseEnabled,ReuseFun, + Cache, CacheCb, LifeTime, Cert) + of + {true, Resumed} -> + {SuggestedId, Resumed}; + {false, undefined} -> + {ssl_manager:new_session_id(Port), undefined} + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -114,33 +112,8 @@ select_session(Sessions, #ssl_options{ciphers = Ciphers}, OwnCert) -> [[Id, _]|_] -> Id end. -%% If we can not generate a not allready in use session ID in -%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The -%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which -%% states : "If we can not find a session id in -%% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone -%% is trying to open roughly very close to 2^128 (or 2^256) SSL -%% sessions to our server" -new_id(_, 0, _, _) -> - <<>>; -new_id(Port, Tries, Cache, CacheCb) -> - Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES), - case CacheCb:lookup(Cache, {Port, Id}) of - undefined -> - Now = calendar:datetime_to_gregorian_seconds({date(), time()}), - %% New sessions can not be set to resumable - %% until handshake is compleate and the - %% other session values are set. - CacheCb:update(Cache, {Port, Id}, #session{session_id = Id, - is_resumable = false, - time_stamp = Now}), - Id; - _ -> - new_id(Port, Tries - 1, Cache, CacheCb) - end. - is_resumable(_, _, false, _, _, _, _, _) -> - false; + {false, undefined}; is_resumable(SuggestedSessionId, Port, true, ReuseFun, Cache, CacheCb, SecondLifeTime, OwnCert) -> case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of @@ -149,13 +122,17 @@ is_resumable(SuggestedSessionId, Port, true, ReuseFun, Cache, compression_method = Compression, is_resumable = IsResumable, peer_certificate = PeerCert} = Session -> - resumable(IsResumable) + case resumable(IsResumable) andalso (OwnCert == SessionOwnCert) andalso valid_session(Session, SecondLifeTime) andalso ReuseFun(SuggestedSessionId, PeerCert, - Compression, CipherSuite); + Compression, CipherSuite) + of + true -> {true, Session}; + false -> {false, undefined} + end; undefined -> - false + {false, undefined} end. resumable(new) -> -- cgit v1.2.3 From 3dab268b2507e296eaae420086c9604397e37349 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Fri, 20 Apr 2012 13:05:17 +0200 Subject: ssl: Use md5 as file ref id instead of filenames Aviods storing a lot of data --- lib/ssl/src/ssl_certificate_db.erl | 38 ++++++++++++++++++++------------------ lib/ssl/src/ssl_connection.erl | 9 +-------- 2 files changed, 21 insertions(+), 26 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl index cb2473576a..ed6e94d445 100644 --- a/lib/ssl/src/ssl_certificate_db.erl +++ b/lib/ssl/src/ssl_certificate_db.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 @@ -73,7 +73,7 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> end. lookup_cached_certs(DbHandle, File) -> - ets:lookup(DbHandle, {file, File}). + ets:lookup(DbHandle, {file, crypto:md5(File)}). %%-------------------------------------------------------------------- -spec add_trusted_certs(pid(), string() | {der, list()}, [db_handle()]) -> {ok, [db_handle()]}. @@ -87,17 +87,18 @@ add_trusted_certs(_Pid, {der, DerList}, [CerDb, _,_]) -> add_certs_from_der(DerList, NewRef, CerDb), {ok, NewRef}; add_trusted_certs(Pid, File, [CertsDb, FileToRefDb, PidToFileDb]) -> - Ref = case lookup(File, FileToRefDb) of + MD5 = crypto:md5(File), + Ref = case lookup(MD5, FileToRefDb) of undefined -> NewRef = make_ref(), add_certs_from_file(File, NewRef, CertsDb), - insert(File, NewRef, 1, FileToRefDb), + insert(MD5, NewRef, 1, FileToRefDb), NewRef; [OldRef] -> - ref_count(File,FileToRefDb,1), + ref_count(MD5,FileToRefDb,1), OldRef end, - insert(Pid, File, PidToFileDb), + insert(Pid, MD5, PidToFileDb), {ok, Ref}. %%-------------------------------------------------------------------- -spec cache_pem_file(pid(), string(), time(), [db_handle()]) -> term(). @@ -107,8 +108,9 @@ add_trusted_certs(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}, {Time, Content}, CertsDb), - insert(Pid, File, PidToFileDb), + MD5 = crypto:md5(File), + insert({file, MD5}, {Time, Content}, CertsDb), + insert(Pid, MD5, PidToFileDb), {ok, Content}. %-------------------------------------------------------------------- @@ -122,7 +124,7 @@ cache_pem_file(Pid, File, Time, [CertsDb, _FileToRefDb, PidToFileDb]) -> %% but with different content. %% -------------------------------------------------------------------- uncache_pem_file(File, [_CertsDb, _FileToRefDb, PidToFileDb]) -> - Pids = select(PidToFileDb, [{{'$1', File},[],['$$']}]), + Pids = select(PidToFileDb, [{{'$1', crypto:md5(File)},[],['$$']}]), lists:foreach(fun([Pid]) -> exit(Pid, shutdown) end, Pids). @@ -135,26 +137,26 @@ uncache_pem_file(File, [_CertsDb, _FileToRefDb, PidToFileDb]) -> %% the file associated to Pid from the runtime database. %%-------------------------------------------------------------------- remove_trusted_certs(Pid, [CertsDb, FileToRefDb, PidToFileDb]) -> - Files = lookup(Pid, PidToFileDb), + FileMD5s = lookup(Pid, PidToFileDb), delete(Pid, PidToFileDb), - Clear = fun(File) -> - delete({file,File}, CertsDb), + Clear = fun(MD5) -> + delete({file,MD5}, CertsDb), try - 0 = ref_count(File, FileToRefDb, -1), - case lookup(File, FileToRefDb) of + 0 = ref_count(MD5, FileToRefDb, -1), + case lookup(MD5, FileToRefDb) of [Ref] when is_reference(Ref) -> remove_certs(Ref, CertsDb); _ -> ok end, - delete(File, FileToRefDb) + delete(MD5, FileToRefDb) catch _:_ -> ok end end, - case Files of + case FileMD5s of undefined -> ok; - _ -> - [Clear(File) || File <- Files], + _ -> + [Clear(FileMD5) || FileMD5 <- FileMD5s], ok end. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index cadc7f4185..3541c9371a 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -320,14 +320,7 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> throw:Error -> gen_fsm:enter_loop(?MODULE, [], error, {Error,State0}, get_timeout(State0)) end. - -%%-------------------------------------------------------------------- -%% 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 -%% same name as the current state name StateName is called to handle -%% the event. It is also called if a timeout occurs. -%% + %%-------------------------------------------------------------------- %% Description:There should be one instance of this function for each %% possible state name. Whenever a gen_fsm receives an event sent -- cgit v1.2.3 From b334632a0fb241d60cfe6c69fb0800047cd598ff Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 11 May 2012 11:27:40 +0200 Subject: ssl: Refactored for readability Instance of state variable that are "updated" in a function is called for example State0 and the last instance, that should be returned, is called State possible intermidiat versions are suffixed by increasing numbers. State0 may be rturned in error cases. Avoid nesting case statments. --- lib/ssl/src/ssl_connection.erl | 147 +++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 70 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 3541c9371a..38dad1c381 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -338,21 +338,21 @@ hello(start, #state{host = Host, port = Port, role = client, session = #session{own_certificate = Cert} = Session0, session_cache = Cache, session_cache_cb = CacheCb, transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates, + connection_states = ConnectionStates0, renegotiation = {Renegotiation, _}} = State0) -> - Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, + Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), - {BinMsg, CS2, Hashes1} = - encode_handshake(Hello, Version, ConnectionStates, Hashes0), + {BinMsg, ConnectionStates, Hashes} = + encode_handshake(Hello, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), - State1 = State0#state{connection_states = CS2, - negotiated_version = Version, %% Requested version + State1 = State0#state{connection_states = ConnectionStates, + negotiated_version = Version, %% Requested version at this point session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_hashes = Hashes1}, + tls_handshake_hashes = Hashes}, {Record, State} = next_record(State1), next_state(hello, hello, Record, State); @@ -386,7 +386,8 @@ hello(#server_hello{cipher_suite = CipherSuite, case ssl_session:is_new(OldId, NewId) of true -> - handle_new_session(NewId, CipherSuite, Compression, State#state{connection_states = ConnectionStates}); + handle_new_session(NewId, CipherSuite, Compression, + State#state{connection_states = ConnectionStates}); false -> handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) end; @@ -544,8 +545,8 @@ certify(#server_hello_done{}, role = client} = State0) -> case ssl_handshake:master_secret(Version, Session, ConnectionStates0, client) of - {MasterSecret, ConnectionStates1} -> - State = State0#state{connection_states = ConnectionStates1}, + {MasterSecret, ConnectionStates} -> + State = State0#state{connection_states = ConnectionStates}, client_certify_and_key_exchange(State); #alert{} = Alert -> handle_own_alert(Alert, Version, certify, State0), @@ -561,10 +562,10 @@ certify(#server_hello_done{}, role = client} = State0) -> case ssl_handshake:master_secret(Version, PremasterSecret, ConnectionStates0, client) of - {MasterSecret, ConnectionStates1} -> + {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates1, - session = Session}, + State = State0#state{connection_states = ConnectionStates, + session = Session}, client_certify_and_key_exchange(State); #alert{} = Alert -> handle_own_alert(Alert, Version, certify, State0), @@ -694,13 +695,13 @@ connection(#hello_request{}, #state{host = Host, port = Port, Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), - {BinMsg, ConnectionStates1, Hashes1} = + {BinMsg, ConnectionStates, Hashes} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), {Record, State} = next_record(State0#state{connection_states = - ConnectionStates1, + ConnectionStates, session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_hashes = Hashes1}), + tls_handshake_hashes = Hashes}), next_state(connection, hello, Record, State); connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> %% Mitigate Computational DoS attack @@ -1222,11 +1223,11 @@ certify_client(#state{client_certificate_requested = true, role = client, socket = Socket, tls_handshake_hashes = Hashes0} = State) -> Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), - {BinCert, ConnectionStates1, Hashes1} = + {BinCert, ConnectionStates, Hashes} = encode_handshake(Certificate, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinCert), - State#state{connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}; + State#state{connection_states = ConnectionStates, + tls_handshake_hashes = Hashes}; certify_client(#state{client_certificate_requested = false} = State) -> State. @@ -1243,12 +1244,12 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, Version, PrivateKey, Hashes0) of #certificate_verify{} = Verified -> - {BinVerified, ConnectionStates1, Hashes1} = + {BinVerified, ConnectionStates, Hashes} = encode_handshake(Verified, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinVerified), - State#state{connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}; + State#state{connection_states = ConnectionStates, + tls_handshake_hashes = Hashes}; ignore -> State; #alert{} = Alert -> @@ -1266,29 +1267,13 @@ do_server_hello(Type, #state{negotiated_version = Version, ServerHello = ssl_handshake:server_hello(SessId, Version, ConnectionStates0, Renegotiation), - State1 = server_hello(ServerHello, State0), + State = server_hello(ServerHello, State0), case Type of new -> - new_server_hello(ServerHello, State1); + new_server_hello(ServerHello, State); resumed -> - ConnectionStates1 = State1#state.connection_states, - case ssl_handshake:master_secret(Version, Session, - ConnectionStates1, server) of - {_, ConnectionStates2} -> - State2 = State1#state{connection_states=ConnectionStates2, - session = Session}, - {ConnectionStates, Hashes} = - finalize_handshake(State2, abbreviated), - State3 = State2#state{connection_states = - ConnectionStates, - tls_handshake_hashes = Hashes}, - {Record, State} = next_record(State3), - next_state(hello, abbreviated, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State1), - {stop, normal, State1} - end + resumed_server_hello(State) end. new_server_hello(#server_hello{cipher_suite = CipherSuite, @@ -1311,6 +1296,27 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, {stop, normal, State0} end. +resumed_server_hello(#state{session = Session, + connection_states = ConnectionStates0, + negotiated_version = Version} = State0) -> + + case ssl_handshake:master_secret(Version, Session, + ConnectionStates0, server) of + {_, ConnectionStates1} -> + State1 = State0#state{connection_states = ConnectionStates1, + session = Session}, + {ConnectionStates, Hashes} = + finalize_handshake(State1, abbreviated), + State2 = State1#state{connection_states = + ConnectionStates, + tls_handshake_hashes = Hashes}, + {Record, State} = next_record(State2), + next_state(hello, abbreviated, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, hello, State0), + {stop, normal, State0} + end. + handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0} = State0) -> Session = Session0#session{session_id = NewId, cipher_suite = CipherSuite, @@ -1326,10 +1332,10 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), case ssl_handshake:master_secret(Version, Session, ConnectionStates0, client) of - {_, ConnectionStates1} -> + {_, ConnectionStates} -> {Record, State} = next_record(State0#state{ - connection_states = ConnectionStates1, + connection_states = ConnectionStates, session = Session}), next_state(hello, abbreviated, Record, State); #alert{} = Alert -> @@ -1382,16 +1388,16 @@ server_hello(ServerHello, #state{transport_cb = Transport, server_hello_done(#state{transport_cb = Transport, socket = Socket, negotiated_version = Version, - connection_states = ConnectionStates, - tls_handshake_hashes = Hashes} = State) -> + connection_states = ConnectionStates0, + tls_handshake_hashes = Hashes0} = State) -> HelloDone = ssl_handshake:server_hello_done(), - {BinHelloDone, NewConnectionStates, NewHashes} = - encode_handshake(HelloDone, Version, ConnectionStates, Hashes), + {BinHelloDone, ConnectionStates, Hashes} = + encode_handshake(HelloDone, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinHelloDone), - State#state{connection_states = NewConnectionStates, - tls_handshake_hashes = NewHashes}. + State#state{connection_states = ConnectionStates, + tls_handshake_hashes = Hashes}. certify_server(#state{key_algorithm = dh_anon} = State) -> State; @@ -1399,18 +1405,18 @@ certify_server(#state{key_algorithm = dh_anon} = State) -> certify_server(#state{transport_cb = Transport, socket = Socket, negotiated_version = Version, - connection_states = ConnectionStates, - tls_handshake_hashes = Hashes, + connection_states = ConnectionStates0, + tls_handshake_hashes = Hashes0, cert_db = CertDbHandle, cert_db_ref = CertDbRef, session = #session{own_certificate = OwnCert}} = State) -> case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of CertMsg = #certificate{} -> - {BinCertMsg, NewConnectionStates, NewHashes} = - encode_handshake(CertMsg, Version, ConnectionStates, Hashes), + {BinCertMsg, ConnectionStates, Hashes} = + encode_handshake(CertMsg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinCertMsg), - State#state{connection_states = NewConnectionStates, - tls_handshake_hashes = NewHashes + State#state{connection_states = ConnectionStates, + tls_handshake_hashes = Hashes }; Alert = #alert{} -> throw(Alert) @@ -1440,12 +1446,12 @@ key_exchange(#state{role = server, key_algorithm = Algo, Algo, ClientRandom, ServerRandom, PrivateKey}), - {BinMsg, ConnectionStates, Hashes1} = + {BinMsg, ConnectionStates, Hashes} = encode_handshake(Msg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), State#state{connection_states = ConnectionStates, diffie_hellman_keys = Keys, - tls_handshake_hashes = Hashes1}; + tls_handshake_hashes = Hashes}; key_exchange(#state{role = client, connection_states = ConnectionStates0, @@ -1456,11 +1462,11 @@ key_exchange(#state{role = client, socket = Socket, transport_cb = Transport, tls_handshake_hashes = Hashes0} = State) -> Msg = rsa_key_exchange(PremasterSecret, PublicKeyInfo), - {BinMsg, ConnectionStates1, Hashes1} = + {BinMsg, ConnectionStates, Hashes} = encode_handshake(Msg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}; + State#state{connection_states = ConnectionStates, + tls_handshake_hashes = Hashes}; key_exchange(#state{role = client, connection_states = ConnectionStates0, key_algorithm = Algorithm, @@ -1472,11 +1478,11 @@ key_exchange(#state{role = client, Algorithm == dhe_rsa; Algorithm == dh_anon -> Msg = ssl_handshake:key_exchange(client, {dh, DhPubKey}), - {BinMsg, ConnectionStates1, Hashes1} = + {BinMsg, ConnectionStates, Hashes} = encode_handshake(Msg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}. + State#state{connection_states = ConnectionStates, + tls_handshake_hashes = Hashes}. rsa_key_exchange(PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) when Algorithm == ?rsaEncryption; @@ -1498,12 +1504,12 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, socket = Socket, transport_cb = Transport} = State) -> Msg = ssl_handshake:certificate_request(ConnectionStates0, CertDbHandle, CertDbRef), - {BinMsg, ConnectionStates1, Hashes1} = + {BinMsg, ConnectionStates, Hashes} = encode_handshake(Msg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), State#state{client_certificate_requested = true, - connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}; + connection_states = ConnectionStates, + tls_handshake_hashes = Hashes}; request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} = State) -> State. @@ -1840,8 +1846,9 @@ format_reply(binary, _, N, Data) when N > 0 -> % Header mode format_reply(binary, _, _, Data) -> Data; format_reply(list, Packet, _, Data) - when Packet == http; Packet == {http, headers}; Packet == http_bin; Packet == {http_bin, headers}; Packet == httph; - Packet == httph_bin-> + when Packet == http; Packet == {http, headers}; + Packet == http_bin; Packet == {http_bin, headers}; + Packet == httph; Packet == httph_bin -> Data; format_reply(list, _,_, Data) -> binary_to_list(Data). @@ -2356,7 +2363,7 @@ linux_workaround_transport_delivery_problems(#alert{level = ?FATAL}, Socket) -> linux_workaround_transport_delivery_problems(_, _) -> ok. -get_timeout(#state{ssl_options=#ssl_options{hibernate_after=undefined}}) -> +get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> infinity; -get_timeout(#state{ssl_options=#ssl_options{hibernate_after=HibernateAfter}}) -> +get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> HibernateAfter. -- cgit v1.2.3 From 68a055e261d70d3053694efddc1e5a26bf3553f1 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 23 May 2012 10:49:40 +0200 Subject: ssl: Simpler PEM cache --- lib/ssl/src/ssl_certificate_db.erl | 193 ++++++++++++++++++++----------------- lib/ssl/src/ssl_connection.erl | 75 +++++++++----- lib/ssl/src/ssl_manager.erl | 108 ++++++++++----------- 3 files changed, 211 insertions(+), 165 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl index ed6e94d445..80fdc5201a 100644 --- a/lib/ssl/src/ssl_certificate_db.erl +++ b/lib/ssl/src/ssl_certificate_db.erl @@ -24,13 +24,18 @@ -module(ssl_certificate_db). -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/file.hrl"). -export([create/0, remove/1, add_trusted_certs/3, - remove_trusted_certs/2, lookup_trusted_cert/4, foldl/3, - lookup_cached_certs/2, cache_pem_file/4, uncache_pem_file/2, lookup/2]). + remove_trusted_certs/2, insert/3, remove/2, clean/1, + ref_count/3, lookup_trusted_cert/4, foldl/3, + lookup_cached_pem/2, cache_pem_file/3, cache_pem_file/4, + lookup/2]). -type time() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}. +-define(NOT_TO_BIG, 10). + %%==================================================================== %% Internal application API %%==================================================================== @@ -43,9 +48,14 @@ %% the process that called create may call the other functions. %%-------------------------------------------------------------------- create() -> - [ets:new(ssl_otp_certificate_db, [set, protected]), - ets:new(ssl_file_to_ref, [set, protected]), - ets:new(ssl_pid_to_file, [bag, private])]. + [%% Let connection process delete trusted certs + %% that can only belong to one connection. (Supplied directly + %% on DER format to ssl:connect/listen.) + ets:new(ssl_otp_cacertificate_db, [set, public]), + %% Let connection processes call ref_count/3 directly + ets:new(ssl_otp_ca_file_ref, [set, public]), + ets:new(ssl_otp_pem_cache, [set, protected]) + ]. %%-------------------------------------------------------------------- -spec remove([db_handle()]) -> term(). @@ -53,7 +63,9 @@ create() -> %% Description: Removes database db %%-------------------------------------------------------------------- remove(Dbs) -> - lists:foreach(fun(Db) -> true = ets:delete(Db) end, Dbs). + lists:foreach(fun(Db) -> + true = ets:delete(Db) + end, Dbs). %%-------------------------------------------------------------------- -spec lookup_trusted_cert(db_handle(), certdb_ref(), serialnumber(), issuer()) -> @@ -72,8 +84,10 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> {ok, Certs} end. -lookup_cached_certs(DbHandle, File) -> - ets:lookup(DbHandle, {file, crypto:md5(File)}). +lookup_cached_pem([_, _, PemChache], MD5) -> + lookup_cached_pem(PemChache, MD5); +lookup_cached_pem(PemChache, MD5) -> + lookup(MD5, PemChache). %%-------------------------------------------------------------------- -spec add_trusted_certs(pid(), string() | {der, list()}, [db_handle()]) -> {ok, [db_handle()]}. @@ -86,84 +100,68 @@ add_trusted_certs(_Pid, {der, DerList}, [CerDb, _,_]) -> NewRef = make_ref(), add_certs_from_der(DerList, NewRef, CerDb), {ok, NewRef}; -add_trusted_certs(Pid, File, [CertsDb, FileToRefDb, PidToFileDb]) -> + +add_trusted_certs(_Pid, File, [CertsDb, RefDb, PemChache] = Db) -> + {ok, #file_info{mtime = LastWrite}} = file:read_file_info(File), MD5 = crypto:md5(File), - Ref = case lookup(MD5, FileToRefDb) of - undefined -> - NewRef = make_ref(), - add_certs_from_file(File, NewRef, CertsDb), - insert(MD5, NewRef, 1, FileToRefDb), - NewRef; - [OldRef] -> - ref_count(MD5,FileToRefDb,1), - OldRef - end, - insert(Pid, MD5, PidToFileDb), - {ok, Ref}. + case lookup_cached_pem(Db, MD5) of + [{Mtime, Content}] -> + case LastWrite of + Mtime -> + Ref0 = make_ref(), + insert(Ref0, [], 1, RefDb), + insert(MD5, {Mtime, Content, Ref0}, PemChache), + add_certs_from_pem(Content, Ref0, CertsDb), + {ok, Ref0}; + _ -> + new_trusted_cert_entry(File, LastWrite, Db) + end; + [{Mtime, _Content, Ref0}] -> + case LastWrite of + Mtime -> + ref_count(Ref0, RefDb, 1), + {ok, Ref0}; + _ -> + new_trusted_cert_entry(File, LastWrite, Db) + end; + undefined -> + new_trusted_cert_entry(File, LastWrite, Db) + end. %%-------------------------------------------------------------------- --spec cache_pem_file(pid(), string(), time(), [db_handle()]) -> term(). +-spec cache_pem_file(string(), time(), [db_handle()]) -> term(). +-spec cache_pem_file(reference(), string(), time(), [db_handle()]) -> term(). %% %% Description: Cache file as binary in DB %%-------------------------------------------------------------------- -cache_pem_file(Pid, File, Time, [CertsDb, _FileToRefDb, PidToFileDb]) -> +cache_pem_file(File, Time, [_CertsDb, _RefDb, PemChache]) -> {ok, PemBin} = file:read_file(File), Content = public_key:pem_decode(PemBin), MD5 = crypto:md5(File), - insert({file, MD5}, {Time, Content}, CertsDb), - insert(Pid, MD5, PidToFileDb), + insert(MD5, {Time, Content}, PemChache), {ok, Content}. -%-------------------------------------------------------------------- --spec uncache_pem_file(string(), [db_handle()]) -> 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', crypto:md5(File)},[],['$$']}]), - lists:foreach(fun([Pid]) -> - exit(Pid, shutdown) - end, Pids). - -%%-------------------------------------------------------------------- --spec remove_trusted_certs(pid(), [db_handle()]) -> term(). - -%% -%% Description: Removes trusted certs originating from -%% the file associated to Pid from the runtime database. -%%-------------------------------------------------------------------- -remove_trusted_certs(Pid, [CertsDb, FileToRefDb, PidToFileDb]) -> - FileMD5s = lookup(Pid, PidToFileDb), - delete(Pid, PidToFileDb), - Clear = fun(MD5) -> - delete({file,MD5}, CertsDb), - try - 0 = ref_count(MD5, FileToRefDb, -1), - case lookup(MD5, FileToRefDb) of - [Ref] when is_reference(Ref) -> - remove_certs(Ref, CertsDb); - _ -> ok - end, - delete(MD5, FileToRefDb) - catch _:_ -> - ok - end - end, - case FileMD5s of - undefined -> ok; - _ -> - [Clear(FileMD5) || FileMD5 <- FileMD5s], - ok - end. +cache_pem_file(Ref, File, Time, [_CertsDb, _RefDb, PemChache]) -> + {ok, PemBin} = file:read_file(File), + Content = public_key:pem_decode(PemBin), + MD5 = crypto:md5(File), + insert(MD5, {Time, Content, Ref}, PemChache), + {ok, Content}. + +remove_trusted_certs(Ref, CertsDb) -> + remove_certs(Ref, CertsDb). + +%%-------------------------------------------------------------------- +-spec remove(term(), db_handle()) -> term(). +%% +%% Description: Removes an element in a . +%%-------------------------------------------------------------------- +remove(Key, Db) -> + _ = ets:delete(Db, Key). %%-------------------------------------------------------------------- -spec lookup(term(), db_handle()) -> term() | undefined. %% -%% Description: Looks up an element in a certificat . +%% Description: Looks up an element in a . %%-------------------------------------------------------------------- lookup(Key, Db) -> case ets:lookup(Db, Key) of @@ -186,25 +184,43 @@ lookup(Key, Db) -> %%-------------------------------------------------------------------- foldl(Fun, Acc0, Cache) -> ets:foldl(Fun, Acc0, Cache). - + %%-------------------------------------------------------------------- -%%% Internal functions +-spec ref_count(term(), db_handle(), integer()) -> integer(). +%% +%% Description: Updates a reference counter in a . +%%-------------------------------------------------------------------- +ref_count(Key, Db, N) -> + ets:update_counter(Db,Key,N). + +%%-------------------------------------------------------------------- +-spec clean(db_handle()) -> term(). +%% +%% Description: Updates a reference counter in a . +%%-------------------------------------------------------------------- +clean(Db) -> + case ets:info(Db, size) of + N when N < ?NOT_TO_BIG -> + ok; + _ -> + ets:delete_all_objects(Db) + end. +%%-------------------------------------------------------------------- +%%-spec insert(Key::term(), Data::term(), Db::db_handle()) -> no_return(). +%% +%% Description: Inserts data into %%-------------------------------------------------------------------- insert(Key, Data, Db) -> true = ets:insert(Db, {Key, Data}). +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +insert(Key, [], Count, Db) -> + true = ets:insert(Db, {Key, Count}); insert(Key, Data, Count, Db) -> true = ets:insert(Db, {Key, Count, Data}). -ref_count(Key, Db,N) -> - ets:update_counter(Db,Key,N). - -delete(Key, Db) -> - _ = ets:delete(Db, Key). - -select(Db, MatchSpec)-> - ets:select(Db, MatchSpec). - remove_certs(Ref, CertsDb) -> ets:match_delete(CertsDb, {{Ref, '_', '_'}, '_'}). @@ -212,10 +228,8 @@ add_certs_from_der(DerList, Ref, CertsDb) -> Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, [Add(Cert) || Cert <- DerList]. -add_certs_from_file(File, Ref, CertsDb) -> +add_certs_from_pem(PemEntries, Ref, CertsDb) -> Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, - {ok, PemBin} = file:read_file(File), - PemEntries = public_key:pem_decode(PemBin), [Add(Cert) || {'Certificate', Cert, not_encrypted} <- PemEntries]. add_certs(Cert, Ref, CertsDb) -> @@ -231,3 +245,10 @@ add_certs(Cert, Ref, CertsDb) -> "it could not be correctly decoded.~n", []), error_logger:info_report(Report) end. + +new_trusted_cert_entry(File, LastWrite, [CertsDb,RefDb,PemChache] = Db) -> + Ref = make_ref(), + insert(Ref, [], 1, RefDb), + {ok, Content} = cache_pem_file(Ref, File, LastWrite, Db), + add_certs_from_pem(Content, Ref, CertsDb), + {ok, Ref}. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 38dad1c381..2abe2d3a6c 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -83,7 +83,8 @@ diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side diffie_hellman_keys, % {PublicKey, PrivateKey} premaster_secret, % - cert_db_ref, % ets_table() + file_ref_db, % ets() + cert_db_ref, % ref() from, % term(), where to reply bytes_to_read, % integer(), # bytes to read in passive mode user_data_buffer, % binary() @@ -305,11 +306,12 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = 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} -> + {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, Key, DHParams} -> Session = State0#state.session, State = State0#state{tls_handshake_hashes = Hashes0, session = Session#session{own_certificate = OwnCert, time_stamp = TimeStamp}, + file_ref_db = FileRefHandle, cert_db_ref = Ref, cert_db = CertDbHandle, session_cache = CacheHandle, @@ -1000,16 +1002,19 @@ terminate(Reason, connection, #state{negotiated_version = Version, connection_states = ConnectionStates, transport_cb = Transport, socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate}) -> + renegotiation = Renegotiate} = State) -> + handle_trusted_certs_db(State), notify_senders(SendQueue), notify_renegotiater(Renegotiate), BinAlert = terminate_alert(Reason, Version, ConnectionStates), Transport:send(Socket, BinAlert), workaround_transport_delivery_problems(Socket, Transport, Reason), Transport:close(Socket); + terminate(Reason, _StateName, #state{transport_cb = Transport, socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate}) -> + renegotiation = Renegotiate} = State) -> + handle_trusted_certs_db(State), notify_senders(SendQueue), notify_renegotiater(Renegotiate), workaround_transport_delivery_problems(Socket, Transport, Reason), @@ -1057,12 +1062,12 @@ ssl_init(SslOpts, Role) -> init_manager_name(SslOpts#ssl_options.erl_dist), - {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert} = init_certificates(SslOpts, Role), + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} = init_certificates(SslOpts, Role), PrivateKey = - init_private_key(CertDbHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, + init_private_key(PemCacheHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, SslOpts#ssl_options.password, Role), - DHParams = init_diffie_hellman(CertDbHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), - {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert, PrivateKey, DHParams}. + DHParams = init_diffie_hellman(PemCacheHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), + {ok, CertDbRef, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, PrivateKey, DHParams}. init_manager_name(false) -> put(ssl_manager, ssl_manager); @@ -1073,7 +1078,7 @@ init_certificates(#ssl_options{cacerts = CaCerts, cacertfile = CACertFile, certfile = CertFile, cert = Cert}, Role) -> - {ok, CertDbRef, CertDbHandle, CacheHandle} = + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle} = try Certs = case CaCerts of undefined -> @@ -1081,36 +1086,36 @@ init_certificates(#ssl_options{cacerts = CaCerts, _ -> {der, CaCerts} end, - {ok, _, _, _} = ssl_manager:connection_init(Certs, Role) + {ok, _, _, _, _, _} = ssl_manager:connection_init(Certs, Role) catch Error:Reason -> handle_file_error(?LINE, Error, Reason, CACertFile, ecacertfile, erlang:get_stacktrace()) end, - init_certificates(Cert, CertDbRef, CertDbHandle, CacheHandle, CertFile, Role). + init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, Role). -init_certificates(undefined, CertDbRef, CertDbHandle, CacheHandle, "", _) -> - {ok, CertDbRef, CertDbHandle, CacheHandle, undefined}; +init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, "", _) -> + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined}; -init_certificates(undefined, CertDbRef, CertDbHandle, CacheHandle, CertFile, client) -> +init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, client) -> try - [OwnCert] = ssl_certificate:file_to_certificats(CertFile, CertDbHandle), - {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert} + [OwnCert] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} catch _Error:_Reason -> - {ok, CertDbRef, CertDbHandle, CacheHandle, undefined} + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined} end; -init_certificates(undefined, CertDbRef, CertDbHandle, CacheRef, CertFile, server) -> +init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CertFile, server) -> try - [OwnCert] = ssl_certificate:file_to_certificats(CertFile, CertDbHandle), - {ok, CertDbRef, CertDbHandle, CacheRef, OwnCert} + [OwnCert] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, OwnCert} catch Error:Reason -> handle_file_error(?LINE, Error, Reason, CertFile, ecertfile, erlang:get_stacktrace()) end; -init_certificates(Cert, CertDbRef, CertDbHandle, CacheRef, _, _) -> - {ok, CertDbRef, CertDbHandle, CacheRef, Cert}. +init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, _, _) -> + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, Cert}. init_private_key(_, undefined, "", _Password, _Client) -> undefined; @@ -1259,7 +1264,7 @@ verify_client_cert(#state{client_certificate_requested = false} = State) -> State. do_server_hello(Type, #state{negotiated_version = Version, - session = #session{session_id = SessId} = Session, + session = #session{session_id = SessId}, connection_states = ConnectionStates0, renegotiation = {Renegotiation, _}} = State0) when is_atom(Type) -> @@ -1273,7 +1278,7 @@ do_server_hello(Type, #state{negotiated_version = Version, new -> new_server_hello(ServerHello, State); resumed -> - resumed_server_hello(State) + resumed_server_hello(State) end. new_server_hello(#server_hello{cipher_suite = CipherSuite, @@ -2367,3 +2372,25 @@ get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> infinity; get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> HibernateAfter. + +handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = ""}}) -> + %% No trusted certs specified + ok; +handle_trusted_certs_db(#state{cert_db_ref = Ref, + cert_db = CertDb, + ssl_options = #ssl_options{cacertfile = undefined}}) -> + %% Certs provided as DER directly can not be shared + %% with other connections and it is safe to delete them when the connection ends. + ssl_certificate_db:remove_trusted_certs(Ref, CertDb); +handle_trusted_certs_db(#state{file_ref_db = undefined}) -> + %% Something went wrong early (typically cacertfile does not exist) so there is nothing to handle + ok; +handle_trusted_certs_db(#state{cert_db_ref = Ref, + file_ref_db = RefDb, + ssl_options = #ssl_options{cacertfile = File}}) -> + case ssl_certificate_db:ref_count(Ref, RefDb, -1) of + 0 -> + ssl_manager:clean_cert_db(Ref, File); + _ -> + ok + end. diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 7ee8f6e9d6..e3bf0a1f92 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -30,7 +30,7 @@ -export([start_link/1, start_link_dist/1, connection_init/2, cache_pem_file/2, lookup_trusted_cert/4, - new_session_id/1, + new_session_id/1, clean_cert_db/2, register_session/2, register_session/3, invalidate_session/2, invalidate_session/3]). @@ -58,8 +58,9 @@ -define('24H_in_sec', 8640). -define(GEN_UNIQUE_ID_MAX_TRIES, 10). -define(SESSION_VALIDATION_INTERVAL, 60000). --define(CERTIFICATE_CACHE_CLEANUP, 30000). +-define(PEM_CACHE_CLEANUP, 120000). -define(CLEAN_SESSION_DB, 60000). +-define(CLEAN_CERT_DB, 500). %%==================================================================== %% API @@ -120,6 +121,9 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> new_session_id(Port) -> call({new_session_id, Port}). +clean_cert_db(Ref, File) -> + erlang:send_after(?CLEAN_CERT_DB, self(), {clean_cert_db, Ref, File}). + %%-------------------------------------------------------------------- -spec register_session(inet:port_number(), #session{}) -> ok. -spec register_session(host(), inet:port_number(), #session{}) -> ok. @@ -166,6 +170,7 @@ init([Name, Opts]) -> SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])), Timer = erlang:send_after(SessionLifeTime * 1000, self(), validate_sessions), + erlang:send_after(?PEM_CACHE_CLEANUP, self(), clean_pem_cache), {ok, #state{certificate_db = CertDb, session_cache = SessionCache, session_cache_cb = CacheCb, @@ -183,21 +188,19 @@ init([Name, Opts]) -> %% %% Description: Handling call messages %%-------------------------------------------------------------------- -handle_call({{connection_init, "", _Role}, Pid}, _From, - #state{certificate_db = [CertDb |_], +handle_call({{connection_init, "", _Role}, _Pid}, _From, + #state{certificate_db = [CertDb, FileRefDb, PemChace], session_cache = Cache} = State) -> - erlang:monitor(process, Pid), - Result = {ok, make_ref(),CertDb, Cache}, + Result = {ok, make_ref(),CertDb, FileRefDb, PemChace, Cache}, {reply, Result, State}; handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From, - #state{certificate_db = [CertDb|_] =Db, + #state{certificate_db = [CertDb, FileRefDb, PemChace] = Db, session_cache = Cache} = State) -> - erlang:monitor(process, Pid), Result = try {ok, Ref} = ssl_certificate_db:add_trusted_certs(Pid, Trustedcerts, Db), - {ok, Ref, CertDb, Cache} + {ok, Ref, CertDb, FileRefDb, PemChace, Cache} catch _:Reason -> {error, Reason} @@ -210,20 +213,16 @@ handle_call({{new_session_id,Port}, _}, Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), {reply, Id, State}; -handle_call({{cache_pem, File, LastWrite}, Pid}, _, + +handle_call({{cache_pem, File, LastWrite}, _Pid}, _, #state{certificate_db = Db} = State) -> - try ssl_certificate_db:cache_pem_file(Pid, File, LastWrite, Db) of + try ssl_certificate_db:cache_pem_file(File, LastWrite, Db) of Result -> {reply, Result, State} catch _:Reason -> {reply, {error, Reason}, State} - end; -handle_call({{recache_pem, File, LastWrite}, Pid}, From, - #state{certificate_db = Db} = State) -> - ssl_certificate_db:uncache_pem_file(File, Db), - cast({recache_pem, File, LastWrite, Pid, From}), - {noreply, State}. + end. %%-------------------------------------------------------------------- -spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. @@ -259,22 +258,7 @@ handle_cast({invalidate_session, Host, Port, handle_cast({invalidate_session, Port, #session{session_id = ID} = Session}, #state{session_cache = Cache, session_cache_cb = CacheCb} = State) -> - invalidate_session(Cache, CacheCb, {Port, ID}, Session, State); - -handle_cast({recache_pem, File, LastWrite, Pid, From}, - #state{certificate_db = [_, FileToRefDb, _]} = State0) -> - case ssl_certificate_db:lookup(File, FileToRefDb) of - undefined -> - {reply, Msg, State} = - handle_call({{cache_pem, File, LastWrite}, Pid}, From, State0), - gen_server:reply(From, Msg), - {noreply, State}; - _ -> %% Send message to self letting cleanup messages be handled - %% first so that no reference to the old version of file - %% exists when we cache the new one. - cast({recache_pem, File, LastWrite, Pid, From}), - {noreply, State0} - end. + invalidate_session(Cache, CacheCb, {Port, ID}, Session, State). %%-------------------------------------------------------------------- -spec handle_info(msg(), #state{}) -> {noreply, #state{}}. @@ -299,23 +283,32 @@ handle_info({delayed_clean_session, Key}, #state{session_cache = Cache, CacheCb:delete(Cache, Key), {noreply, State}; -handle_info({'EXIT', _, _}, State) -> - %% Session validator died!! Do we need to take any action? - %% maybe error log +handle_info(clean_pem_cache, #state{certificate_db = [_,_,PemChace]} = State) -> + ssl_certificate_db:clean(PemChace), + erlang:send_after(?PEM_CACHE_CLEANUP, self(), clean_pem_cache), {noreply, State}; -handle_info({'DOWN', _Ref, _Type, _Pid, ecacertfile}, State) -> +handle_info({clean_cert_db, Ref, File}, + #state{certificate_db = [CertDb,RefDb, PemCache]} = State) -> + case ssl_certificate_db:ref_count(Ref, RefDb, 0) of + 0 -> + MD5 = crypto:md5(File), + case ssl_certificate_db:lookup_cached_pem(MD5, PemCache) of + [{Mtime, Content, Ref}] -> + ssl_certificate_db:insert(MD5, {Mtime, Content}, PemCache); + undefined -> + ok + end, + ssl_certificate_db:remove(Ref, RefDb), + ssl_certificate_db:remove_trusted_certs(Ref, CertDb); + _ -> + ok + end, {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{certificate_db = Db} = State) -> - ssl_certificate_db:remove_trusted_certs(Pid, Db), +handle_info({'EXIT', _, _}, State) -> + %% Session validator died!! Do we need to take any action? + %% maybe error log {noreply, State}; handle_info(_Info, State) -> @@ -388,15 +381,20 @@ session_validation({{Port, _}, Session}, LifeTime) -> LifeTime. cache_pem_file(File, LastWrite, DbHandle) -> - case ssl_certificate_db:lookup_cached_certs(DbHandle,File) of - [{_, {Mtime, Content}}] -> - case LastWrite of - Mtime -> - {ok, Content}; - _ -> - call({recache_pem, File, LastWrite}) - end; - [] -> + case ssl_certificate_db:lookup_cached_pem(DbHandle,crypto:md5(File)) of + [{Mtime, Content}] -> + handle_cached_entry(File, LastWrite, Mtime, Content); + [{Mtime, Content,_}] -> + handle_cached_entry(File, LastWrite, Mtime, Content); + undefined -> + call({cache_pem, File, LastWrite}) + end. + +handle_cached_entry(File, LastWrite, Time, Content) -> + case LastWrite of + Time -> + {ok, Content}; + _ -> call({cache_pem, File, LastWrite}) end. -- cgit v1.2.3 From 8e80b89c7aafc70fbcb538c9c6b464912cb11d83 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 31 May 2012 11:39:46 +0200 Subject: ssl: File handling optimization Avoid cach validation with file:file_info/2 as this i too expensive and causes a bottleneck in the file server. Instead we expose a new API function ssl:clear_pem_cache/0 to deal with the problem. As we think it will be of occasional use and the normal case is that the cache will be valid we think it is the right thing to do. Convert file paths to binary representation in the ssl API module to avoid uncessarry calls in file later on. Also add sanity checks for openssl versions in testsuite due to new openssl bugs. --- lib/ssl/src/ssl.erl | 36 ++++++++++++--- lib/ssl/src/ssl_certificate_db.erl | 84 +++++++++++++++------------------- lib/ssl/src/ssl_connection.erl | 6 +-- lib/ssl/src/ssl_manager.erl | 86 ++++++++++++++++++++--------------- lib/ssl/test/ssl_basic_SUITE.erl | 2 + lib/ssl/test/ssl_to_openssl_SUITE.erl | 15 +++++- 6 files changed, 134 insertions(+), 95 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 0bcdffbeff..5e3ced144a 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -30,7 +30,7 @@ controlling_process/2, listen/2, pid/1, peername/1, peercert/1, recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1, versions/0, session_info/1, format_error/1, - renegotiate/1, prf/5]). + renegotiate/1, prf/5, clear_pem_cache/0]). -deprecated({pid, 1, next_major_release}). @@ -431,6 +431,15 @@ prf(#sslsocket{pid = Pid, fd = new_ssl}, Secret, Label, Seed, WantedLength) -> ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength). + +%%-------------------------------------------------------------------- +-spec clear_pem_cache() -> ok. +%% +%% Description: Clear the PEM cache +%%-------------------------------------------------------------------- +clear_pem_cache() -> + ssl_manager:clear_pem_cache(). + %%--------------------------------------------------------------- -spec format_error({error, term()}) -> list(). %% @@ -532,7 +541,7 @@ handle_options(Opts0, _Role) -> throw({error, {eoptions, {verify, Value}}}) end, - CertFile = handle_option(certfile, Opts, ""), + CertFile = handle_option(certfile, Opts, <<>>), SSLOptions = #ssl_options{ versions = handle_option(versions, Opts, []), @@ -619,8 +628,12 @@ validate_option(depth, Value) when is_integer(Value), validate_option(cert, Value) when Value == undefined; is_binary(Value) -> Value; -validate_option(certfile, Value) when Value == undefined; is_list(Value) -> +validate_option(certfile, undefined = Value) -> Value; +validate_option(certfile, Value) when is_binary(Value) -> + Value; +validate_option(certfile, Value) when is_list(Value) -> + list_to_binary(Value); validate_option(key, undefined) -> undefined; @@ -631,8 +644,13 @@ validate_option(key, {KeyType, Value}) when is_binary(Value), KeyType == 'DSAPrivateKey'; KeyType == 'PrivateKeyInfo' -> {KeyType, Value}; -validate_option(keyfile, Value) when is_list(Value) -> + +validate_option(keyfile, undefined) -> + <<>>; +validate_option(keyfile, Value) when is_binary(Value) -> Value; +validate_option(keyfile, Value) when is_list(Value), Value =/= "" -> + list_to_binary(Value); validate_option(password, Value) when is_list(Value) -> Value; @@ -642,16 +660,20 @@ validate_option(cacerts, Value) when Value == undefined; %% certfile must be present in some cases otherwhise it can be set %% to the empty string. validate_option(cacertfile, undefined) -> - ""; -validate_option(cacertfile, Value) when is_list(Value), Value =/= "" -> + <<>>; +validate_option(cacertfile, Value) when is_binary(Value) -> Value; +validate_option(cacertfile, Value) when is_list(Value), Value =/= ""-> + list_to_binary(Value); validate_option(dh, Value) when Value == undefined; is_binary(Value) -> Value; validate_option(dhfile, undefined = Value) -> Value; -validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> +validate_option(dhfile, Value) when is_binary(Value) -> Value; +validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> + list_to_binary(Value); validate_option(ciphers, Value) when is_list(Value) -> Version = ssl_record:highest_protocol_version([]), try cipher_suites(Version, Value) diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl index 80fdc5201a..01ddf056c9 100644 --- a/lib/ssl/src/ssl_certificate_db.erl +++ b/lib/ssl/src/ssl_certificate_db.erl @@ -27,15 +27,11 @@ -include_lib("kernel/include/file.hrl"). -export([create/0, remove/1, add_trusted_certs/3, - remove_trusted_certs/2, insert/3, remove/2, clean/1, + remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, ref_count/3, lookup_trusted_cert/4, foldl/3, - lookup_cached_pem/2, cache_pem_file/3, cache_pem_file/4, + lookup_cached_pem/2, cache_pem_file/2, cache_pem_file/3, lookup/2]). --type time() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}. - --define(NOT_TO_BIG, 10). - %%==================================================================== %% Internal application API %%==================================================================== @@ -90,7 +86,8 @@ lookup_cached_pem(PemChache, MD5) -> lookup(MD5, PemChache). %%-------------------------------------------------------------------- --spec add_trusted_certs(pid(), string() | {der, list()}, [db_handle()]) -> {ok, [db_handle()]}. +-spec add_trusted_certs(pid(), {erlang:timestamp(), string()} | + {der, list()}, [db_handle()]) -> {ok, [db_handle()]}. %% %% Description: Adds the trusted certificates from file to the %% runtime database. Returns Ref that should be handed to lookup_trusted_cert @@ -102,49 +99,36 @@ add_trusted_certs(_Pid, {der, DerList}, [CerDb, _,_]) -> {ok, NewRef}; add_trusted_certs(_Pid, File, [CertsDb, RefDb, PemChache] = Db) -> - {ok, #file_info{mtime = LastWrite}} = file:read_file_info(File), MD5 = crypto:md5(File), case lookup_cached_pem(Db, MD5) of - [{Mtime, Content}] -> - case LastWrite of - Mtime -> - Ref0 = make_ref(), - insert(Ref0, [], 1, RefDb), - insert(MD5, {Mtime, Content, Ref0}, PemChache), - add_certs_from_pem(Content, Ref0, CertsDb), - {ok, Ref0}; - _ -> - new_trusted_cert_entry(File, LastWrite, Db) - end; - [{Mtime, _Content, Ref0}] -> - case LastWrite of - Mtime -> - ref_count(Ref0, RefDb, 1), - {ok, Ref0}; - _ -> - new_trusted_cert_entry(File, LastWrite, Db) - end; + [{_Content, Ref}] -> + ref_count(Ref, RefDb, 1), + {ok, Ref}; + [Content] -> + Ref = make_ref(), + insert(Ref, [], 1, RefDb), + insert(MD5, {Content, Ref}, PemChache), + add_certs_from_pem(Content, Ref, CertsDb), + {ok, Ref}; undefined -> - new_trusted_cert_entry(File, LastWrite, Db) + new_trusted_cert_entry({MD5, File}, Db) end. %%-------------------------------------------------------------------- --spec cache_pem_file(string(), time(), [db_handle()]) -> term(). --spec cache_pem_file(reference(), string(), time(), [db_handle()]) -> term(). +-spec cache_pem_file(string(), [db_handle()]) -> term(). +-spec cache_pem_file(reference(), string(), [db_handle()]) -> term(). %% %% Description: Cache file as binary in DB %%-------------------------------------------------------------------- -cache_pem_file(File, Time, [_CertsDb, _RefDb, PemChache]) -> - {ok, PemBin} = file:read_file(File), +cache_pem_file({MD5, File}, [_CertsDb, _RefDb, PemChache]) -> + {ok, PemBin} = file:read_file(File), Content = public_key:pem_decode(PemBin), - MD5 = crypto:md5(File), - insert(MD5, {Time, Content}, PemChache), + insert(MD5, Content, PemChache), {ok, Content}. -cache_pem_file(Ref, File, Time, [_CertsDb, _RefDb, PemChache]) -> +cache_pem_file(Ref, {MD5, File}, [_CertsDb, _RefDb, PemChache]) -> {ok, PemBin} = file:read_file(File), Content = public_key:pem_decode(PemBin), - MD5 = crypto:md5(File), - insert(MD5, {Time, Content, Ref}, PemChache), + insert(MD5, {Content, Ref}, PemChache), {ok, Content}. remove_trusted_certs(Ref, CertsDb) -> @@ -194,17 +178,21 @@ ref_count(Key, Db, N) -> ets:update_counter(Db,Key,N). %%-------------------------------------------------------------------- --spec clean(db_handle()) -> term(). +-spec clear(db_handle()) -> term(). %% -%% Description: Updates a reference counter in a . +%% Description: Clears the cache %%-------------------------------------------------------------------- -clean(Db) -> - case ets:info(Db, size) of - N when N < ?NOT_TO_BIG -> - ok; - _ -> - ets:delete_all_objects(Db) - end. +clear(Db) -> + ets:delete_all_objects(Db). + +%%-------------------------------------------------------------------- +-spec db_size(db_handle()) -> integer(). +%% +%% Description: Returns the size of the db +%%-------------------------------------------------------------------- +db_size(Db) -> + ets:info(Db, size). + %%-------------------------------------------------------------------- %%-spec insert(Key::term(), Data::term(), Db::db_handle()) -> no_return(). %% @@ -246,9 +234,9 @@ add_certs(Cert, Ref, CertsDb) -> error_logger:info_report(Report) end. -new_trusted_cert_entry(File, LastWrite, [CertsDb,RefDb,PemChache] = Db) -> +new_trusted_cert_entry(FileRef, [CertsDb, RefDb, _] = Db) -> Ref = make_ref(), insert(Ref, [], 1, RefDb), - {ok, Content} = cache_pem_file(Ref, File, LastWrite, Db), + {ok, Content} = cache_pem_file(Ref, FileRef, Db), add_certs_from_pem(Content, Ref, CertsDb), {ok, Ref}. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 2abe2d3a6c..ce20e72524 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1094,7 +1094,7 @@ init_certificates(#ssl_options{cacerts = CaCerts, end, init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, Role). -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, "", _) -> +init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, <<>>, _) -> {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined}; init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, client) -> @@ -1117,7 +1117,7 @@ init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHan init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, _, _) -> {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, Cert}. -init_private_key(_, undefined, "", _Password, _Client) -> +init_private_key(_, undefined, <<>>, _Password, _Client) -> undefined; init_private_key(DbHandle, undefined, KeyFile, Password, _) -> try @@ -2373,7 +2373,7 @@ get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> HibernateAfter. -handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = ""}}) -> +handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>}}) -> %% No trusted certs specified ok; handle_trusted_certs_db(#state{cert_db_ref = Ref, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index e3bf0a1f92..a18cb70e2d 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -32,7 +32,7 @@ lookup_trusted_cert/4, new_session_id/1, clean_cert_db/2, register_session/2, register_session/3, invalidate_session/2, - invalidate_session/3]). + invalidate_session/3, clear_pem_cache/0]). % Spawn export -export([init_session_validator/1]). @@ -58,9 +58,10 @@ -define('24H_in_sec', 8640). -define(GEN_UNIQUE_ID_MAX_TRIES, 10). -define(SESSION_VALIDATION_INTERVAL, 60000). --define(PEM_CACHE_CLEANUP, 120000). +-define(CLEAR_PEM_CACHE, 120000). -define(CLEAN_SESSION_DB, 60000). -define(CLEAN_CERT_DB, 500). +-define(NOT_TO_BIG, 10). %%==================================================================== %% API @@ -84,24 +85,46 @@ start_link_dist(Opts) -> gen_server:start_link({local, ssl_manager_dist}, ?MODULE, [ssl_manager_dist, Opts], []). %%-------------------------------------------------------------------- --spec connection_init(string()| {der, list()}, client | server) -> +-spec connection_init(binary()| {der, list()}, client | server) -> {ok, certdb_ref(), db_handle(), db_handle()}. %% %% Description: Do necessary initializations for a new connection. %%-------------------------------------------------------------------- +connection_init({der, _} = Trustedcerts, Role) -> + call({connection_init, Trustedcerts, Role}); + +connection_init(<<>> = Trustedcerts, Role) -> + call({connection_init, Trustedcerts, Role}); + connection_init(Trustedcerts, Role) -> call({connection_init, Trustedcerts, Role}). + %%-------------------------------------------------------------------- --spec cache_pem_file(string(), term()) -> {ok, term()} | {error, reason()}. +-spec cache_pem_file(binary(), term()) -> {ok, term()} | {error, reason()}. %% %% Description: Cach a pem file and return its content. %%-------------------------------------------------------------------- cache_pem_file(File, DbHandle) -> - case file:read_file_info(File) of - {ok, #file_info{mtime = LastWrite}} -> - cache_pem_file(File, LastWrite, DbHandle); - Error -> Error + MD5 = crypto:md5(File), + case ssl_certificate_db:lookup_cached_pem(DbHandle, MD5) of + [Content] -> + {ok, Content}; + [{Content,_}] -> + {ok, Content}; + undefined -> + call({cache_pem, {MD5, File}}) end. + +%%-------------------------------------------------------------------- +-spec clear_pem_cache() -> ok. +%% +%% Description: Clear the PEM cache +%%-------------------------------------------------------------------- +clear_pem_cache() -> + %% Not supported for distribution at the moement, should it be? + put(ssl_manager, ssl_manager), + call(unconditionally_clear_pem_cache). + %%-------------------------------------------------------------------- -spec lookup_trusted_cert(term(), reference(), serialnumber(), issuer()) -> undefined | @@ -170,7 +193,7 @@ init([Name, Opts]) -> SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])), Timer = erlang:send_after(SessionLifeTime * 1000, self(), validate_sessions), - erlang:send_after(?PEM_CACHE_CLEANUP, self(), clean_pem_cache), + erlang:send_after(?CLEAR_PEM_CACHE, self(), clear_pem_cache), {ok, #state{certificate_db = CertDb, session_cache = SessionCache, session_cache_cb = CacheCb, @@ -188,7 +211,7 @@ init([Name, Opts]) -> %% %% Description: Handling call messages %%-------------------------------------------------------------------- -handle_call({{connection_init, "", _Role}, _Pid}, _From, +handle_call({{connection_init, <<>>, _Role}, _Pid}, _From, #state{certificate_db = [CertDb, FileRefDb, PemChace], session_cache = Cache} = State) -> Result = {ok, make_ref(),CertDb, FileRefDb, PemChace, Cache}, @@ -214,15 +237,18 @@ handle_call({{new_session_id,Port}, _}, {reply, Id, State}; -handle_call({{cache_pem, File, LastWrite}, _Pid}, _, +handle_call({{cache_pem, File}, _Pid}, _, #state{certificate_db = Db} = State) -> - try ssl_certificate_db:cache_pem_file(File, LastWrite, Db) of + try ssl_certificate_db:cache_pem_file(File, Db) of Result -> {reply, Result, State} catch _:Reason -> {reply, {error, Reason}, State} - end. + end; +handle_call({unconditionally_clear_pem_cache, _},_, #state{certificate_db = [_,_,PemChace]} = State) -> + ssl_certificate_db:clear(PemChace), + {reply, ok, State}. %%-------------------------------------------------------------------- -spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. @@ -283,19 +309,25 @@ handle_info({delayed_clean_session, Key}, #state{session_cache = Cache, CacheCb:delete(Cache, Key), {noreply, State}; -handle_info(clean_pem_cache, #state{certificate_db = [_,_,PemChace]} = State) -> - ssl_certificate_db:clean(PemChace), - erlang:send_after(?PEM_CACHE_CLEANUP, self(), clean_pem_cache), +handle_info(clear_pem_cache, #state{certificate_db = [_,_,PemChace]} = State) -> + case ssl_certificate_db:db_size(PemChace) of + N when N < ?NOT_TO_BIG -> + ok; + _ -> + ssl_certificate_db:clear(PemChace) + end, + erlang:send_after(?CLEAR_PEM_CACHE, self(), clear_pem_cache), {noreply, State}; + handle_info({clean_cert_db, Ref, File}, #state{certificate_db = [CertDb,RefDb, PemCache]} = State) -> case ssl_certificate_db:ref_count(Ref, RefDb, 0) of 0 -> MD5 = crypto:md5(File), case ssl_certificate_db:lookup_cached_pem(MD5, PemCache) of - [{Mtime, Content, Ref}] -> - ssl_certificate_db:insert(MD5, {Mtime, Content}, PemCache); + [{Content, Ref}] -> + ssl_certificate_db:insert(MD5, Content, PemCache); undefined -> ok end, @@ -380,24 +412,6 @@ session_validation({{Port, _}, Session}, LifeTime) -> validate_session(Port, Session, LifeTime), LifeTime. -cache_pem_file(File, LastWrite, DbHandle) -> - case ssl_certificate_db:lookup_cached_pem(DbHandle,crypto:md5(File)) of - [{Mtime, Content}] -> - handle_cached_entry(File, LastWrite, Mtime, Content); - [{Mtime, Content,_}] -> - handle_cached_entry(File, LastWrite, Mtime, Content); - undefined -> - call({cache_pem, File, LastWrite}) - end. - -handle_cached_entry(File, LastWrite, Time, Content) -> - case LastWrite of - Time -> - {ok, Content}; - _ -> - call({cache_pem, File, LastWrite}) - end. - delay_time() -> case application:get_env(ssl, session_delay_cleanup_time) of {ok, Time} when is_integer(Time) -> diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 590ecf33ca..0618628df2 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -3649,6 +3649,8 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client0), + ssl:clear_pem_cache(), + NewServerOpts = new_config(PrivDir, DsaServerOpts), Server1 = diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 01fca1f166..8197e14b64 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -112,6 +112,9 @@ special_init(TestCase, Config) special_init(ssl2_erlang_server_openssl_client, Config) -> check_sane_openssl_sslv2(Config); +special_init(ciphers_dsa_signed_certs, Config) -> + check_sane_openssl_dsa(Config); + special_init(_, Config) -> Config. @@ -1440,14 +1443,24 @@ check_sane_openssl_renegotaite(Config) -> {skip, "Known renegotiation bug in OppenSSL"}; "OpenSSL 0.9.7" ++ _ -> {skip, "Known renegotiation bug in OppenSSL"}; + "OpenSSL 1.0.1c" ++ _ -> + {skip, "Known renegotiation bug in OppenSSL"}; _ -> Config end. check_sane_openssl_sslv2(Config) -> case os:cmd("openssl version") of - "OpenSSL 1.0.0" ++ _ -> + "OpenSSL 1." ++ _ -> {skip, "sslv2 by default turned of in 1.*"}; _ -> Config end. + +check_sane_openssl_dsa(Config) -> + case os:cmd("openssl version") of + "OpenSSL 1.0.1" ++ _ -> + {skip, "known dsa bug in openssl"}; + _ -> + Config + end. -- cgit v1.2.3 From 06260ce858582aa0319a4ad9768291bd51f63505 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 8 Jun 2012 14:51:08 +0200 Subject: ssl: Avoid second bottleneck in supervisor Do proc_lib:spawn_link instead of proc_lib:start_link as synchronized init is not used/needed anyway. --- lib/ssl/src/ssl_connection.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index ce20e72524..0f436a6caf 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -298,10 +298,9 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> %% does not return until Module:init/1 has returned. %%-------------------------------------------------------------------- start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - proc_lib:start_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]]). + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> - proc_lib:init_ack({ok, self()}), State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), Hashes0 = ssl_handshake:init_hashes(), TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), -- cgit v1.2.3 From f88ec275329908b017ae731f8e4ca11d862a9243 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Mon, 11 Jun 2012 15:59:32 +0200 Subject: ssl: Test case fixes --- lib/ssl/test/ssl_basic_SUITE.erl | 1 + lib/ssl/test/ssl_packet_SUITE.erl | 3 ++- lib/ssl/test/ssl_payload_SUITE.erl | 1 + lib/ssl/test/ssl_session_cache_SUITE.erl | 1 + lib/ssl/test/ssl_to_openssl_SUITE.erl | 5 +++-- 5 files changed, 8 insertions(+), 3 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 0618628df2..2eaab02665 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -50,6 +50,7 @@ %%-------------------------------------------------------------------- init_per_suite(Config0) -> Dog = ssl_test_lib:timetrap(?LONG_TIMEOUT *2), + catch crypto:stop(), try crypto:start() of ok -> application:start(public_key), diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index 4b74f57a60..593b1fda5e 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_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 @@ -53,6 +53,7 @@ %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) -> + catch crypto:stop(), try crypto:start() of ok -> application:start(public_key), diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index 24e86b3913..02b5516e35 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -37,6 +37,7 @@ %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) -> + catch crypto:stop(), try crypto:start() of ok -> application:start(public_key), diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index 491aa893c2..6d758ecb01 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -49,6 +49,7 @@ %%-------------------------------------------------------------------- init_per_suite(Config0) -> Dog = ssl_test_lib:timetrap(?LONG_TIMEOUT *2), + catch crypto:stop(), try crypto:start() of ok -> application:start(public_key), diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 8197e14b64..f593c1c552 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -50,6 +50,7 @@ init_per_suite(Config0) -> false -> {skip, "Openssl not found"}; _ -> + catch crypto:stop(), try crypto:start() of ok -> application:start(public_key), @@ -603,7 +604,7 @@ erlang_server_openssl_client_no_wrap_sequence_number(Config) when is_list(Config {from, self()}, {mfa, {ssl_test_lib, trigger_renegotiate, [[Data, N+2]]}}, - {options, [{renegotiate_at, N} | ServerOpts]}]), + {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ @@ -1443,7 +1444,7 @@ check_sane_openssl_renegotaite(Config) -> {skip, "Known renegotiation bug in OppenSSL"}; "OpenSSL 0.9.7" ++ _ -> {skip, "Known renegotiation bug in OppenSSL"}; - "OpenSSL 1.0.1c" ++ _ -> + "OpenSSL 1.0.1c" ++ _ -> {skip, "Known renegotiation bug in OppenSSL"}; _ -> Config -- cgit v1.2.3