summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/catlfish.erl4
-rw-r--r--src/dnssecport.erl130
-rw-r--r--src/v1.erl55
3 files changed, 163 insertions, 26 deletions
diff --git a/src/catlfish.erl b/src/catlfish.erl
index e3b5939..711deaa 100644
--- a/src/catlfish.erl
+++ b/src/catlfish.erl
@@ -286,7 +286,7 @@ entryhash_from_entry(PackedEntry) ->
crypto:hash(sha256, [Cert | Chain]).
%% Private functions.
--spec pack_entry(normal|precert, binary(), binary(), [binary()]) -> binary().
+-spec pack_entry(normal|precert, binary(), binary(), [binary()]) -> list().
pack_entry(Type, MTLText, EndEntityCert, CertChain) ->
[{<<"MTL1">>, MTLText},
{case Type of
@@ -297,7 +297,7 @@ pack_entry(Type, MTLText, EndEntityCert, CertChain) ->
list_to_binary(
[tlv:encode(<<"X509">>, E) || E <- CertChain])}].
--spec unpack_entry(binary()) -> {normal|precert, binary(), binary(), [binary()]}.
+-spec unpack_entry(list()) -> {normal|precert, binary(), binary(), [binary()]}.
unpack_entry(Entry) ->
[{<<"MTL1">>, MTLText}|Rest1] = Entry,
[{EECType, EndEntityCert}|Rest2] = Rest1,
diff --git a/src/dnssecport.erl b/src/dnssecport.erl
new file mode 100644
index 0000000..804e727
--- /dev/null
+++ b/src/dnssecport.erl
@@ -0,0 +1,130 @@
+%%% Copyright (c) 2016, NORDUnet A/S.
+%%% See LICENSE for licensing information.
+
+-module(dnssecport).
+-behaviour(gen_server).
+-export([start_link/0, stop/0]).
+-export([validate/2]).
+%% gen_server callbacks.
+-export([init/1, handle_call/3, terminate/2, handle_cast/2, handle_info/2,
+ code_change/3]).
+
+-include_lib("eunit/include/eunit.hrl").
+
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE,
+ [code:priv_dir(catlfish) ++ "/dnssecport"], []).
+
+stop() ->
+ gen_server:call(?MODULE, stop).
+
+validate(ValidateRR, SupportRRs) ->
+ gen_server:call(?MODULE, {validate, [ValidateRR, SupportRRs]}).
+
+-record(state, {port :: port()}).
+
+init(Program) ->
+ lager:debug("starting dnssec service"),
+ Port = create_port(Program, []), % TODO: Pass path to dir with trust root.
+ {ok, #state{port = Port}}.
+
+handle_call(stop, _From, State) ->
+ lager:debug("dnssec stop request received"),
+ stop_port(State);
+handle_call({validate, [ValidateRR, SupportRRs]}, _From, State) ->
+ lager:debug("dnssec validate incoming request: ~p", [ValidateRR]),
+ case State#state.port of
+ undefined ->
+ {error, noport};
+ Port when is_port(Port) ->
+ S = list_to_binary(SupportRRs),
+ Port ! {self(), {command, <<ValidateRR/binary, S/binary>>}},
+ receive
+ {Port, {data, Response}} ->
+ lager:debug("received response from dnssec port: ~p",
+ [Response]),
+ case Response of
+ "valid" ->
+ {reply, ok, State};
+ Err ->
+ {reply, {error, Err}, State}
+ end;
+ {Port, {exit_status, ExitStatus}} ->
+ lager:error("dnssec port ~p exiting with status ~p",
+ [Port, ExitStatus]),
+ {stop, portexit, State#state{port = undefined}}
+ after
+ 3000 ->
+ lager:error("dnssec port timeout"),
+ {stop, timeout, State}
+ end
+ end.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+terminate(Reason, _State) ->
+ lager:info("dnssec port terminating: ~p", [Reason]),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%
+create_port(Program, Args) ->
+ open_port({spawn_executable, Program},
+ [{args, Args},
+ exit_status, % Let us know if process dies.
+ {packet, 4}]).
+
+stop_port(State) ->
+ Port = State#state.port,
+ Port ! {self(), close},
+ receive
+ {Port, closed} ->
+ {stop, closed, State#state{port = undefined}};
+ {Port, Msg} ->
+ lager:debug("message received from dying port: ~p", [Msg]),
+ {stop, unknown, State#state{port = undefined}}
+ after
+ 2000 ->
+ lager:error("dnssec port ~p refuses to die", [Port]),
+ {stop, timeout, State}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%
+%% Unit tests.
+start_test_port(TestType) ->
+ Port = create_port("priv/dnssecport", ["--testmode", atom_to_list(TestType)]),
+ ?debugFmt("Port: ~p", [Port]),
+ Port.
+stop_test_port(Port) ->
+ {stop, closed, _State} = stop_port(#state{port = Port}),
+ ok.
+
+err_test_() ->
+ {setup,
+ fun() -> start_test_port(err) end,
+ fun(Port) -> stop_test_port(Port) end,
+ fun(Port) ->
+ R = handle_call({validate, [<<"invalid-DS">>, []]},
+ self(), #state{port = Port}),
+ [
+ ?_assertMatch({reply, {error, "err"}, _State}, R)
+ ]
+ end}.
+
+ok_test_() ->
+ {setup,
+ fun() -> start_test_port(ok) end,
+ fun(Port) -> stop_test_port(Port) end,
+ fun(Port) ->
+ R = handle_call({validate, [<<"invalid-DS">>, []]},
+ self(), #state{port = Port}),
+ [
+ ?_assertMatch({reply, ok, _State}, R)
+ ]
+ end}.
diff --git a/src/v1.erl b/src/v1.erl
index 7b7f6bf..86cd799 100644
--- a/src/v1.erl
+++ b/src/v1.erl
@@ -1,4 +1,4 @@
-%%% Copyright (c) 2014-2015, NORDUnet A/S.
+%%% Copyright (c) 2014-2016, NORDUnet A/S.
%%% See LICENSE for licensing information.
%%% @doc Certificate Transparency (RFC 6962)
@@ -7,7 +7,7 @@
%% API (URL)
-export([request/4]).
--define(APPURL_CT_V1, "open/gaol/v1").
+-define(APPURL_CT_V1, "dt/v1").
check_valid_sth() ->
case plop:sth() of
@@ -30,9 +30,9 @@ check_valid_sth() ->
end.
%% Public functions, i.e. part of URL.
-request(post, ?APPURL_CT_V1, "add-blob", Input) ->
+request(post, ?APPURL_CT_V1, "add-ds-rr", Input) ->
check_valid_sth(),
- add_blob(Input);
+ add_ds(Input);
request(get, ?APPURL_CT_V1, "get-sth", _Query) ->
check_valid_sth(),
@@ -147,29 +147,36 @@ internalerror(Text) ->
"~s~n" ++
"</body></html>~n", [Text])}.
--spec add_blob(any()) -> any().
-add_blob(Input) ->
+-spec add_ds(any()) -> any().
+add_ds(Input) ->
case (catch mochijson2:decode(Input)) of
{error, E} ->
- err400("add-blob: bad input:", E);
- {struct, [{<<"blob">>, Blob}]} ->
- case (catch base64:decode(Blob)) of
- {'EXIT', _} ->
- err400("add-blob: invalid base64-encoded blob", Blob);
- DecodedBlob ->
- add_blob_helper(DecodedBlob,
- application:get_env(catlfish,
- max_submit_size,
- 0))
+ err400("add-ds-rr: bad input:", E);
+ {struct, [{<<"chain">>, List}]} ->
+ case decode_chain(List) of
+ {invalid, ErrText} ->
+ err400(io:format("add-ds-rr: ~p", [ErrText]), List);
+ [DSRR, DSRRSIG | SupportRRs] ->
+ add_ds_helper(DSRR, DSRRSIG, SupportRRs);
+ _ ->
+ err400("add-ds-rr: missing one or more entries", List)
end;
_ ->
- err400("add-blob: missing input: blob", Input)
+ err400("add-ds-rr: missing input: chain", Input)
end.
-add_blob_helper(Blob, MaxSize) when MaxSize == 0 ->
- success(catlfish:add_chain(Blob, [], normal));
-add_blob_helper(Blob, MaxSize) when erlang:size(Blob) =< MaxSize ->
- add_blob_helper(Blob, 0);
-add_blob_helper(Blob, MaxSize) ->
- err400(io_lib:format("add-blob: blob too large (~p > ~p)",
- [erlang:size(Blob), MaxSize]), Blob).
+decode_chain(List) ->
+ case (catch [base64:decode(X) || X <- List]) of
+ {'EXIT', _} ->
+ {invalid, "invalid base64-encoding"};
+ L ->
+ L
+ end.
+
+add_ds_helper(DSRR, DSRRSIG, Support) ->
+ case dnssecport:dnssec_validate([DSRR, DSRRSIG], Support) of
+ ok ->
+ success(catlfish:add_chain(DSRR, [DSRRSIG | Support], normal));
+ _ ->
+ err400("add-ds-rr: invalid DS record", [DSRR, DSRRSIG | Support])
+ end.