summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Nordberg <linus@nordu.net>2016-04-06 10:07:48 +0200
committerLinus Nordberg <linus@nordu.net>2016-04-07 16:06:12 +0200
commit03874b8eca8f065f6a1b133f06f19c0ff1d1494e (patch)
tree49ba20ad12e907270468353bc868742001086098
parentb8d1f0175850fbbbb8e8f6e2cdab5438ab8a54b6 (diff)
Add unit test for validation, from dnssecport:handle_call().
- The port now returns the RRset (DS, chain, trust root and all RRSIG's). This in preparatino for when this data will be normalised. - dnssecport decodes and encodes DNS data. - v1 stores the DS RR in the leaf and the rest, including the DS RRSIG, in the chain.
-rw-r--r--c_src/dnssec.c116
-rw-r--r--ebin/catlfish.app3
-rw-r--r--src/dns.erl69
-rw-r--r--src/dnssecport.erl113
-rw-r--r--src/v1.erl17
-rwxr-xr-xtest/check.erl2
-rw-r--r--test/testdata/dnssec/req.1bin0 -> 2597 bytes
-rw-r--r--test/testdata/dnssec/trust_anchorsbin0 -> 433 bytes
-rw-r--r--tools/dnssec/dns-text2wire.c1
9 files changed, 249 insertions, 72 deletions
diff --git a/c_src/dnssec.c b/c_src/dnssec.c
index fe46d3d..6b9431d 100644
--- a/c_src/dnssec.c
+++ b/c_src/dnssec.c
@@ -19,8 +19,8 @@
*
* Output format:
* - Lenght of data (4 octets)
- * - Return value -- the getdns_return_t value in network byte order
- * (2 octets)
+ * - Status code -- the getdns_return_t value in network byte order (2
+ * octets)
* - (RR's)* -- if validation succeeded: the DS+RRSIG and the full
* chain up to and including the trust anchor; if validation failed:
* nothing
@@ -33,27 +33,27 @@
#include <string.h>
#include <errno.h>
#include <time.h>
-#include <arpa/inet.h>
+#include <endian.h>
#include <getopt.h>
#include <getdns/getdns.h>
#include <getdns/getdns_extra.h>
#include "erlport.h"
#include "dnssec_test.h"
-static int debug = 1; /* DEBUG */
+static int debug = 0; /* DEBUG */
#if defined(TEST)
static char *testmode = NULL;
#endif
static void
-print_tree(const getdns_list *tree, const char *name)
+print_tree(FILE *fp, const getdns_list *tree, const char *name)
{
if (name)
- printf("* %s\n", name);
+ fprintf(fp, "* %s\n", name);
char *s = getdns_pretty_print_list(tree);
- puts(s);
+ fputs(s, fp);
free(s);
}
@@ -122,19 +122,54 @@ wire_rrs2list(const uint8_t *buf, size_t buf_len, getdns_list **list_out)
static int
read_trust_anchors(const char *fname, getdns_list **list_out)
{
+ if (debug)
+ fprintf(stderr, "reading trust anchors from file %s\n", fname);
+
FILE *fp = fopen(fname, "r");
if (fp == NULL)
return -errno;
uint8_t *buf = NULL;
size_t n = read_file(fp, &buf, 0);
- return (int) wire_rrs2list(buf, n, list_out);
+ if (fclose(fp))
+ fprintf(stderr, "unable to close trust anchors file: %d", errno);
+ int r = wire_rrs2list(buf, n, list_out);
+ free(buf);
+ return r;
+}
+
+static getdns_return_t
+list2wire(const getdns_list *list, uint8_t *out_buf, size_t *out_buf_len)
+{
+ size_t list_len;
+ getdns_return_t r = getdns_list_get_length(list, &list_len);
+ if (r)
+ return r;
+
+ getdns_dict *rr = NULL;
+ uint8_t *buf = NULL;
+ size_t buf_len = 0;
+ *out_buf_len = 0;
+ for (int i = 0; i < list_len; i++)
+ {
+ if ((r = getdns_list_get_dict(list, i , &rr)))
+ return r;
+ if ((r = getdns_rr_dict2wire(rr, &buf, &buf_len)))
+ return r; /* FIXME: Risk of leaking buf? */
+ memcpy(out_buf, buf, buf_len);
+ free(buf);
+ out_buf += buf_len;
+ *out_buf_len += buf_len;
+ }
+
+ return r;
}
#if !defined(TEST)
static getdns_return_t
validate(const uint8_t *buf, size_t buf_len,
getdns_list *trust_anchors,
- time_t validation_time, uint32_t skew)
+ time_t validation_time, uint32_t skew,
+ uint8_t *out_buf, size_t *out_buf_len)
{
getdns_return_t r = GETDNS_DNSSEC_INDETERMINATE;
getdns_list *list = NULL;
@@ -153,6 +188,7 @@ validate(const uint8_t *buf, size_t buf_len,
getdns_dict *ds_dict = NULL;
getdns_dict *rrsig_ds_dict = NULL;
uint32_t rrtype = 0;
+ /* DS */
if ((r = getdns_list_get_dict(list, 0, &ds_dict)))
goto out;
if ((r = getdns_dict_get_int(ds_dict, "type", &rrtype)))
@@ -164,7 +200,7 @@ validate(const uint8_t *buf, size_t buf_len,
}
if ((r = getdns_list_set_dict(to_validate, 0, ds_dict)))
goto out;
-
+ /* RRSIG DS */
if ((r = getdns_list_get_dict(list, 1, &rrsig_ds_dict)))
goto out;
if ((r = getdns_dict_get_int(rrsig_ds_dict, "type", &rrtype)))
@@ -177,7 +213,7 @@ validate(const uint8_t *buf, size_t buf_len,
if ((r = getdns_list_set_dict(to_validate, 1, rrsig_ds_dict)))
goto out;
- /* The rest is "support records". Copy those to support_records. */
+ /* The rest is "support records". Copy them to support_records. */
size_t list_len;
if ((r = getdns_list_get_length(list, &list_len)))
goto out;
@@ -190,11 +226,11 @@ validate(const uint8_t *buf, size_t buf_len,
goto out;
}
- if (debug)
+ if (0 && debug)
{
- print_tree(to_validate, "to_validate");
- print_tree(support_records, "support_records");
- print_tree(trust_anchors, "trust_anchors");
+ print_tree(stderr, to_validate, "to_validate");
+ print_tree(stderr, support_records, "support_records");
+ print_tree(stderr, trust_anchors, "trust_anchors");
}
r = getdns_validate_dnssec2(to_validate,
@@ -203,6 +239,30 @@ validate(const uint8_t *buf, size_t buf_len,
validation_time,
skew);
+ if (out_buf && out_buf_len)
+ {
+ getdns_return_t r_save = r;
+ size_t len = 0;
+ *out_buf_len = 0;
+
+ if ((r = list2wire(to_validate, out_buf, &len)))
+ goto out;
+ out_buf += len;
+ *out_buf_len += len;
+
+ if ((r = list2wire(support_records, out_buf, &len)))
+ goto out;
+ out_buf += len;
+ *out_buf_len += len;
+
+ if ((r = list2wire(trust_anchors, out_buf, &len)))
+ goto out;
+ out_buf += len;
+ *out_buf_len += len;
+
+ r = r_save;
+ }
+
out:
if (list)
getdns_list_destroy(list);
@@ -218,25 +278,29 @@ static void
loop(getdns_list *trust_anchors)
{
getdns_return_t r = GETDNS_RETURN_GENERIC_ERROR;
- unsigned char buf[65536];
+ unsigned char buf[64 * 1024]; /* FIXME */
ssize_t len;
while ((len = read_command(buf, sizeof(buf), 4)) > 0)
{
- unsigned char reply[2];
+ unsigned char reply[2 * 64 * 1024]; /* FIXME */
+ size_t out_len = 0;
#if !defined(TEST)
- r = validate(buf, len, trust_anchors, time(NULL), DNSSEC_VALIDATION_SKEW);
+ r = validate(buf, len, trust_anchors,
+ time(NULL), DNSSEC_VALIDATION_SKEW,
+ reply + 2, &out_len);
#else
r = test_validate(buf, len, trust_anchors, testmode);
#endif
if (debug)
{
- switch (r)
+ int intr = r; /* GETDNS_DNSSEC_SECURE is not in enum getdns_return_t */
+ switch (intr)
{
- case GETDNS_RETURN_GOOD:
- fprintf(stderr, "validation success\n");
+ case GETDNS_DNSSEC_SECURE:
+ fprintf(stderr, "validation successful\n");
break;
default:
fprintf(stderr, "validation error %d (%s)\n",
@@ -244,8 +308,12 @@ loop(getdns_list *trust_anchors)
}
}
- *((uint16_t *) reply) = htons(r);
- write_reply(reply, 2, 4);
+ *((uint16_t *) reply) = htobe16(r);
+ if (debug)
+ fprintf(stderr, "writing %d octets of data, including status code %d\n",
+ 2 + out_len, r);
+ if (write_reply(reply, 2 + out_len, 4))
+ fprintf(stderr, "error writing reply\n");
}
}
@@ -281,7 +349,7 @@ main(int argc, char *argv[])
}
/* Read trust anchors file. */
- if (optind >= argc)
+ if (optind < argc)
{
int r = read_trust_anchors(argv[optind], &trust_anchors);
if (r < 0)
diff --git a/ebin/catlfish.app b/ebin/catlfish.app
index aa833bc..3bb882c 100644
--- a/ebin/catlfish.app
+++ b/ebin/catlfish.app
@@ -6,6 +6,7 @@
{application, catlfish,
[{description, "catlfish -- Certificate Transparency Log Server"},
{vsn, "0.9.0-dev"},
- {modules, [catlfish, catlfish_app, catlfish_sup, catlfish_web, v1, ratelimit]},
+ {modules, [catlfish, catlfish_app, catlfish_sup, catlfish_web, v1, ratelimit,
+ dns, dnssecport]},
{applications, [kernel, stdlib, plop, lager, mochiweb, idna, asn1, crypto]},
{mod, {catlfish_app, []}}]}.
diff --git a/src/dns.erl b/src/dns.erl
new file mode 100644
index 0000000..24fb8fb
--- /dev/null
+++ b/src/dns.erl
@@ -0,0 +1,69 @@
+%%% Copyright (c) 2016, NORDUnet A/S.
+%%% See LICENSE for licensing information.
+
+-module(dns).
+-export([split_rrset/1, encode_rr/1, encode_rrset/1]).
+
+decode_name_label(Name) ->
+ <<Len:8/integer, Label:Len/binary, Rest/binary>> = Name,
+ {Label, Rest}.
+
+encode_name_label(Label) ->
+ Len = byte_size(Label),
+ <<Len:8/integer, Label/binary>>.
+
+decode_name(RR) ->
+ decode_name(RR, []).
+decode_name(<<0, Rest/binary>>, Acc) ->
+ {lists:reverse(Acc), Rest};
+decode_name(Name, Acc) ->
+ {Label, Rest} = decode_name_label(Name),
+ decode_name(Rest, [Label | Acc]).
+
+-spec encode_name(list()) -> binary().
+encode_name(Name) ->
+ encode_name(Name, []).
+encode_name([], Acc) ->
+ Bin = list_to_binary(lists:reverse(Acc)),
+ <<Bin/binary, 0>>;
+encode_name([H|T], Acc) ->
+ encode_name(T, [encode_name_label(H) | Acc]).
+
+-spec decode_rr(binary()) -> {list(), binary()}.
+decode_rr(RR) ->
+ {Name, RestRR} = decode_name(RR),
+ <<Type:2/binary,
+ Class:2/binary,
+ TTL:4/integer-unit:8,
+ RDLength:2/integer-unit:8,
+ RDATA:RDLength/binary,
+ Rest/binary>> = RestRR,
+ {[Name, Type, Class, TTL, RDATA], Rest}.
+
+-spec split_rrset(binary()) -> list().
+split_rrset(RRSet) ->
+ split_rrset(RRSet, []).
+split_rrset(<<>>, Acc) ->
+ lists:reverse(Acc);
+split_rrset(RRSet, Acc) ->
+ {RR, Rest} = decode_rr(RRSet),
+ split_rrset(Rest, [RR | Acc]).
+
+-spec encode_rr(list()) -> binary().
+encode_rr([Name, Type, Class, TTL, RDATA]) ->
+ EncodedName = encode_name(Name),
+ RDLength = byte_size(RDATA),
+ <<EncodedName/binary,
+ Type:2/binary,
+ Class:2/binary,
+ TTL:4/integer-unit:8,
+ RDLength:2/integer-unit:8,
+ RDATA/binary>>.
+
+-spec encode_rrset(list()) -> binary().
+encode_rrset(RRSet) ->
+ encode_rrset(RRSet, []).
+encode_rrset([], Acc) ->
+ list_to_binary(lists:reverse(Acc));
+encode_rrset([H|T], Acc) ->
+ encode_rrset(T, [encode_rr(H) | Acc]).
diff --git a/src/dnssecport.erl b/src/dnssecport.erl
index 804e727..02f919a 100644
--- a/src/dnssecport.erl
+++ b/src/dnssecport.erl
@@ -4,7 +4,7 @@
-module(dnssecport).
-behaviour(gen_server).
-export([start_link/0, stop/0]).
--export([validate/2]).
+-export([validate/1]).
%% gen_server callbacks.
-export([init/1, handle_call/3, terminate/2, handle_cast/2, handle_info/2,
code_change/3]).
@@ -18,36 +18,40 @@ start_link() ->
stop() ->
gen_server:call(?MODULE, stop).
-validate(ValidateRR, SupportRRs) ->
- gen_server:call(?MODULE, {validate, [ValidateRR, SupportRRs]}).
+validate(Data) ->
+ gen_server:call(?MODULE, {validate, Data}).
-record(state, {port :: port()}).
init(Program) ->
lager:debug("starting dnssec service"),
- Port = create_port(Program, []), % TODO: Pass path to dir with trust root.
+ Port = create_port(Program, []), % TODO: Pass path to trust root file.
{ok, #state{port = Port}}.
+decode_response(Response) ->
+ <<Status:16/integer, RRSet/binary>> = Response,
+ {ok, Status, dns:split_rrset(RRSet)}.
+
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]),
+handle_call({validate, Data}, _From, State) ->
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>>}},
+ Port ! {self(), {command, dns:encode_rrset(Data)}},
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}
+ case decode_response(list_to_binary(Response)) of
+ {ok, 400, [DS | Chain]} ->
+ {reply,
+ {ok, [dns:encode_rr(DS) | dns:encode_rrset(Chain)]},
+ State};
+ {ok, Error, _} ->
+ {reply, {error, Error}, State};
+ {error, Reason} ->
+ {stop, {protocolerror, Reason}, State}
end;
{Port, {exit_status, ExitStatus}} ->
lager:error("dnssec port ~p exiting with status ~p",
@@ -97,34 +101,67 @@ stop_port(State) ->
%%%%%%%%%%%%%%%%%%%%
%% Unit tests.
-start_test_port(TestType) ->
- Port = create_port("priv/dnssecport", ["--testmode", atom_to_list(TestType)]),
- ?debugFmt("Port: ~p", [Port]),
- Port.
+-define(TA_FILE, "test/testdata/dnssec/trust_anchors").
+-define(REQ1_FILE, "test/testdata/dnssec/req.1").
+
+start_test_port() ->
+ create_port("priv/dnssecport", [?TA_FILE]).
+
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}.
+read_submission_from_file(Filename) ->
+ {ok, Data} = file:read_file(Filename),
+ dns:split_rrset(Data).
+
+read_dec_enc_test_() ->
+ DecodedRRset = read_submission_from_file(?REQ1_FILE),
+ {ok, FileContent} = file:read_file(?REQ1_FILE),
+ [?_assertEqual(FileContent, dns:encode_rrset(DecodedRRset))].
-ok_test_() ->
+full_test_() ->
{setup,
- fun() -> start_test_port(ok) end,
- fun(Port) -> stop_test_port(Port) end,
- fun(Port) ->
- R = handle_call({validate, [<<"invalid-DS">>, []]},
+ fun() ->
+ start_test_port() end,
+ fun(Port) ->
+ stop_test_port(Port) end,
+ fun(Port) ->
+ R = handle_call({validate, read_submission_from_file(?REQ1_FILE)},
self(), #state{port = Port}),
[
- ?_assertMatch({reply, ok, _State}, R)
- ]
- end}.
+ ?_assertMatch({reply, {ok, _}, _State}, R)
+ ] end
+ }.
+
+%% 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 86cd799..bab77aa 100644
--- a/src/v1.erl
+++ b/src/v1.erl
@@ -156,8 +156,8 @@ add_ds(Input) ->
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);
+ Data when is_list(Data) ->
+ add_ds_helper(Data);
_ ->
err400("add-ds-rr: missing one or more entries", List)
end;
@@ -173,10 +173,11 @@ decode_chain(List) ->
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])
+add_ds_helper(Data) ->
+ case dnssecport:dnssec_validate(Data) of
+ {ok, [DS | Chain]} ->
+ success(catlfish:add_chain(DS, Chain, normal));
+ {error, ErrorCode} ->
+ err400(io:format("add-ds-rr: invalid DS record: ~p", [ErrorCode]),
+ Data)
end.
diff --git a/test/check.erl b/test/check.erl
index 2cb388c..caad037 100755
--- a/test/check.erl
+++ b/test/check.erl
@@ -8,5 +8,5 @@
%% config/check.config.
main(_) ->
- ok = dnssecport:test().
+ ok = dnssecport:test(),
ok = catlfish:test().
diff --git a/test/testdata/dnssec/req.1 b/test/testdata/dnssec/req.1
new file mode 100644
index 0000000..e15a791
--- /dev/null
+++ b/test/testdata/dnssec/req.1
Binary files differ
diff --git a/test/testdata/dnssec/trust_anchors b/test/testdata/dnssec/trust_anchors
new file mode 100644
index 0000000..06a1a21
--- /dev/null
+++ b/test/testdata/dnssec/trust_anchors
Binary files differ
diff --git a/tools/dnssec/dns-text2wire.c b/tools/dnssec/dns-text2wire.c
index 99d418f..f15ae19 100644
--- a/tools/dnssec/dns-text2wire.c
+++ b/tools/dnssec/dns-text2wire.c
@@ -38,6 +38,7 @@ main(int argc, char *argv[])
r = getdns_rr_dict2wire(rr, &buf, &buf_len);
assert(!r);
ssize_t n = write(1, buf, buf_len);
+ free(buf);
assert(n == buf_len);
}
return 0;