diff options
Diffstat (limited to 'lib/ssl/src/ssl_manager.erl')
-rw-r--r-- | lib/ssl/src/ssl_manager.erl | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl new file mode 100644 index 0000000000..6b83c2ea46 --- /dev/null +++ b/lib/ssl/src/ssl_manager.erl @@ -0,0 +1,340 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%---------------------------------------------------------------------- +%% Purpose: Manages ssl sessions and trusted certifacates +%%---------------------------------------------------------------------- + +-module(ssl_manager). +-behaviour(gen_server). + +%% Internal application API +-export([start_link/0, start_link/1, + connection_init/2, cache_pem_file/1, + lookup_trusted_cert/3, client_session_id/3, server_session_id/3, + register_session/2, register_session/3, invalidate_session/2, + invalidate_session/3]). + +% Spawn export +-export([init_session_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"). + +-record(state, { + session_cache, + session_cache_cb, + session_lifetime, + certificate_db, + session_validation_timer + }). + +-define('24H_in_msec', 8640000). +-define('24H_in_sec', 8640). +-define(SESSION_VALIDATION_INTERVAL, 60000). +-define(CERTIFICATE_CACHE_CLEANUP, 30000). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +start_link(Opts) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []). + +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%%-------------------------------------------------------------------- +connection_init(TrustedcertsFile, Role) -> + call({connection_init, TrustedcertsFile, Role}). + +cache_pem_file(File) -> + case ets:lookup(ssl_file_to_ref,File) of + [{_,_,Content}] -> + {ok, Content}; + [] -> + {ok, Db} = call({cache_pem, File}), + [{_,_,Content}] = ets:lookup(Db,File), + {ok, Content} + end. + +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%%-------------------------------------------------------------------- +lookup_trusted_cert(SerialNumber, Issuer, Ref) -> + ssl_certificate_db:lookup_trusted_cert(Ref, SerialNumber, Issuer). + +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%%-------------------------------------------------------------------- +client_session_id(Host, Port, SslOpts) -> + call({client_session_id, Host, Port, SslOpts}). + +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%%-------------------------------------------------------------------- +server_session_id(Port, SuggestedSessionId, SslOpts) -> + call({server_session_id, Port, SuggestedSessionId, SslOpts}). + +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%%-------------------------------------------------------------------- +register_session(Host, Port, Session) -> + cast({register_session, Host, Port, Session}). + +register_session(Port, Session) -> + cast({register_session, Port, Session}). + +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%%-------------------------------------------------------------------- +invalidate_session(Host, Port, Session) -> + cast({invalidate_session, Host, Port, Session}). + +invalidate_session(Port, Session) -> + cast({invalidate_session, Port, Session}). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init(Opts) -> + process_flag(trap_exit, true), + CacheCb = proplists:get_value(session_cache, Opts, ssl_session_cache), + SessionLifeTime = + proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'), + CertDb = ssl_certificate_db:create(), + SessionCache = CacheCb:init(), + Timer = erlang:send_after(SessionLifeTime * 1000, + self(), validate_sessions), + {ok, #state{certificate_db = CertDb, + session_cache = SessionCache, + session_cache_cb = CacheCb, + session_lifetime = SessionLifeTime , + session_validation_timer = Timer}}. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call({{connection_init, "", _Role}, Pid}, _From, + #state{session_cache = Cache} = State) -> + erlang:monitor(process, Pid), + Result = {ok, make_ref(), Cache}, + {reply, Result, State}; + +handle_call({{connection_init, TrustedcertsFile, _Role}, Pid}, _From, + #state{certificate_db = Db, + session_cache = Cache} = State) -> + erlang:monitor(process, Pid), + Result = + case (catch ssl_certificate_db:add_trusted_certs(Pid, + TrustedcertsFile, + Db)) of + {ok, Ref} -> + {ok, Ref, Cache}; + Error -> + {error, Error} + end, + {reply, Result, State}; + +handle_call({{client_session_id, Host, Port, SslOpts}, _}, _, + #state{session_cache = Cache, + session_cache_cb = CacheCb} = State) -> + Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb), + {reply, Id, State}; + +handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts}, _}, + _, #state{session_cache_cb = CacheCb, + session_cache = Cache, + session_lifetime = LifeTime} = State) -> + Id = ssl_session:id(Port, SuggestedSessionId, SslOpts, + Cache, CacheCb, LifeTime), + {reply, Id, State}; + +handle_call({{cache_pem, File},Pid}, _, State = #state{certificate_db = Db}) -> + try ssl_certificate_db:cache_pem_file(Pid,File,Db) of + Result -> + {reply, Result, State} + catch _:Reason -> + {reply, {error, Reason}, State} + end; + +handle_call(_,_, State) -> + {reply, ok, State}. +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast({register_session, Host, Port, Session}, + #state{session_cache = Cache, + session_cache_cb = CacheCb} = State) -> + TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), + NewSession = Session#session{time_stamp = TimeStamp}, + CacheCb:update(Cache, {{Host, Port}, + NewSession#session.session_id}, NewSession), + {noreply, State}; + +handle_cast({register_session, Port, Session}, + #state{session_cache = Cache, + session_cache_cb = CacheCb} = State) -> + TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), + NewSession = Session#session{time_stamp = TimeStamp}, + CacheCb:update(Cache, {Port, NewSession#session.session_id}, NewSession), + {noreply, State}; + +handle_cast({invalidate_session, Host, Port, + #session{session_id = ID}}, + #state{session_cache = Cache, + session_cache_cb = CacheCb} = State) -> + CacheCb:delete(Cache, {{Host, Port}, ID}), + {noreply, State}; + +handle_cast({invalidate_session, Port, #session{session_id = ID}}, + #state{session_cache = Cache, + session_cache_cb = CacheCb} = State) -> + CacheCb:delete(Cache, {Port, ID}), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info(validate_sessions, #state{session_cache_cb = CacheCb, + session_cache = Cache, + session_lifetime = LifeTime + } = State) -> + Timer = erlang:send_after(?SESSION_VALIDATION_INTERVAL, + self(), validate_sessions), + start_session_validator(Cache, CacheCb, LifeTime), + {noreply, State#state{session_validation_timer = Timer}}; + +handle_info({'EXIT', _, _}, State) -> + %% Session validator died!! Do we need to take any action? + %% maybe error log + {noreply, State}; + +handle_info({'DOWN', _Ref, _Type, _Pid, ecacertfile}, State) -> + {noreply, State}; + +handle_info({'DOWN', _Ref, _Type, Pid, _Reason}, State) -> + erlang:send_after(?CERTIFICATE_CACHE_CLEANUP, self(), + {remove_trusted_certs, Pid}), + {noreply, State}; +handle_info({remove_trusted_certs, Pid}, + State = #state{certificate_db = Db}) -> + ssl_certificate_db:remove_trusted_certs(Pid, Db), + {noreply, State}; + +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% 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{certificate_db = Db, + session_cache = SessionCache, + session_cache_cb = CacheCb, + session_validation_timer = Timer}) -> + erlang:cancel_timer(Timer), + ssl_certificate_db:remove(Db), + CacheCb:terminate(SessionCache), + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +call(Msg) -> + gen_server:call(?MODULE, {Msg, self()}, infinity). + +cast(Msg) -> + gen_server:cast(?MODULE, Msg). + +validate_session(Host, Port, Session, LifeTime) -> + case ssl_session:valid_session(Session, LifeTime) of + true -> + ok; + false -> + invalidate_session(Host, Port, Session) + end. + +validate_session(Port, Session, LifeTime) -> + case ssl_session:valid_session(Session, LifeTime) of + true -> + ok; + false -> + invalidate_session(Port, Session) + end. + +start_session_validator(Cache, CacheCb, LifeTime) -> + spawn_link(?MODULE, init_session_validator, + [[Cache, CacheCb, LifeTime]]). + +init_session_validator([Cache, CacheCb, LifeTime]) -> + CacheCb:foldl(fun session_validation/2, + LifeTime, Cache). + +session_validation({{Host, Port, _}, Session}, LifeTime) -> + validate_session(Host, Port, Session, LifeTime), + LifeTime; +session_validation({{Port, _}, Session}, LifeTime) -> + validate_session(Port, Session, LifeTime), + LifeTime. + |