summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dns.erl74
-rw-r--r--src/dnssecport.erl27
-rw-r--r--src/v1.erl2
3 files changed, 70 insertions, 33 deletions
diff --git a/src/dns.erl b/src/dns.erl
index 24fb8fb..b8a8ffe 100644
--- a/src/dns.erl
+++ b/src/dns.erl
@@ -2,22 +2,33 @@
%%% See LICENSE for licensing information.
-module(dns).
--export([split_rrset/1, encode_rr/1, encode_rrset/1]).
+-export([decode_rrset/1, decode_rr/1, encode_rrset/1, encode_rr/1,
+ canonicalize_dsrr/2]).
-decode_name_label(Name) ->
- <<Len:8/integer, Label:Len/binary, Rest/binary>> = Name,
- {Label, Rest}.
+-record(rr, {name :: list(), % List of name labels.
+ type :: binary(),
+ class :: binary(),
+ ttl :: integer(),
+ rdata :: binary()}).
+-type rr() :: #rr{}.
+-spec decode_name_label(binary()) -> tuple().
+decode_name_label(RRbin) ->
+ <<Len:8/integer, Label:Len/binary, Rest/binary>> = RRbin,
+ {binary_to_list(Label), Rest}.
+
+-spec encode_name_label(string()) -> binary().
encode_name_label(Label) ->
- Len = byte_size(Label),
- <<Len:8/integer, Label/binary>>.
+ LabelBin = list_to_binary(Label),
+ Len = byte_size(LabelBin),
+ <<Len:8/integer, LabelBin/binary>>.
-decode_name(RR) ->
- decode_name(RR, []).
+decode_name(RRbin) ->
+ decode_name(RRbin, []).
decode_name(<<0, Rest/binary>>, Acc) ->
{lists:reverse(Acc), Rest};
-decode_name(Name, Acc) ->
- {Label, Rest} = decode_name_label(Name),
+decode_name(RRbin, Acc) ->
+ {Label, Rest} = decode_name_label(RRbin),
decode_name(Rest, [Label | Acc]).
-spec encode_name(list()) -> binary().
@@ -29,28 +40,29 @@ encode_name([], Acc) ->
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),
+-spec decode_rr(binary()) -> {rr(), binary()}.
+decode_rr(RRBin) ->
+ {Name, RestRR} = decode_name(RRBin),
<<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}.
+ {#rr{name = Name, type = Type, class = Class, ttl = TTL, rdata = RDATA},
+ Rest}.
--spec split_rrset(binary()) -> list().
-split_rrset(RRSet) ->
- split_rrset(RRSet, []).
-split_rrset(<<>>, Acc) ->
+-spec decode_rrset(binary()) -> [rr()].
+decode_rrset(RRSet) ->
+ decode_rrset(RRSet, []).
+decode_rrset(<<>>, Acc) ->
lists:reverse(Acc);
-split_rrset(RRSet, Acc) ->
+decode_rrset(RRSet, Acc) ->
{RR, Rest} = decode_rr(RRSet),
- split_rrset(Rest, [RR | Acc]).
+ decode_rrset(Rest, [RR | Acc]).
--spec encode_rr(list()) -> binary().
-encode_rr([Name, Type, Class, TTL, RDATA]) ->
+-spec encode_rr(rr()) -> binary().
+encode_rr(#rr{name = Name, type = Type, class = Class, ttl = TTL, rdata = RDATA}) ->
EncodedName = encode_name(Name),
RDLength = byte_size(RDATA),
<<EncodedName/binary,
@@ -67,3 +79,21 @@ encode_rrset([], Acc) ->
list_to_binary(lists:reverse(Acc));
encode_rrset([H|T], Acc) ->
encode_rrset(T, [encode_rr(H) | Acc]).
+
+%% Cacnonicalise a single DS RR according to RFC4034 section 6.2.
+canonicalize_dsrr(DS, RRSIG) ->
+ %% 1. expand domain name
+ %% FIXME: What does a compressed name look like?
+
+ %% 2. lowercase
+ LCName = lists:map(fun(L) -> string:to_lower(L) end, DS#rr.name),
+
+ %% 3. N/A for DS
+ %% 4. N/A for DS FIXME: verify
+
+ %% 5. set TTL to that of the RRSIG
+ OrigTTL = RRSIG#rr.ttl,
+
+ DS#rr{name = LCName, ttl = OrigTTL}.
+
+%% TODO: Add unit tests.
diff --git a/src/dnssecport.erl b/src/dnssecport.erl
index 02f919a..c942fb4 100644
--- a/src/dnssecport.erl
+++ b/src/dnssecport.erl
@@ -30,7 +30,7 @@ init(Program) ->
decode_response(Response) ->
<<Status:16/integer, RRSet/binary>> = Response,
- {ok, Status, dns:split_rrset(RRSet)}.
+ {ok, Status, dns:decode_rrset(RRSet)}.
handle_call(stop, _From, State) ->
lager:debug("dnssec stop request received"),
@@ -45,13 +45,12 @@ handle_call({validate, Data}, _From, State) ->
{Port, {data, Response}} ->
case decode_response(list_to_binary(Response)) of
{ok, 400, [DS | Chain]} ->
- {reply,
- {ok, [dns:encode_rr(DS) | dns:encode_rrset(Chain)]},
- State};
+ RRSIG = hd(Chain),
+ R = [dns:encode_rr(dns:canonicalize_dsrr(DS, RRSIG)),
+ dns:encode_rrset(Chain)],
+ {reply, {ok, R}, State};
{ok, Error, _} ->
- {reply, {error, Error}, State};
- {error, Reason} ->
- {stop, {protocolerror, Reason}, State}
+ {reply, {error, Error}, State}
end;
{Port, {exit_status, ExitStatus}} ->
lager:error("dnssec port ~p exiting with status ~p",
@@ -103,6 +102,7 @@ stop_port(State) ->
%% Unit tests.
-define(TA_FILE, "test/testdata/dnssec/trust_anchors").
-define(REQ1_FILE, "test/testdata/dnssec/req.1").
+-define(REQ2_FILE, "test/testdata/dnssec/req-lowttl").
start_test_port() ->
create_port("priv/dnssecport", [?TA_FILE]).
@@ -113,13 +113,14 @@ stop_test_port(Port) ->
read_submission_from_file(Filename) ->
{ok, Data} = file:read_file(Filename),
- dns:split_rrset(Data).
+ dns:decode_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))].
+%% TODO: These tests are a bit lame. Room for improvement!
full_test_() ->
{setup,
fun() ->
@@ -127,10 +128,16 @@ full_test_() ->
fun(Port) ->
stop_test_port(Port) end,
fun(Port) ->
- R = handle_call({validate, read_submission_from_file(?REQ1_FILE)},
+ R1 = handle_call({validate, read_submission_from_file(?REQ1_FILE)},
self(), #state{port = Port}),
+ R2 = handle_call({validate, read_submission_from_file(?REQ2_FILE)},
+ self(), #state{port = Port}),
+ {reply, {ok, [DSBin | _ChainBin]}, _} = R2,
+ {DS, <<>>} = dns:decode_rr(DSBin),
[
- ?_assertMatch({reply, {ok, _}, _State}, R)
+ ?_assertMatch({reply, {ok, _}, _State}, R1),
+ ?_assertMatch({reply, {ok, _}, _State}, R2),
+ ?_assertMatch({rr, _Name, _Type, _Class, 3600, _RDATA}, DS)
] end
}.
diff --git a/src/v1.erl b/src/v1.erl
index bab77aa..ef9aadd 100644
--- a/src/v1.erl
+++ b/src/v1.erl
@@ -174,7 +174,7 @@ decode_chain(List) ->
end.
add_ds_helper(Data) ->
- case dnssecport:dnssec_validate(Data) of
+ case dnssecport:validate(Data) of
{ok, [DS | Chain]} ->
success(catlfish:add_chain(DS, Chain, normal));
{error, ErrorCode} ->