diff options
-rw-r--r-- | c_src/dnssec.c | 116 | ||||
-rw-r--r-- | ebin/catlfish.app | 3 | ||||
-rw-r--r-- | src/dns.erl | 69 | ||||
-rw-r--r-- | src/dnssecport.erl | 113 | ||||
-rw-r--r-- | src/v1.erl | 17 | ||||
-rwxr-xr-x | test/check.erl | 2 | ||||
-rw-r--r-- | test/testdata/dnssec/req.1 | bin | 0 -> 2597 bytes | |||
-rw-r--r-- | test/testdata/dnssec/trust_anchors | bin | 0 -> 433 bytes | |||
-rw-r--r-- | tools/dnssec/dns-text2wire.c | 1 |
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}. @@ -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 Binary files differnew file mode 100644 index 0000000..e15a791 --- /dev/null +++ b/test/testdata/dnssec/req.1 diff --git a/test/testdata/dnssec/trust_anchors b/test/testdata/dnssec/trust_anchors Binary files differnew file mode 100644 index 0000000..06a1a21 --- /dev/null +++ b/test/testdata/dnssec/trust_anchors 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; |