diff options
author | Linus Nordberg <linus@nordu.net> | 2016-04-03 18:55:57 +0200 |
---|---|---|
committer | Linus Nordberg <linus@nordu.net> | 2016-04-07 16:06:12 +0200 |
commit | b8d1f0175850fbbbb8e8f6e2cdab5438ab8a54b6 (patch) | |
tree | ae80341439e15f77909c1142f1188385b76b80d3 /c_src | |
parent | 46326cc00564d6ae68b5576616ae76677ccbbf8f (diff) |
Implement DNSSEC validation.
NOTE: Doesn't return canonicalised RR's, only the getdns return value.
Diffstat (limited to 'c_src')
-rw-r--r-- | c_src/dnssec.c | 258 |
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; } |