diff options
Diffstat (limited to 'src/catlfish.erl')
-rw-r--r-- | src/catlfish.erl | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/src/catlfish.erl b/src/catlfish.erl index ae4da9d..73066bb 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -3,6 +3,8 @@ -module(catlfish). -export([add_chain/2, entries/2, entry_and_proof/2]). +-export([known_roots/0, update_known_roots/0]). +-include_lib("eunit/include/eunit.hrl"). -define(PROTOCOL_VERSION, 0). @@ -161,3 +163,107 @@ decode_tls_vector(Binary, LengthLen) -> <<Length:LengthLen/integer-unit:8, Rest/binary>> = Binary, <<ExtractedBinary:Length/binary-unit:8, Rest2/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}. |