1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 20016-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%%---------------------------------------------------------------------- 22%% Purpose: Manages ssl sessions and trusted certifacates 23%%---------------------------------------------------------------------- 24 25-module(ssl_pem_cache). 26-behaviour(gen_server). 27 28%% Internal application API 29-export([start_link/1, 30 start_link_dist/1, 31 name/1, 32 insert/2, 33 clear/0]). 34 35% Spawn export 36-export([init_pem_cache_validator/1]). 37 38%% gen_server callbacks 39-export([init/1, handle_call/3, handle_cast/2, handle_info/2, 40 terminate/2, code_change/3]). 41 42-include("ssl_handshake.hrl"). 43-include("ssl_internal.hrl"). 44-include_lib("kernel/include/file.hrl"). 45 46-record(state, { 47 pem_cache, 48 last_pem_check :: integer(), 49 clear :: integer() 50 }). 51 52-define(CLEAR_PEM_CACHE, 120000). 53-define(DEFAULT_MAX_SESSION_CACHE, 1000). 54 55%%==================================================================== 56%% API 57%%==================================================================== 58 59%%-------------------------------------------------------------------- 60-spec name(normal | dist) -> atom(). 61%% 62%% Description: Returns the registered name of the ssl cache process 63%% in the operation modes 'normal' and 'dist'. 64%%-------------------------------------------------------------------- 65name(normal) -> 66 ?MODULE; 67name(dist) -> 68 list_to_atom(atom_to_list(?MODULE) ++ "_dist"). 69 70%%-------------------------------------------------------------------- 71-spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}. 72%% 73%% Description: Starts the ssl pem cache handler 74%%-------------------------------------------------------------------- 75start_link(_) -> 76 CacheName = name(normal), 77 gen_server:start_link({local, CacheName}, 78 ?MODULE, [CacheName], []). 79 80%%-------------------------------------------------------------------- 81-spec start_link_dist(list()) -> {ok, pid()} | ignore | {error, term()}. 82%% 83%% Description: Starts a special instance of the ssl manager to 84%% be used by the erlang distribution. Note disables soft upgrade! 85%%-------------------------------------------------------------------- 86start_link_dist(_) -> 87 DistCacheName = name(dist), 88 gen_server:start_link({local, DistCacheName}, 89 ?MODULE, [DistCacheName], []). 90 91 92%%-------------------------------------------------------------------- 93-spec insert(binary(), term()) -> ok | {error, reason()}. 94%% 95%% Description: Cache a pem file and return its content. 96%%-------------------------------------------------------------------- 97insert(File, Content) -> 98 case bypass_cache() of 99 true -> 100 ok; 101 false -> 102 cast({cache_pem, File, Content}), 103 ok 104 end. 105 106%%-------------------------------------------------------------------- 107-spec clear() -> ok. 108%% 109%% Description: Clear the PEM cache 110%%-------------------------------------------------------------------- 111clear() -> 112 %% Not supported for distribution at the moement, should it be? 113 put(ssl_pem_cache, name(normal)), 114 call(unconditionally_clear_pem_cache). 115 116-spec invalidate_pem(File::binary()) -> ok. 117invalidate_pem(File) -> 118 cast({invalidate_pem, File}). 119 120%%==================================================================== 121%% gen_server callbacks 122%%==================================================================== 123 124%%-------------------------------------------------------------------- 125-spec init(list()) -> {ok, #state{}}. 126%% Possible return values not used now. 127%% | {ok, #state{}, timeout()} | ignore | {stop, term()}. 128%% 129%% Description: Initiates the server 130%%-------------------------------------------------------------------- 131init([Name]) -> 132 put(ssl_pem_cache, Name), 133 process_flag(trap_exit, true), 134 PemCache = ssl_pkix_db:create_pem_cache(Name), 135 Interval = pem_check_interval(), 136 erlang:send_after(Interval, self(), clear_pem_cache), 137 erlang:system_time(second), 138 {ok, #state{pem_cache = PemCache, 139 last_pem_check = erlang:convert_time_unit(os:system_time(), native, second), 140 clear = Interval 141 }}. 142 143%%-------------------------------------------------------------------- 144-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. 145%% Possible return values not used now. 146%% {reply, reply(), #state{}, timeout()} | 147%% {noreply, #state{}} | 148%% {noreply, #state{}, timeout()} | 149%% {stop, reason(), reply(), #state{}} | 150%% {stop, reason(), #state{}}. 151%% 152%% Description: Handling call messages 153%%-------------------------------------------------------------------- 154handle_call({unconditionally_clear_pem_cache, _},_, 155 #state{pem_cache = PemCache} = State) -> 156 ssl_pkix_db:clear(PemCache), 157 {reply, ok, State}. 158 159%%-------------------------------------------------------------------- 160-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. 161%% Possible return values not used now. 162%% | {noreply, #state{}, timeout()} | 163%% {stop, reason(), #state{}}. 164%% 165%% Description: Handling cast messages 166%%-------------------------------------------------------------------- 167handle_cast({cache_pem, File, Content}, #state{pem_cache = Db} = State) -> 168 ssl_pkix_db:insert(File, Content, Db), 169 {noreply, State}; 170 171handle_cast({invalidate_pem, File}, #state{pem_cache = Db} = State) -> 172 ssl_pkix_db:remove(File, Db), 173 {noreply, State}. 174 175 176%%-------------------------------------------------------------------- 177-spec handle_info(msg(), #state{}) -> {noreply, #state{}}. 178%% Possible return values not used now. 179%% |{noreply, #state{}, timeout()} | 180%% {stop, reason(), #state{}}. 181%% 182%% Description: Handling all non call/cast messages 183%%------------------------------------------------------------------- 184handle_info(clear_pem_cache, #state{pem_cache = PemCache, 185 clear = Interval, 186 last_pem_check = CheckPoint} = State) -> 187 NewCheckPoint = erlang:convert_time_unit(os:system_time(), native, second), 188 start_pem_cache_validator(PemCache, CheckPoint), 189 erlang:send_after(Interval, self(), clear_pem_cache), 190 {noreply, State#state{last_pem_check = NewCheckPoint}}; 191 192handle_info(_Info, State) -> 193 {noreply, State}. 194 195%%-------------------------------------------------------------------- 196-spec terminate(reason(), #state{}) -> ok. 197%% 198%% Description: This function is called by a gen_server when it is about to 199%% terminate. It should be the opposite of Module:init/1 and do any necessary 200%% cleaning up. When it returns, the gen_server terminates with Reason. 201%% The return value is ignored. 202%%-------------------------------------------------------------------- 203terminate(_Reason, #state{}) -> 204 ok. 205 206%%-------------------------------------------------------------------- 207-spec code_change(term(), #state{}, list()) -> {ok, #state{}}. 208%% 209%% Description: Convert process state when code is changed 210%%-------------------------------------------------------------------- 211code_change(_OldVsn, State, _Extra) -> 212 {ok, State}. 213 214%%-------------------------------------------------------------------- 215%%% Internal functions 216%%-------------------------------------------------------------------- 217call(Msg) -> 218 gen_server:call(get(ssl_pem_cache), {Msg, self()}, infinity). 219 220cast(Msg) -> 221 gen_server:cast(get(ssl_pem_cache), Msg). 222 223start_pem_cache_validator(PemCache, CheckPoint) -> 224 spawn_link(?MODULE, init_pem_cache_validator, 225 [[get(ssl_pem_cache), PemCache, CheckPoint]]). 226 227init_pem_cache_validator([CacheName, PemCache, CheckPoint]) -> 228 put(ssl_pem_cache, CacheName), 229 ssl_pkix_db:foldl(fun pem_cache_validate/2, 230 CheckPoint, PemCache). 231 232pem_cache_validate({File, _}, CheckPoint) -> 233 case file:read_file_info(File, [{time, posix}]) of 234 {ok, #file_info{mtime = Time}} when Time < CheckPoint -> 235 ok; 236 _ -> 237 invalidate_pem(File) 238 end, 239 CheckPoint. 240 241pem_check_interval() -> 242 case application:get_env(ssl, ssl_pem_cache_clean) of 243 {ok, Interval} when is_integer(Interval) -> 244 Interval; 245 _ -> 246 ?CLEAR_PEM_CACHE 247 end. 248 249bypass_cache() -> 250 case application:get_env(ssl, bypass_pem_cache) of 251 {ok, Bool} when is_boolean(Bool) -> 252 Bool; 253 _ -> 254 false 255 end. 256