summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Nordberg <linus@nordu.net>2016-04-03 18:55:57 +0200
committerLinus Nordberg <linus@nordu.net>2016-04-03 18:55:57 +0200
commitba016c8b02006943198aa5bfb68cf13fccdc33ca (patch)
treecdb2fecb4b7bb771640c96b880f5344d4e49da3f
parent9c3158dfcfb97d4123b3d97eb0d14c425095c2ba (diff)
Implement DNSSEC validation.
NOTE: Doesn't return canonicalised RR's, only the getdns return value.
-rw-r--r--c_src/dnssec.c258
1 files changed, 215 insertions, 43 deletions
diff --git a/c_src/dnssec.c b/c_src/dnssec.c
index 693d645..fe46d3d 100644
--- a/c_src/dnssec.c
+++ b/c_src/dnssec.c
@@ -1,72 +1,219 @@
/*
* Copyright (c) 2016, NORDUnet A/S.
* See LICENSE for licensing information.
+ *
+ * Invocation: dnssec <path-to-trust-anchor-file>
+ *
+ * Once running, read DNSSEC RR's from stdin, canonicalise RR's
+ * (RFC4034 6.2), validate RR's (todo:ref) and write the result to
+ * stdout.
+ *
+ * All length fields in the input and output denotes the length of the
+ * piece of data to follow in number of octets.
+ *
+ * Input format:
+ * - Length of data (4 octets)
+ * - DNSSEC RR's as a DNSSEC_key_chain, specified in
+ * draft-zhang-trans-ct-dnssec-03 section 4.1 but without the TLS
+ * data structure encoding.
+ *
+ * Output format:
+ * - Lenght of data (4 octets)
+ * - Return value -- 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
+ *
+ * (RR's)* denotes zero or more RR's.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <err.h>
+#include <errno.h>
+#include <time.h>
+#include <arpa/inet.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 */
+
+#if defined(TEST)
static char *testmode = NULL;
+#endif
+
+static void
+print_tree(const getdns_list *tree, const char *name)
+{
+ if (name)
+ printf("* %s\n", name);
+
+ char *s = getdns_pretty_print_list(tree);
+ puts(s);
+ free(s);
+}
+
+/* TODO: Replace read_file() and wire_rrs2list() with getdns_fp2rr_list()? */
+size_t
+read_file(FILE *infp, uint8_t **bufp_out, size_t size_hint)
+{
+#define CHUNKSIZE 4096
+ size_t nread = 0;
+ uint8_t *wirebuf = NULL;
+ size_t chunksize = CHUNKSIZE;
+ int chunks = 1;
+
+ if (size_hint > 0)
+ chunksize = size_hint;
+ wirebuf = malloc(chunksize);
+
+ if (wirebuf == NULL)
+ goto out;
+
+ while (1)
+ {
+ size_t n = fread(wirebuf + nread, 1, chunksize, infp);
+ nread += n;
+ if (n < chunksize)
+ break; /* Done. */
+
+ wirebuf = realloc(wirebuf, ++chunks * chunksize);
+ if (wirebuf == NULL)
+ break;
+ }
+
+ out:
+ if (bufp_out != NULL)
+ *bufp_out = wirebuf;
+ return nread;
+}
+
+static getdns_return_t
+wire_rrs2list(const uint8_t *buf, size_t buf_len, getdns_list **list_out)
+{
+ getdns_return_t r = GETDNS_RETURN_GOOD;
+ getdns_list *list = getdns_list_create();
+ getdns_dict *dict = NULL;
+ size_t rr_count = 0;
+
+ if (list == NULL)
+ return GETDNS_RETURN_MEMORY_ERROR;
+ while (buf_len > 0)
+ {
+ r = getdns_wire2rr_dict_scan(&buf, &buf_len, &dict);
+ if (r)
+ break;
+ r = getdns_list_set_dict(list, rr_count, dict);
+ getdns_dict_destroy(dict); /* The list has a copy. */
+ if (r)
+ break;
+ rr_count++;
+ }
+
+ if (list_out)
+ *list_out = list;
+ return r;
+}
-/* getdns/src/convert.c */
-getdns_return_t getdns_wire2rr_dict(const uint8_t *wire, size_t wire_len,
- getdns_dict **rr_dict);
+static int
+read_trust_anchors(const char *fname, getdns_list **list_out)
+{
+ 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 !defined(TEST)
static getdns_return_t
-validate(const unsigned char *records,
- size_t records_len,
- getdns_list *trust_anchors)
+validate(const uint8_t *buf, size_t buf_len,
+ getdns_list *trust_anchors,
+ time_t validation_time, uint32_t skew)
{
getdns_return_t r = GETDNS_DNSSEC_INDETERMINATE;
+ getdns_list *list = NULL;
getdns_list *to_validate = getdns_list_create();
getdns_list *support_records = getdns_list_create();
- getdns_dict *records_dict = NULL;
if (to_validate == NULL || support_records == NULL)
+ return GETDNS_RETURN_MEMORY_ERROR;
+
+ /* Convert RR's in buf to dicts in a list. */
+ if ((r = wire_rrs2list(buf, buf_len, &list)))
+ goto out;
+
+ /* First record MUST be the DS RR to validate. Second record MUST be
+ an RRSIG covering the DS RR. Copy those to to_validate. */
+ getdns_dict *ds_dict = NULL;
+ getdns_dict *rrsig_ds_dict = NULL;
+ uint32_t rrtype = 0;
+ if ((r = getdns_list_get_dict(list, 0, &ds_dict)))
+ goto out;
+ if ((r = getdns_dict_get_int(ds_dict, "type", &rrtype)))
+ goto out;
+ if (rrtype != GETDNS_RRTYPE_DS)
{
- r = GETDNS_RETURN_MEMORY_ERROR;
+ r = GETDNS_RETURN_INVALID_PARAMETER;
goto out;
}
+ if ((r = getdns_list_set_dict(to_validate, 0, ds_dict)))
+ goto out;
- /* TODO: figure out if we get _all_ RRs in records here bc i have
- the feeling that we're not supposed to mix RR types in the same
- dict; maybe this will help some:
- https://getdnsapi.net/pipermail/users/2015-May/000032.html
- */
- r = getdns_wire2rr_dict(records, records_len, &records_dict);
- if (r)
+ if ((r = getdns_list_get_dict(list, 1, &rrsig_ds_dict)))
+ goto out;
+ if ((r = getdns_dict_get_int(rrsig_ds_dict, "type", &rrtype)))
+ goto out;
+ if (rrtype != GETDNS_RRTYPE_RRSIG)
+ {
+ r = GETDNS_RETURN_INVALID_PARAMETER;
+ goto out;
+ }
+ if ((r = getdns_list_set_dict(to_validate, 1, rrsig_ds_dict)))
goto out;
- /*
- to_validate: one dict with the DS and one with a RRSIG for that DS
- support_records: DS and DNSKEY dicts with accompanying RRSIG's
- trust_anchors: DNSKEY (or DS)
- */
- r = getdns_list_set_dict(to_validate, i, records_dict);
- if (r)
+ /* The rest is "support records". Copy those to support_records. */
+ size_t list_len;
+ if ((r = getdns_list_get_length(list, &list_len)))
goto out;
+ for (int i = 2; i < list_len; i++)
+ {
+ getdns_dict *tmp_dict = NULL;
+ if ((r = getdns_list_get_dict(list, i, &tmp_dict)))
+ goto out;
+ if ((r = getdns_list_set_dict(support_records, i - 2, tmp_dict)))
+ goto out;
+ }
- r = getdns_validate_dnssec(to_validate,
- support_records,
- trust_anchors);
+ if (debug)
+ {
+ print_tree(to_validate, "to_validate");
+ print_tree(support_records, "support_records");
+ print_tree(trust_anchors, "trust_anchors");
+ }
-out:
- if (to_validate)
- getdns_list_destroy(to_validate);
- if (support_records)
- getdns_list_destroy(support_records);
+ r = getdns_validate_dnssec2(to_validate,
+ support_records,
+ trust_anchors,
+ validation_time,
+ skew);
+out:
+ if (list)
+ getdns_list_destroy(list);
+ getdns_list_destroy(to_validate);
+ getdns_list_destroy(support_records);
return r;
}
#endif /* !TEST */
+#define DNSSEC_VALIDATION_SKEW 30 /* Seconds. */
+
static void
loop(getdns_list *trust_anchors)
{
@@ -76,28 +223,33 @@ loop(getdns_list *trust_anchors)
while ((len = read_command(buf, sizeof(buf), 4)) > 0)
{
- unsigned char *reply = NULL;
+ unsigned char reply[2];
#if !defined(TEST)
- r = validate(buf, len, trust_anchors);
+ r = validate(buf, len, trust_anchors, time(NULL), DNSSEC_VALIDATION_SKEW);
#else
r = test_validate(buf, len, trust_anchors, testmode);
#endif
- switch (r)
+ if (debug)
{
- case GETDNS_RETURN_GOOD:
- reply = (unsigned char *) "valid";
- break;
- default:
- fprintf(stderr, "error %d\n", r); /* DEBUG */
- reply = (unsigned char *) "err";
+ switch (r)
+ {
+ case GETDNS_RETURN_GOOD:
+ fprintf(stderr, "validation success\n");
+ break;
+ default:
+ fprintf(stderr, "validation error %d (%s)\n",
+ r, getdns_get_errorstr_by_id(r));
+ }
}
- write_reply(reply, strlen((const char *) reply), 4);
+ *((uint16_t *) reply) = htons(r);
+ write_reply(reply, 2, 4);
}
}
+
int
main(int argc, char *argv[])
{
@@ -105,6 +257,7 @@ main(int argc, char *argv[])
getdns_list *trust_anchors = NULL;
time_t trust_anchor_date;
+ /* Parse command line. */
while (1) {
static struct option long_options[] = {
{"testmode", required_argument, NULL, 't'},
@@ -122,17 +275,36 @@ main(int argc, char *argv[])
break;
#endif
default:
- fprintf(stderr, "dnssecport: bad option: %s", argv[optind]);
+ fprintf(stderr, "bad option: %s", argv[optind]);
return -1;
}
}
- if (optind < argc) /* Using getdns trust anchor. */
+ /* Read trust anchors file. */
+ if (optind >= argc)
+ {
+ int r = read_trust_anchors(argv[optind], &trust_anchors);
+ if (r < 0)
+ {
+ perror("read trust anchors");
+ return -r;
+ }
+ else if (r > 0)
+ {
+ fprintf(stderr,
+ "unable to read trust anchors file %s: %d (%s)",
+ argv[optind], r, getdns_get_errorstr_by_id(r));
+ return r;
+ }
+ }
+ else /* DEBUG: Using getdns trust anchor. */
{
trust_anchors = getdns_root_trust_anchor(&trust_anchor_date);
}
+ /* Eternal loop. */
loop(trust_anchors);
+ /* Not reached. */
return 0;
}