aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/ssl_manager.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/ssl_manager.erl')
-rw-r--r--lib/ssl/src/ssl_manager.erl222
1 files changed, 142 insertions, 80 deletions
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 0151426d43..f845b1ecc0 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -24,10 +24,13 @@
-module(ssl_manager).
-behaviour(gen_server).
+-include("ssl_internal.hrl").
+
%% Internal application API
--export([start_link/0, start_link/1,
+-export([start_link/1,
connection_init/2, cache_pem_file/1,
- lookup_trusted_cert/3, client_session_id/3, server_session_id/3,
+ lookup_trusted_cert/3, issuer_candidate/1, client_session_id/4,
+ server_session_id/4,
register_session/2, register_session/3, invalidate_session/2,
invalidate_session/3]).
@@ -40,6 +43,7 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include_lib("kernel/include/file.hrl").
-record(state, {
session_cache,
@@ -58,63 +62,84 @@
%% API
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+-spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}.
+%%
%% 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:
+-spec connection_init(string()| {der, list()}, client | server) -> {ok, reference(), cache_ref()}.
+%%
+%% Description: Do necessary initializations for a new connection.
%%--------------------------------------------------------------------
-connection_init(TrustedcertsFile, Role) ->
- call({connection_init, TrustedcertsFile, Role}).
-
-cache_pem_file(File) ->
- case ssl_certificate_db:lookup_cached_certs(File) of
- [{_,Content}] ->
- {ok, Content};
- [] ->
- call({cache_pem, File})
- end.
-
+connection_init(Trustedcerts, Role) ->
+ call({connection_init, Trustedcerts, Role}).
+%%--------------------------------------------------------------------
+-spec cache_pem_file(string()) -> {ok, term()} | {error, reason()}.
+%%
+%% Description: Cach a pem file and return its content.
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+cache_pem_file(File) ->
+ try file:read_file_info(File) of
+ {ok, #file_info{mtime = LastWrite}} ->
+ cache_pem_file(File, LastWrite)
+ catch
+ _:Reason ->
+ {error, Reason}
+ end.
%%--------------------------------------------------------------------
-lookup_trusted_cert(SerialNumber, Issuer, Ref) ->
+-spec lookup_trusted_cert(reference(), serialnumber(), issuer()) ->
+ undefined |
+ {ok, {der_cert(), #'OTPCertificate'{}}}.
+%%
+%% Description: Lookup the trusted cert with Key = {reference(),
+%% serialnumber(), issuer()}.
+%% --------------------------------------------------------------------
+lookup_trusted_cert(Ref, SerialNumber, Issuer) ->
ssl_certificate_db:lookup_trusted_cert(Ref, SerialNumber, Issuer).
-
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec issuer_candidate(cert_key() | no_candidate) ->
+ {cert_key(), {der_cert(), #'OTPCertificate'{}}} | no_more_candidates.
+%%
+%% Description: Return next issuer candidate.
%%--------------------------------------------------------------------
-client_session_id(Host, Port, SslOpts) ->
- call({client_session_id, Host, Port, SslOpts}).
-
+issuer_candidate(PrevCandidateKey) ->
+ ssl_certificate_db:issuer_candidate(PrevCandidateKey).
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec client_session_id(host(), port_num(), #ssl_options{},
+ der_cert() | undefined) -> session_id().
+%%
+%% Description: Select a session id for the client.
%%--------------------------------------------------------------------
-server_session_id(Port, SuggestedSessionId, SslOpts) ->
- call({server_session_id, Port, SuggestedSessionId, SslOpts}).
+client_session_id(Host, Port, SslOpts, OwnCert) ->
+ call({client_session_id, Host, Port, SslOpts, OwnCert}).
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec server_session_id(host(), port_num(), #ssl_options{}, der_cert()) -> session_id().
+%%
+%% Description: Select a session id for the server.
+%%--------------------------------------------------------------------
+server_session_id(Port, SuggestedSessionId, SslOpts, OwnCert) ->
+ call({server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}).
+
+%%--------------------------------------------------------------------
+-spec register_session(port_num(), #session{}) -> ok.
+-spec register_session(host(), port_num(), #session{}) -> ok.
+%%
+%% Description: Make the session available for reuse.
%%--------------------------------------------------------------------
register_session(Host, Port, Session) ->
cast({register_session, Host, Port, Session}).
register_session(Port, Session) ->
cast({register_session, Port, Session}).
-
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec invalidate_session(port_num(), #session{}) -> ok.
+-spec invalidate_session(host(), port_num(), #session{}) -> ok.
+%%
+%% Description: Make the session unavilable for reuse.
%%--------------------------------------------------------------------
invalidate_session(Host, Port, Session) ->
cast({invalidate_session, Host, Port, Session}).
@@ -127,34 +152,36 @@ invalidate_session(Port, Session) ->
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
+-spec init(list()) -> {ok, #state{}}.
+%% Possible return values not used now.
+%% | {ok, #state{}, timeout()} | ignore | {stop, term()}.
+%%
%% Description: Initiates the server
%%--------------------------------------------------------------------
-init(Opts) ->
+init([Opts]) ->
process_flag(trap_exit, true),
- CacheCb = proplists:get_value(session_cache, Opts, ssl_session_cache),
+ CacheCb = proplists:get_value(session_cb, Opts, ssl_session_cache),
SessionLifeTime =
proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'),
CertDb = ssl_certificate_db:create(),
- SessionCache = CacheCb:init(),
+ SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])),
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_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}
+-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({{connection_init, "", _Role}, Pid}, _From,
@@ -163,52 +190,55 @@ handle_call({{connection_init, "", _Role}, Pid}, _From,
Result = {ok, make_ref(), Cache},
{reply, Result, State};
-handle_call({{connection_init, TrustedcertsFile, _Role}, Pid}, _From,
+handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From,
#state{certificate_db = Db,
session_cache = Cache} = State) ->
erlang:monitor(process, Pid),
Result =
try
- {ok, Ref} = ssl_certificate_db:add_trusted_certs(Pid, TrustedcertsFile, Db),
+ {ok, Ref} = ssl_certificate_db:add_trusted_certs(Pid, Trustedcerts, Db),
{ok, Ref, Cache}
catch
- _:{badmatch, Error} ->
- {error, Error};
- _E:_R ->
- {error, {_R,erlang:get_stacktrace()}}
+ _:Reason ->
+ {error, Reason}
end,
{reply, Result, State};
-handle_call({{client_session_id, Host, Port, SslOpts}, _}, _,
+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),
+ Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
{reply, Id, State};
-handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts}, _},
+handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}, _},
_, #state{session_cache_cb = CacheCb,
session_cache = Cache,
session_lifetime = LifeTime} = State) ->
Id = ssl_session:id(Port, SuggestedSessionId, SslOpts,
- Cache, CacheCb, LifeTime),
+ Cache, CacheCb, LifeTime, OwnCert),
{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
+handle_call({{cache_pem, File, LastWrite}, Pid}, _,
+ #state{certificate_db = Db} = State) ->
+ try ssl_certificate_db:cache_pem_file(Pid, File, LastWrite, Db) of
Result ->
{reply, Result, State}
- catch _:{badmatch, Reason} ->
- {reply, Reason, State};
- _:Reason ->
+ catch
+ _:Reason ->
{reply, {error, Reason}, State}
end;
-
-handle_call(_,_, State) ->
- {reply, ok, State}.
+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}.
+
%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, 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({register_session, Host, Port, Session},
@@ -239,12 +269,28 @@ 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}.
+ {noreply, 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.
%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, 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(validate_sessions, #state{session_cache_cb = CacheCb,
@@ -264,12 +310,14 @@ handle_info({'EXIT', _, _}, State) ->
handle_info({'DOWN', _Ref, _Type, _Pid, ecacertfile}, State) ->
{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 = #state{certificate_db = Db}) ->
+ #state{certificate_db = Db} = State) ->
ssl_certificate_db:remove_trusted_certs(Pid, Db),
{noreply, State};
@@ -277,7 +325,8 @@ handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
+-spec terminate(reason(), #state{}) -> term().
+%%
%% 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.
@@ -293,7 +342,8 @@ terminate(_Reason, #state{certificate_db = Db,
ok.
%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+-spec code_change(term(), #state{}, list()) -> {ok, #state{}}.
+%%
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
@@ -332,10 +382,22 @@ init_session_validator([Cache, CacheCb, LifeTime]) ->
CacheCb:foldl(fun session_validation/2,
LifeTime, Cache).
-session_validation({{Host, Port, _}, Session}, LifeTime) ->
+session_validation({{{Host, Port}, _}, Session}, LifeTime) ->
validate_session(Host, Port, Session, LifeTime),
LifeTime;
session_validation({{Port, _}, Session}, LifeTime) ->
validate_session(Port, Session, LifeTime),
LifeTime.
-
+
+cache_pem_file(File, LastWrite) ->
+ case ssl_certificate_db:lookup_cached_certs(File) of
+ [{_, {Mtime, Content}}] ->
+ case LastWrite of
+ Mtime ->
+ {ok, Content};
+ _ ->
+ call({recache_pem, File, LastWrite})
+ end;
+ [] ->
+ call({cache_pem, File, LastWrite})
+ end.