aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/ssl_pkix_db.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/ssl_pkix_db.erl')
-rw-r--r--lib/ssl/src/ssl_pkix_db.erl253
1 files changed, 253 insertions, 0 deletions
diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl
new file mode 100644
index 0000000000..9de50c8f26
--- /dev/null
+++ b/lib/ssl/src/ssl_pkix_db.erl
@@ -0,0 +1,253 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2013. 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: Storage for trusted certificates
+%%----------------------------------------------------------------------
+
+-module(ssl_pkix_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, insert/3, remove/2, clear/1, db_size/1,
+ ref_count/3, lookup_trusted_cert/4, foldl/3,
+ lookup_cached_pem/2, cache_pem_file/2, cache_pem_file/3,
+ lookup/2]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec create() -> [db_handle(),...].
+%%
+%% Description: Creates a new certificate db.
+%% Note: lookup_trusted_cert/4 may be called from any process but only
+%% the process that called create may call the other functions.
+%%--------------------------------------------------------------------
+create() ->
+ [%% 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()]) -> ok.
+%%
+%% Description: Removes database db
+%%--------------------------------------------------------------------
+remove(Dbs) ->
+ lists:foreach(fun(Db) ->
+ true = ets:delete(Db)
+ end, Dbs).
+
+%%--------------------------------------------------------------------
+-spec lookup_trusted_cert(db_handle(), certdb_ref(), serialnumber(), issuer()) ->
+ undefined | {ok, {der_cert(), #'OTPCertificate'{}}}.
+
+%%
+%% Description: Retrives the trusted certificate identified by
+%% <SerialNumber, Issuer>. Ref is used as it is specified
+%% for each connection which certificates are trusted.
+%%--------------------------------------------------------------------
+lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) ->
+ case lookup({Ref, SerialNumber, Issuer}, DbHandle) of
+ undefined ->
+ undefined;
+ [Certs] ->
+ {ok, Certs}
+ end.
+
+lookup_cached_pem([_, _, PemChache], MD5) ->
+ lookup_cached_pem(PemChache, MD5);
+lookup_cached_pem(PemChache, MD5) ->
+ lookup(MD5, PemChache).
+
+%%--------------------------------------------------------------------
+-spec add_trusted_certs(pid(), {erlang:timestamp(), string()} |
+ {der, list()}, [db_handle()]) -> {ok, [db_handle()]}.
+%%
+%% Description: Adds the trusted certificates from file <File> to the
+%% runtime database. Returns Ref that should be handed to lookup_trusted_cert
+%% together with the cert serialnumber and issuer.
+%%--------------------------------------------------------------------
+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, RefDb, PemChache] = Db) ->
+ MD5 = crypto:hash(md5, File),
+ case lookup_cached_pem(Db, MD5) of
+ [{_Content, Ref}] ->
+ ref_count(Ref, RefDb, 1),
+ {ok, Ref};
+ [Content] ->
+ Ref = make_ref(),
+ update_counter(Ref, 1, RefDb),
+ insert(MD5, {Content, Ref}, PemChache),
+ add_certs_from_pem(Content, Ref, CertsDb),
+ {ok, Ref};
+ undefined ->
+ new_trusted_cert_entry({MD5, File}, Db)
+ end.
+%%--------------------------------------------------------------------
+-spec cache_pem_file({binary(), binary()}, [db_handle()]) -> {ok, term()}.
+-spec cache_pem_file(reference(), {binary(), binary()}, [db_handle()]) -> {ok, term()}.
+%%
+%% Description: Cache file as binary in DB
+%%--------------------------------------------------------------------
+cache_pem_file({MD5, File}, [_CertsDb, _RefDb, PemChache]) ->
+ {ok, PemBin} = file:read_file(File),
+ Content = public_key:pem_decode(PemBin),
+ insert(MD5, Content, PemChache),
+ {ok, Content}.
+
+cache_pem_file(Ref, {MD5, File}, [_CertsDb, _RefDb, PemChache]) ->
+ {ok, PemBin} = file:read_file(File),
+ Content = public_key:pem_decode(PemBin),
+ insert(MD5, {Content, Ref}, PemChache),
+ {ok, Content}.
+
+%%--------------------------------------------------------------------
+-spec remove_trusted_certs(reference(), db_handle()) -> ok.
+%%
+%% Description: Removes all trusted certificates refernced by <Ref>.
+%%--------------------------------------------------------------------
+remove_trusted_certs(Ref, CertsDb) ->
+ remove_certs(Ref, CertsDb).
+
+%%--------------------------------------------------------------------
+-spec remove(term(), db_handle()) -> ok.
+%%
+%% Description: Removes an element in a <Db>.
+%%--------------------------------------------------------------------
+remove(Key, Db) ->
+ ets:delete(Db, Key),
+ ok.
+
+%%--------------------------------------------------------------------
+-spec lookup(term(), db_handle()) -> [term()] | undefined.
+%%
+%% Description: Looks up an element in a <Db>.
+%%--------------------------------------------------------------------
+lookup(Key, Db) ->
+ case ets:lookup(Db, Key) of
+ [] ->
+ undefined;
+ Contents ->
+ Pick = fun({_, Data}) -> Data;
+ ({_,_,Data}) -> Data
+ end,
+ [Pick(Data) || Data <- Contents]
+ end.
+%%--------------------------------------------------------------------
+-spec foldl(fun((_,_) -> term()), term(), db_handle()) -> term().
+%%
+%% Description: Calls Fun(Elem, AccIn) on successive elements of the
+%% cache, starting with AccIn == Acc0. Fun/2 must return a new
+%% accumulator which is passed to the next call. The function returns
+%% the final value of the accumulator. Acc0 is returned if the certifate
+%% db is empty.
+%%--------------------------------------------------------------------
+foldl(Fun, Acc0, Cache) ->
+ ets:foldl(Fun, Acc0, Cache).
+
+%%--------------------------------------------------------------------
+-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 clear(db_handle()) -> ok.
+%%
+%% Description: Clears the cache
+%%--------------------------------------------------------------------
+clear(Db) ->
+ true = ets:delete_all_objects(Db),
+ ok.
+
+%%--------------------------------------------------------------------
+-spec db_size(db_handle()) -> integer().
+%%
+%% Description: Returns the size of the db
+%%--------------------------------------------------------------------
+db_size(Db) ->
+ ets:info(Db, size).
+
+%%--------------------------------------------------------------------
+-spec insert(Key::term(), Data::term(), Db::db_handle()) -> ok.
+%%
+%% Description: Inserts data into <Db>
+%%--------------------------------------------------------------------
+insert(Key, Data, Db) ->
+ true = ets:insert(Db, {Key, Data}),
+ ok.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+update_counter(Key, Count, Db) ->
+ true = ets:insert(Db, {Key, Count}),
+ ok.
+
+remove_certs(Ref, CertsDb) ->
+ true = ets:match_delete(CertsDb, {{Ref, '_', '_'}, '_'}),
+ ok.
+
+add_certs_from_der(DerList, Ref, CertsDb) ->
+ Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end,
+ [Add(Cert) || Cert <- DerList],
+ ok.
+
+add_certs_from_pem(PemEntries, Ref, CertsDb) ->
+ Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end,
+ [Add(Cert) || {'Certificate', Cert, not_encrypted} <- PemEntries],
+ ok.
+
+add_certs(Cert, Ref, CertsDb) ->
+ try ErlCert = public_key:pkix_decode_cert(Cert, otp),
+ TBSCertificate = ErlCert#'OTPCertificate'.tbsCertificate,
+ SerialNumber = TBSCertificate#'OTPTBSCertificate'.serialNumber,
+ Issuer = public_key:pkix_normalize_name(
+ TBSCertificate#'OTPTBSCertificate'.issuer),
+ insert({Ref, SerialNumber, Issuer}, {Cert,ErlCert}, CertsDb)
+ catch
+ error:_ ->
+ Report = io_lib:format("SSL WARNING: Ignoring a CA cert as "
+ "it could not be correctly decoded.~n", []),
+ error_logger:info_report(Report)
+ end.
+
+new_trusted_cert_entry(FileRef, [CertsDb, RefDb, _] = Db) ->
+ Ref = make_ref(),
+ update_counter(Ref, 1, RefDb),
+ {ok, Content} = cache_pem_file(Ref, FileRef, Db),
+ add_certs_from_pem(Content, Ref, CertsDb),
+ {ok, Ref}.