From 0aeb7d1de8e50dd0fa92e763ce4c8dd3c172dac8 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 15 Oct 2014 16:03:25 +0200 Subject: Implement cert chain validation. NOTE: Presence of and constraints on names are not being validated. --- src/catlfish.erl | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) (limited to 'src/catlfish.erl') diff --git a/src/catlfish.erl b/src/catlfish.erl index e261824..cd871f5 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -3,7 +3,9 @@ -module(catlfish). -export([add_chain/2, entries/2, entry_and_proof/2]). +-export([known_roots/0, update_known_roots/0]). -include("$CTROOT/plop/include/plop.hrl"). +-include_lib("eunit/include/eunit.hrl"). -define(PROTOCOL_VERSION, 0). @@ -162,3 +164,107 @@ decode_tls_vector(Binary, LengthLen) -> <> = Binary, <> = Rest, {ExtractedBinary, Rest2}. + +-define(ROOTS_TABLE, catlfish_roots). + +update_known_roots() -> + case application:get_env(catlfish, known_roots_path) of + {ok, Dir} -> update_known_roots(Dir); + undefined -> [] + end. + +update_known_roots(Directory) -> + known_roots(Directory, update_tab). + +known_roots() -> + case application:get_env(catlfish, known_roots_path) of + {ok, Dir} -> known_roots(Dir, use_cache); + undefined -> [] + end. + +-spec known_roots(file:filename(), use_cache|update_tab) -> list(). +known_roots(Directory, CacheUsage) -> + case ets:info(?ROOTS_TABLE) of + undefined -> + read_pemfiles_from_dir( + ets:new(?ROOTS_TABLE, [set, protected, named_table]), + Directory); + _ -> + case CacheUsage of + use_cache -> + ets:lookup_element(?ROOTS_TABLE, list, 2); + update_tab -> + read_pemfiles_from_dir(?ROOTS_TABLE, Directory) + end + end. + +-spec read_pemfiles_from_dir(ets:tab(), file:filename()) -> list(). +read_pemfiles_from_dir(Tab, Dir) -> + DerList = + case file:list_dir(Dir) of + {error, enoent} -> + []; % FIXME: log enoent + {error, _Reason} -> + []; % FIXME: log Reason + {ok, Filenames} -> + Files = lists:filter( + fun(F) -> + string:equal(".pem", filename:extension(F)) + end, + Filenames), + ders_from_pemfiles(Dir, Files) + end, + true = ets:insert(Tab, {list, DerList}), + DerList. + +ders_from_pemfiles(Dir, Filenames) -> + L = [ders_from_pemfile(filename:join(Dir, X)) || X <- Filenames], + lists:flatten(L). + +ders_from_pemfile(Filename) -> + Pems = case (catch public_key:pem_decode(pems_from_file(Filename))) of + {'EXIT', _} -> []; + P -> P + end, + [der_from_pem(X) || X <- Pems]. + +-include_lib("public_key/include/public_key.hrl"). +der_from_pem(Pem) -> + case Pem of + {_Type, Der, not_encrypted} -> + case (catch public_key:pkix_decode_cert(Der, otp)) of + {'EXIT', _} -> + []; + #'OTPCertificate'{} -> + Der; + _Unknown -> + [] + end; + _ -> [] + end. + +pems_from_file(Filename) -> + {ok, Pems} = file:read_file(Filename), + Pems. + +%%%%%%%%%%%%%%%%%%%% +%% Testing internal functions. +-define(PEMFILES_DIR_OK, "../test/testdata/known-roots"). +-define(PEMFILES_DIR_NONEXISTENT, "../test/testdata/nonexistent-dir"). + +read_pemfiles_test_() -> + {setup, + fun() -> {known_roots(?PEMFILES_DIR_OK, use_cache), + known_roots(?PEMFILES_DIR_OK, use_cache)} + end, + fun(_) -> ets:delete(?ROOTS_TABLE) end, + fun({L, LCached}) -> + [?_assertMatch(7, length(L)), + ?_assertEqual(L, LCached)] + end}. + +read_pemfiles_fail_test_() -> + {setup, + fun() -> known_roots(?PEMFILES_DIR_NONEXISTENT, use_cache) end, + fun(_) -> ets:delete(?ROOTS_TABLE) end, + fun(Empty) -> [?_assertMatch([], Empty)] end}. -- cgit v1.1