summaryrefslogtreecommitdiff
path: root/src/catlfish.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/catlfish.erl')
-rw-r--r--src/catlfish.erl106
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}.