diff options
author | Ingela Anderton Andin <[email protected]> | 2012-05-23 10:49:40 +0200 |
---|---|---|
committer | Ingela Anderton Andin <[email protected]> | 2012-06-08 16:52:06 +0200 |
commit | 68a055e261d70d3053694efddc1e5a26bf3553f1 (patch) | |
tree | c40c234839c5a93a8166f0e9b94987662141ef9f /lib | |
parent | b334632a0fb241d60cfe6c69fb0800047cd598ff (diff) | |
download | otp-68a055e261d70d3053694efddc1e5a26bf3553f1.tar.gz otp-68a055e261d70d3053694efddc1e5a26bf3553f1.tar.bz2 otp-68a055e261d70d3053694efddc1e5a26bf3553f1.zip |
ssl: Simpler PEM cache
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ssl/src/ssl_certificate_db.erl | 193 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 75 | ||||
-rw-r--r-- | lib/ssl/src/ssl_manager.erl | 108 |
3 files changed, 211 insertions, 165 deletions
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 <Db>. +%%-------------------------------------------------------------------- +remove(Key, Db) -> + _ = ets:delete(Db, Key). %%-------------------------------------------------------------------- -spec lookup(term(), db_handle()) -> term() | undefined. %% -%% Description: Looks up an element in a certificat <Db>. +%% Description: Looks up an element in a <Db>. %%-------------------------------------------------------------------- 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 <Db>. +%%-------------------------------------------------------------------- +ref_count(Key, Db, N) -> + ets:update_counter(Db,Key,N). + +%%-------------------------------------------------------------------- +-spec clean(db_handle()) -> term(). +%% +%% Description: Updates a reference counter in a <Db>. +%%-------------------------------------------------------------------- +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 <Db> %%-------------------------------------------------------------------- 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. |