1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2007-2020. 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: Storage for trusted certificates 23%%---------------------------------------------------------------------- 24 25-module(ssl_pkix_db). 26 27-include("ssl_internal.hrl"). 28-include_lib("public_key/include/public_key.hrl"). 29-include_lib("kernel/include/file.hrl"). 30-include_lib("kernel/include/logger.hrl"). 31 32-export([create/1, create_pem_cache/1, 33 add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3, 34 refresh_trusted_certs/1, 35 refresh_trusted_certs/2, 36 extract_trusted_certs/1, 37 remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, 38 ref_count/3, lookup_trusted_cert/4, foldl/3, select_cert_by_issuer/2, 39 decode_pem_file/1, lookup/2]). 40 41%%==================================================================== 42%% Internal application API 43%%==================================================================== 44 45%%-------------------------------------------------------------------- 46-spec create(atom()) -> [db_handle(),...]. 47%% 48%% Description: Creates a new certificate db. 49%% Note: lookup_trusted_cert/4 may be called from any process but only 50%% the process that called create may call the other functions. 51%%-------------------------------------------------------------------- 52create(PEMCacheName) -> 53 [%% Let connection process delete trusted certs 54 %% that can only belong to one connection. (Supplied directly 55 %% on DER format to ssl:connect/listen.) 56 ets:new(ssl_otp_cacertificate_db, [set, public]), 57 %% Let connection processes call ref_count/3 directly 58 {ets:new(ssl_otp_ca_file_ref, [set, public]), 59 ets:new(ssl_otp_ca_ref_file_mapping, [set, protected]) 60 }, 61 %% Lookups in named table owned by ssl_pem_cache process 62 PEMCacheName, 63 %% Default cache 64 {ets:new(ssl_otp_crl_cache, [set, protected]), 65 ets:new(ssl_otp_crl_issuer_mapping, [bag, protected])} 66 ]. 67 68create_pem_cache(Name) -> 69 ets:new(Name, [named_table, set, protected]). 70 71%%-------------------------------------------------------------------- 72-spec remove([db_handle()]) -> ok. 73%% 74%% Description: Removes database db 75%%-------------------------------------------------------------------- 76remove(Dbs) -> 77 lists:foreach(fun({Db0, Db1}) -> 78 true = ets:delete(Db0), 79 true = ets:delete(Db1); 80 (undefined) -> 81 ok; 82 (Name) when is_atom(Name) -> 83 NormalName = ssl_pem_cache:name(normal), 84 DistName = ssl_pem_cache:name(dist), 85 case Name of 86 NormalName -> 87 ok; 88 DistName -> 89 ok; 90 _ -> 91 true = ets:delete(Name) 92 end; 93 (Db) -> 94 true = ets:delete(Db) 95 end, Dbs). 96 97%%-------------------------------------------------------------------- 98-spec lookup_trusted_cert(db_handle(), certdb_ref(), serialnumber(), issuer()) -> 99 undefined | {ok, public_key:combined_cert()}. 100 101%% 102%% Description: Retrives the trusted certificate identified by 103%% <SerialNumber, Issuer>. Ref is used as it is specified 104%% for each connection which certificates are trusted. 105%%-------------------------------------------------------------------- 106lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) when is_reference(Ref) -> 107 case lookup({Ref, SerialNumber, Issuer}, DbHandle) of 108 undefined -> 109 undefined; 110 [Certs] -> 111 {ok, Certs} 112 end; 113lookup_trusted_cert(_DbHandle, {extracted,Certs}, SerialNumber, Issuer) -> 114 try 115 [throw(Cert) 116 || {decoded, {{_Ref,CertSerial,CertIssuer}, Cert}} <- Certs, 117 CertSerial =:= SerialNumber, CertIssuer =:= Issuer], 118 undefined 119 catch 120 Cert -> 121 {ok, Cert} 122 end. 123 124%%-------------------------------------------------------------------- 125-spec add_trusted_certs(pid(), {erlang:timestamp(), string()} | 126 {der, list()}, [db_handle()]) -> {ok, [db_handle()]}. 127%% 128%% Description: Adds the trusted certificates from file <File> to the 129%% runtime database. Returns Ref that should be handed to lookup_trusted_cert 130%% together with the cert serialnumber and issuer. 131%%-------------------------------------------------------------------- 132add_trusted_certs(_Pid, {extracted, _} = Certs, _) -> 133 {ok, Certs}; 134 135add_trusted_certs(_Pid, {der, DerList}, [CertDb, _,_ | _]) -> 136 NewRef = make_ref(), 137 add_certs_from_der(DerList, NewRef, CertDb), 138 {ok, NewRef}; 139 140add_trusted_certs(_Pid, File, [ _, {RefDb, FileMapDb} | _] = Db) -> 141 case lookup(File, FileMapDb) of 142 [Ref] -> 143 ref_count(Ref, RefDb, 1), 144 {ok, Ref}; 145 undefined -> 146 new_trusted_cert_entry(File, Db) 147 end. 148 149refresh_trusted_certs(File, [CertsDb, {_, FileMapDb} | _]) -> 150 case lookup(File, FileMapDb) of 151 [Ref] -> 152 {ok, Content} = decode_pem_file(File), 153 remove_trusted_certs(Ref, CertsDb), 154 add_certs_from_pem(Content, Ref, CertsDb); 155 undefined -> 156 ok 157 end. 158refresh_trusted_certs([_, {_, FileMapDb} | _] = Db) -> 159 Refresh = fun({File, _}, Acc) -> 160 refresh_trusted_certs(File, Db), 161 Acc 162 end, 163 foldl(Refresh, refresh, FileMapDb). 164 165extract_trusted_certs({der, DerList}) -> 166 {ok, {extracted, certs_from_der(DerList)}}; 167extract_trusted_certs({der_otp, DerList}) -> 168 {ok, {extracted, certs_from_der(DerList)}}; 169extract_trusted_certs(File) -> 170 case file:read_file(File) of 171 {ok, PemBin} -> 172 Content = public_key:pem_decode(PemBin), 173 DerList = [Cert || {'Certificate', Cert, not_encrypted} <- Content], 174 {ok, {extracted, certs_from_der(DerList)}}; 175 Error -> 176 %% Have to simulate a failure happening in a server for 177 %% external handlers. 178 {error, {badmatch, Error}} 179 end. 180 181-spec decode_pem_file(binary()) -> {ok, term()} | {error, term()}. 182decode_pem_file(File) -> 183 case file:read_file(File) of 184 {ok, PemBin} -> 185 Content = public_key:pem_decode(PemBin), 186 {ok, Content}; 187 Error -> 188 %% Have to simulate a failure happening in a server for 189 %% external handlers. 190 {error, {badmatch, Error}} 191 end. 192 193%%-------------------------------------------------------------------- 194-spec remove_trusted_certs(reference(), db_handle()) -> ok. 195%% 196%% Description: Removes all trusted certificates refernced by <Ref>. 197%%-------------------------------------------------------------------- 198remove_trusted_certs(Ref, CertsDb) -> 199 remove_certs(Ref, CertsDb). 200 201%%-------------------------------------------------------------------- 202-spec remove(term(), db_handle()) -> ok. 203%% 204%% Description: Removes an element in a <Db>. 205%%-------------------------------------------------------------------- 206remove(Key, Db) -> 207 ets:delete(Db, Key), 208 ok. 209 210%%-------------------------------------------------------------------- 211-spec remove(term(), term(), db_handle()) -> ok. 212%% 213%% Description: Removes an element in a <Db>. 214%%-------------------------------------------------------------------- 215remove(Key, Data, Db) -> 216 ets:delete_object(Db, {Key, Data}), 217 ok. 218 219%%-------------------------------------------------------------------- 220-spec lookup(term(), db_handle()) -> [term()] | undefined. 221%% 222%% Description: Looks up an element in a <Db>. 223%%-------------------------------------------------------------------- 224lookup(Key, Db) -> 225 case ets:lookup(Db, Key) of 226 [] -> 227 undefined; 228 Contents -> 229 Pick = fun({_, Data}) -> Data; 230 ({_,_,Data}) -> Data 231 end, 232 [Pick(Data) || Data <- Contents] 233 end. 234%%-------------------------------------------------------------------- 235-spec foldl(fun((_,_) -> term()), term(), db_handle()) -> term(). 236%% 237%% Description: Calls Fun(Elem, AccIn) on successive elements of the 238%% cache, starting with AccIn == Acc0. Fun/2 must return a new 239%% accumulator which is passed to the next call. The function returns 240%% the final value of the accumulator. Acc0 is returned if the certifate 241%% db is empty. 242%%-------------------------------------------------------------------- 243foldl(Fun, Acc0, Cache) -> 244 ets:foldl(Fun, Acc0, Cache). 245 246 247select_cert_by_issuer(Cache, Issuer) -> 248 ets:select(Cache, [{{{'_','_', Issuer},{'_', '$1'}},[],['$$']}]). 249 250%%-------------------------------------------------------------------- 251-spec ref_count(term(), db_handle(), integer()) -> integer(). 252%% 253%% Description: Updates a reference counter in a <Db>. 254%%-------------------------------------------------------------------- 255ref_count({extracted, _}, _Db, _N) -> 256 not_cached; 257ref_count(Key, {Db, _}, N) -> 258 ref_count(Key, Db, N); 259ref_count(Key, Db, N) -> 260 ets:update_counter(Db,Key,N). 261 262%%-------------------------------------------------------------------- 263-spec clear(db_handle()) -> ok. 264%% 265%% Description: Clears the cache 266%%-------------------------------------------------------------------- 267clear(Db) -> 268 true = ets:delete_all_objects(Db), 269 ok. 270 271%%-------------------------------------------------------------------- 272-spec db_size(db_handle()) -> integer(). 273%% 274%% Description: Returns the size of the db 275%%-------------------------------------------------------------------- 276db_size(Db) -> 277 ets:info(Db, size). 278 279%%-------------------------------------------------------------------- 280-spec insert(Key::term(), Data::term(), Db::db_handle()) -> ok. 281%% 282%% Description: Inserts data into <Db> 283%%-------------------------------------------------------------------- 284insert(Key, Data, Db) -> 285 true = ets:insert(Db, {Key, Data}), 286 ok. 287 288%%-------------------------------------------------------------------- 289%%% Internal functions 290%%-------------------------------------------------------------------- 291init_ref_db(Ref, File, {RefDb, FileMapDb}) -> 292 true = ets:insert(RefDb, {Ref, 1}), 293 true = ets:insert(FileMapDb, {File, Ref}). 294 295remove_certs(Ref, CertsDb) -> 296 true = ets:match_delete(CertsDb, {{Ref, '_', '_'}, '_'}), 297 ok. 298 299add_certs_from_der(DerList, Ref, CertsDb) -> 300 Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, 301 [Add(Cert) || Cert <- DerList], 302 ok. 303 304certs_from_der(DerList) -> 305 Ref = make_ref(), 306 [Decoded || Cert <- DerList, 307 Decoded <- [decode_certs(Ref, Cert)], 308 Decoded =/= undefined]. 309 310add_certs_from_pem(PemEntries, Ref, CertsDb) -> 311 Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, 312 [Add(Cert) || {'Certificate', Cert, not_encrypted} <- PemEntries], 313 ok. 314 315add_certs(Cert, Ref, CertsDb) -> 316 try 317 {decoded, {Key, Val}} = decode_certs(Ref, Cert), 318 insert(Key, Val, CertsDb) 319 catch 320 error:_ -> 321 ok 322 end. 323 324decode_certs(Ref, #cert{otp=ErlCert} = Cert) -> 325 TBSCertificate = ErlCert#'OTPCertificate'.tbsCertificate, 326 SerialNumber = TBSCertificate#'OTPTBSCertificate'.serialNumber, 327 Issuer = public_key:pkix_normalize_name( 328 TBSCertificate#'OTPTBSCertificate'.issuer), 329 {decoded, {{Ref, SerialNumber, Issuer}, Cert}}; 330decode_certs(Ref, Der) -> 331 try public_key:pkix_decode_cert(Der, otp) of 332 ErlCert -> 333 decode_certs(Ref, #cert{der=Der, otp=ErlCert}) 334 catch error:_ -> 335 ?LOG_NOTICE("SSL WARNING: Ignoring a CA cert as " 336 "it could not be correctly decoded.~n"), 337 undefined 338 end. 339 340new_trusted_cert_entry(File, [CertsDb, RefsDb, _ | _]) -> 341 case decode_pem_file(File) of 342 {ok, Content} -> 343 Ref = make_ref(), 344 init_ref_db(Ref, File, RefsDb), 345 ok = ssl_pem_cache:insert(File, Content), 346 add_certs_from_pem(Content, Ref, CertsDb), 347 {ok, Ref}; 348 Error -> 349 Error 350 end. 351 352add_crls([_,_,_, {_, Mapping} | _], ?NO_DIST_POINT, CRLs) -> 353 [add_crls(CRL, Mapping) || CRL <- CRLs]; 354add_crls([_,_,_, {Cache, Mapping} | _], Path, CRLs) -> 355 insert(Path, CRLs, Cache), 356 [add_crls(CRL, Mapping) || CRL <- CRLs]. 357 358add_crls(CRL, Mapping) -> 359 insert(crl_issuer(CRL), CRL, Mapping). 360 361remove_crls([_,_,_, {_, Mapping} | _], {?NO_DIST_POINT, CRLs}) -> 362 [rm_crls(CRL, Mapping) || CRL <- CRLs]; 363 364remove_crls([_,_,_, {Cache, Mapping} | _], Path) -> 365 case lookup(Path, Cache) of 366 undefined -> 367 ok; 368 CRLs -> 369 remove(Path, Cache), 370 [rm_crls(CRL, Mapping) || CRL <- CRLs] 371 end. 372 373rm_crls(CRL, Mapping) -> 374 remove(crl_issuer(CRL), CRL, Mapping). 375 376crl_issuer(DerCRL) -> 377 CRL = public_key:der_decode('CertificateList', DerCRL), 378 TBSCRL = CRL#'CertificateList'.tbsCertList, 379 TBSCRL#'TBSCertList'.issuer. 380 381