From 56d70baa79ae5907b11445364bbea9b31ee4cd20 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Sun, 27 Mar 2016 19:27:30 +0200 Subject: WIP --- src/catlfish.erl | 4 +- src/dnssecport.erl | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/v1.erl | 55 +++++++++++++---------- 3 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 src/dnssecport.erl (limited to 'src') 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, <>}}, + 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" ++ "~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. -- cgit v1.1