%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 20016-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %%---------------------------------------------------------------------- %% Purpose: Manages ssl sessions and trusted certifacates %%---------------------------------------------------------------------- -module(ssl_pem_cache). -behaviour(gen_server). %% Internal application API -export([start_link/1, start_link_dist/1, name/1, insert/2, clear/0]). % Spawn export -export([init_pem_cache_validator/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). -include_lib("kernel/include/file.hrl"). -record(state, { pem_cache, last_pem_check :: integer(), clear :: integer() }). -define(CLEAR_PEM_CACHE, 120000). -define(DEFAULT_MAX_SESSION_CACHE, 1000). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- -spec name(normal | dist) -> atom(). %% %% Description: Returns the registered name of the ssl cache process %% in the operation modes 'normal' and 'dist'. %%-------------------------------------------------------------------- name(normal) -> ?MODULE; name(dist) -> list_to_atom(atom_to_list(?MODULE) ++ "_dist"). %%-------------------------------------------------------------------- -spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}. %% %% Description: Starts the ssl pem cache handler %%-------------------------------------------------------------------- start_link(_) -> CacheName = name(normal), gen_server:start_link({local, CacheName}, ?MODULE, [CacheName], []). %%-------------------------------------------------------------------- -spec start_link_dist(list()) -> {ok, pid()} | ignore | {error, term()}. %% %% Description: Starts a special instance of the ssl manager to %% be used by the erlang distribution. Note disables soft upgrade! %%-------------------------------------------------------------------- start_link_dist(_) -> DistCacheName = name(dist), gen_server:start_link({local, DistCacheName}, ?MODULE, [DistCacheName], []). %%-------------------------------------------------------------------- -spec insert(binary(), term()) -> ok | {error, reason()}. %% %% Description: Cache a pem file and return its content. %%-------------------------------------------------------------------- insert(File, Content) -> case bypass_cache() of true -> ok; false -> cast({cache_pem, File, Content}), ok end. %%-------------------------------------------------------------------- -spec clear() -> ok. %% %% Description: Clear the PEM cache %%-------------------------------------------------------------------- clear() -> %% Not supported for distribution at the moement, should it be? put(ssl_pem_cache, name(normal)), call(unconditionally_clear_pem_cache). -spec invalidate_pem(File::binary()) -> ok. invalidate_pem(File) -> cast({invalidate_pem, File}). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- -spec init(list()) -> {ok, #state{}}. %% Possible return values not used now. %% | {ok, #state{}, timeout()} | ignore | {stop, term()}. %% %% Description: Initiates the server %%-------------------------------------------------------------------- init([Name]) -> put(ssl_pem_cache, Name), process_flag(trap_exit, true), PemCache = ssl_pkix_db:create_pem_cache(Name), Interval = pem_check_interval(), erlang:send_after(Interval, self(), clear_pem_cache), erlang:system_time(second), {ok, #state{pem_cache = PemCache, last_pem_check = erlang:convert_time_unit(os:system_time(), native, second), clear = Interval }}. %%-------------------------------------------------------------------- -spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. %% Possible return values not used now. %% {reply, reply(), #state{}, timeout()} | %% {noreply, #state{}} | %% {noreply, #state{}, timeout()} | %% {stop, reason(), reply(), #state{}} | %% {stop, reason(), #state{}}. %% %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call({unconditionally_clear_pem_cache, _},_, #state{pem_cache = PemCache} = State) -> ssl_pkix_db:clear(PemCache), {reply, ok, State}. %%-------------------------------------------------------------------- -spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. %% Possible return values not used now. %% | {noreply, #state{}, timeout()} | %% {stop, reason(), #state{}}. %% %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast({cache_pem, File, Content}, #state{pem_cache = Db} = State) -> ssl_pkix_db:insert(File, Content, Db), {noreply, State}; handle_cast({invalidate_pem, File}, #state{pem_cache = Db} = State) -> ssl_pkix_db:remove(File, Db), {noreply, State}. %%-------------------------------------------------------------------- -spec handle_info(msg(), #state{}) -> {noreply, #state{}}. %% Possible return values not used now. %% |{noreply, #state{}, timeout()} | %% {stop, reason(), #state{}}. %% %% Description: Handling all non call/cast messages %%------------------------------------------------------------------- handle_info(clear_pem_cache, #state{pem_cache = PemCache, clear = Interval, last_pem_check = CheckPoint} = State) -> NewCheckPoint = erlang:convert_time_unit(os:system_time(), native, second), start_pem_cache_validator(PemCache, CheckPoint), erlang:send_after(Interval, self(), clear_pem_cache), {noreply, State#state{last_pem_check = NewCheckPoint}}; handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- -spec terminate(reason(), #state{}) -> ok. %% %% Description: This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, #state{}) -> ok. %%-------------------------------------------------------------------- -spec code_change(term(), #state{}, list()) -> {ok, #state{}}. %% %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- call(Msg) -> gen_server:call(get(ssl_pem_cache), {Msg, self()}, infinity). cast(Msg) -> gen_server:cast(get(ssl_pem_cache), Msg). start_pem_cache_validator(PemCache, CheckPoint) -> spawn_link(?MODULE, init_pem_cache_validator, [[get(ssl_pem_cache), PemCache, CheckPoint]]). init_pem_cache_validator([CacheName, PemCache, CheckPoint]) -> put(ssl_pem_cache, CacheName), ssl_pkix_db:foldl(fun pem_cache_validate/2, CheckPoint, PemCache). pem_cache_validate({File, _}, CheckPoint) -> case file:read_file_info(File, [{time, posix}]) of {ok, #file_info{mtime = Time}} when Time < CheckPoint -> ok; _ -> invalidate_pem(File) end, CheckPoint. pem_check_interval() -> case application:get_env(ssl, ssl_pem_cache_clean) of {ok, Interval} when is_integer(Interval) -> Interval; _ -> ?CLEAR_PEM_CACHE end. bypass_cache() -> case application:get_env(ssl, bypass_pem_cache) of {ok, Bool} when is_boolean(Bool) -> Bool; _ -> false end.