From 5f8e58f49bf6798f8bf341573dd95c6a12e3e0cf Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 9 Jan 2014 17:31:19 +0000 Subject: [PATCH] DNSSEC consolidation. --- src/dns-protocol.h | 15 +- src/dnsmasq.h | 2 - src/dnssec.c | 1073 +++++++++++++++++++------------------------- src/rfc1035.c | 2 +- 4 files changed, 471 insertions(+), 621 deletions(-) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 8518dd0..90257ad 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -39,16 +39,29 @@ #define C_ANY 255 /* wildcard match */ #define T_A 1 -#define T_NS 2 +#define T_NS 2 +#define T_MD 3 +#define T_MF 4 #define T_CNAME 5 #define T_SOA 6 +#define T_MB 7 +#define T_MG 8 +#define T_MR 9 #define T_PTR 12 +#define T_MINFO 14 #define T_MX 15 #define T_TXT 16 +#define T_RP 17 +#define T_AFSDB 18 +#define T_RT 21 #define T_SIG 24 +#define T_PX 26 #define T_AAAA 28 +#define T_NXT 30 #define T_SRV 33 #define T_NAPTR 35 +#define T_KX 36 +#define T_DNAME 39 #define T_OPT 41 #define T_DS 43 #define T_RRSIG 46 diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 230f35b..ca4fa61 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1054,8 +1054,6 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); -int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, - int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo, int keytag); int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class); int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen); diff --git a/src/dnssec.c b/src/dnssec.c index f9d3aab..daf1dd3 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -20,378 +20,12 @@ #ifdef HAVE_DNSSEC #include "dnssec-crypto.h" -#include - -/* Maximum length in octects of a domain name, in wire format */ -#define MAXCDNAME 256 - -#define MAXRRSET 16 #define SERIAL_UNDEF -100 #define SERIAL_EQ 0 #define SERIAL_LT -1 #define SERIAL_GT 1 -/* Implement RFC1982 wrapped compare for 32-bit numbers */ -static int serial_compare_32(unsigned long s1, unsigned long s2) -{ - if (s1 == s2) - return SERIAL_EQ; - - if ((s1 < s2 && (s2 - s1) < (1UL<<31)) || - (s1 > s2 && (s1 - s2) > (1UL<<31))) - return SERIAL_LT; - if ((s1 < s2 && (s2 - s1) > (1UL<<31)) || - (s1 > s2 && (s1 - s2) < (1UL<<31))) - return SERIAL_GT; - return SERIAL_UNDEF; -} - - -/* process_domain_name() - do operations with domain names in canonicalized wire format. - * - * Handling domain names in wire format can be done with buffers as large as MAXCDNAME (256), - * while the representation format (as created by, eg., extract_name) requires MAXDNAME (1024). - * - * With "canonicalized wire format", we mean the standard DNS wire format, eg: - * - * <3>www<7>example<3>org<0> - * - * with all ÅSCII letters converted to lowercase, and no wire-level compression. - * - * The function works with two different buffers: - * - Input buffer: 'rdata' is a pointer to the actual wire data, and 'rdlen' is - * the total length till the end of the rdata or DNS packet section. Both - * variables are updated after processing the domain name, so that rdata points - * after it, and rdlen is decreased by the amount of the processed octects. - * - Output buffer: 'out' points to it. In some cases, this buffer can be prefilled - * and used as additional input (see below). - * - * The argument "action" decides what to do with the submitted domain name: - * - * PDN_EXTRACT: - * Extract the domain name from input buffer into the output buffer, possibly uncompressing it. - * Return the length of the domain name in the output buffer in octects, or zero if error. - * - * PDN_COMPARE: - * Compare the domain name in the input buffer and the one in the output buffer (ignoring - * differences in compression). Returns 0 in case of error, a positive number - * if they are equal, or a negative number if they are different. This function always - * consumes the whole name in the input buffer (there is no early exit). - * - * PDN_ORDER: - * Order between the domain name in the input buffer and the domain name in the output buffer. - * Returns 0 if the names are equal, 1 if input > output, or -1 if input < output. This - * function early-exits when it finds a difference, so rdata might not be fully updated. - * - * Notice: because of compression, rdata/rdlen might be updated with a different quantity than - * the returned number of octects. For instance, if we extract a compressed domain name, rdata/rdlen - * might be updated only by 2 bytes (that is, rdata is incresed by 2, and rdlen decreased by 2), - * because it then reuses existing data elsewhere in the DNS packet, while the return value might be - * larger, reflecting the total number of octects composing the domain name. - * - */ -#define PWN_EXTRACT 0 -#define PWN_COMPARE 1 -#define PWN_ORDER 2 -static int process_domain_name(struct dns_header *header, size_t pktlen, - unsigned char** rdata, size_t* rdlen, - unsigned char *out, int action) -{ - int hops = 0, total = 0, i; - unsigned char label_type; - unsigned char *end = (unsigned char *)header + pktlen; - unsigned char count; unsigned char *p = *rdata; - int nonequal = 0; - -#define PROCESS(ch) \ - do { \ - if (action == PWN_EXTRACT) \ - *out++ = ch; \ - else if (action == PWN_COMPARE) \ - { \ - if (*out++ != ch) \ - nonequal = 1; \ - } \ - else if (action == PWN_ORDER) \ - { \ - char _ch = *out++; \ - if (ch < _ch) \ - return -1; \ - else if (_ch > ch) \ - return 1; \ - } \ - } while (0) - - while (1) - { - if (p >= end) - return 0; - if (!(count = *p++)) - break; - label_type = count & 0xC0; - if (label_type == 0xC0) - { - int l2; - if (p >= end) - return 0; - l2 = *p++; - if (hops == 0) - { - if (p - *rdata > *rdlen) - return 0; - *rdlen -= p - *rdata; - *rdata = p; - } - if (++hops == 256) - return 0; - p = (unsigned char*)header + (count & 0x3F) * 256 + l2; - } - else if (label_type == 0x00) - { - if (p+count-1 >= end) - return 0; - total += count+1; - if (total >= MAXCDNAME) - return 0; - PROCESS(count); - for (i = 0; i < count; ++i) - { - unsigned char ch = *p++; - if (ch >= 'A' && ch <= 'Z') - ch += 'a' - 'A'; - PROCESS(ch); - } - } - else - return 0; /* unsupported label_type */ - } - - if (hops == 0) - { - if (p - *rdata > *rdlen) - return 0; - *rdlen -= p - *rdata; - *rdata = p; - } - ++total; - if (total >= MAXCDNAME) - return 0; - PROCESS(0); - - /* If we arrived here without early-exit, they're equal */ - if (action == PWN_ORDER) - return 0; - return nonequal ? -total : total; - - #undef PROCESS -} - - -/* RDATA meta-description. - * - * RFC4034 §6.2 introduces the concept of a "canonical form of a RR". This canonical - * form is used in two important points within the DNSSEC protocol/algorithm: - * - * 1) When computing the hash for verifying the RRSIG signature, we need to do it on - * the canonical form. - * 2) When ordering a RRset in canonical order (§6.3), we need to lexicographically sort - * the RRs in canonical form. - * - * The canonical form of a RR is specifically tricky because it also affects the RDATA, - * which is different for each RR type; in fact, RFC4034 says that "domain names in - * RDATA must be canonicalized" (= uncompressed and lower-cased). - * - * To handle this correctly, we then need a way to describe how the RDATA section is - * composed for each RR type; we don't need to describe every field, but just to specify - * where domain names are. The following array contains this description, and it is - * used by rrset_canonical_order() and verifyalg_add_rdata(), to adjust their behaviour - * for each RR type. - * - * The format of the description is very easy, for instance: - * - * { 12, RDESC_DOMAIN, RDESC_DOMAIN, 4, RDESC_DOMAIN, RDESC_END } - * - * This means that this (ficticious) RR type has a RDATA section containing 12 octects - * (we don't care what they contain), followed by 2 domain names, followed by 4 octects, - * followed by 1 domain name, and then followed by an unspecificied number of octects (0 - * or more). - */ - -#define RDESC_DOMAIN -1 -#define RDESC_END 0 -static const int rdata_description[][8] = -{ - /**/ { RDESC_END }, - /* 1: A */ { RDESC_END }, - /* 2: NS */ { RDESC_DOMAIN, RDESC_END }, - /* 3: .. */ { RDESC_END }, - /* 4: .. */ { RDESC_END }, - /* 5: CNAME */ { RDESC_DOMAIN, RDESC_END }, - /* 6: SOA */ { RDESC_DOMAIN, RDESC_DOMAIN, RDESC_END }, - /* 7: */ { RDESC_END }, - /* 8: */ { RDESC_END }, - /* 9: */ { RDESC_END }, - /* 10: */ { RDESC_END }, - /* 11: */ { RDESC_END }, - /* 12: */ { RDESC_END }, - /* 13: */ { RDESC_END }, - /* 14: */ { RDESC_END }, - /* 15: MX */ { 2, RDESC_DOMAIN, RDESC_END }, -}; - - -/* On-the-fly rdata canonicalization - * - * This set of functions allow the user to iterate over the rdata section of a RR - * while canonicalizing it on-the-fly. This is a great memory saving since the user - * doesn't need to allocate memory for a copy of the whole rdata section. - * - * Sample usage: - * - * RDataCFrom cf; - * rdata_cfrom_init( - * &cf, - * header, pktlen, // dns_header - * rdata, // pointer to rdata section - * rrtype, // RR tyep - * tmpbuf); // temporary buf (MAXCDNAME) - * - * while ((p = rdata_cfrom_next(&cf, &len)) - * { - * // Process p[0..len] - * } - * - * if (rdata_cfrom_error(&cf)) - * // error occurred while parsing - * - */ -typedef struct -{ - struct dns_header *header; - size_t pktlen; - unsigned char *rdata; - unsigned char *tmpbuf; - size_t rdlen; - int rrtype; - int cnt; -} RDataCForm; - -static void rdata_cform_init(RDataCForm *ctx, struct dns_header *header, size_t pktlen, - unsigned char *rdata, int rrtype, unsigned char *tmpbuf) -{ - if (rrtype >= countof(rdata_description)) - rrtype = 0; - ctx->header = header; - ctx->pktlen = pktlen; - ctx->rdata = rdata; - ctx->rrtype = rrtype; - ctx->tmpbuf = tmpbuf; - ctx->cnt = -1; - GETSHORT(ctx->rdlen, ctx->rdata); -} - -static int rdata_cform_error(RDataCForm *ctx) -{ - return ctx->cnt == -2; -} - -static unsigned char *rdata_cform_next(RDataCForm *ctx, size_t *len) -{ - if (ctx->cnt != -1 && rdata_description[ctx->rrtype][ctx->cnt] == RDESC_END) - return NULL; - - int d = rdata_description[ctx->rrtype][++ctx->cnt]; - if (d == RDESC_DOMAIN) - { - *len = process_domain_name(ctx->header, ctx->pktlen, &ctx->rdata, &ctx->rdlen, ctx->tmpbuf, PWN_EXTRACT); - if (!*len) - { - ctx->cnt = -2; - return NULL; - } - return ctx->tmpbuf; - } - else if (d == RDESC_END) - { - *len = ctx->rdlen; - return ctx->rdata; - } - else - { - unsigned char *ret = ctx->rdata; - ctx->rdlen -= d; - ctx->rdata += d; - *len = d; - return ret; - } -} - - -/* Check whether today/now is between date_start and date_end */ -static int check_date_range(unsigned long date_start, unsigned long date_end) -{ - /* TODO: double-check that time(0) is the correct time we are looking for */ - /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */ - unsigned long curtime = time(0); - - /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ - return serial_compare_32(curtime, date_start) == SERIAL_GT - && serial_compare_32(curtime, date_end) == SERIAL_LT; -} - - -/* Sort RRs within a RRset in canonical order, according to RFC4034, §6.3 - Notice that the RRDATA sections have been already normalized, so a memcpy - is sufficient. - NOTE: r1/r2 point immediately after the owner name. */ - -struct { - struct dns_header *header; - size_t pktlen; -} rrset_canonical_order_ctx; - -static int rrset_canonical_order(const void *r1, const void *r2) -{ - size_t r1len, r2len; - int rrtype; - unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2; - unsigned char tmp1[MAXCDNAME], tmp2[MAXCDNAME]; /* TODO: use part of daemon->namebuff */ - - GETSHORT(rrtype, pr1); - pr1 += 6; pr2 += 8; - - RDataCForm cf1, cf2; - rdata_cform_init(&cf1, rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen, - pr1, rrtype, tmp1); - rdata_cform_init(&cf2, rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen, - pr2, rrtype, tmp2); - while ((pr1 = rdata_cform_next(&cf1, &r1len)) && - (pr2 = rdata_cform_next(&cf2, &r2len))) - { - int res = memcmp(pr1, pr2, MIN(r1len,r2len)); - if (res != 0) - return res; - if (r1len < r2len) - return -1; - if (r2len > r1len) - return 1; - } - - /* If we reached this point, the two RRs are identical (or an error occurred). - RFC2181 says that an RRset is not allowed to contain duplicate - records. If it happens, it is a protocol error and anything goes. */ - return 1; -} - -typedef struct PendingRRSIGValidation -{ - VerifyAlgCtx *alg; - char *signer_name; - int keytag; -} PendingRRSIGValidation; - - /* Convert from presentation format to wire format, in place. Also map UC -> LC. Note that using extract_name to get presentation format @@ -442,80 +76,423 @@ static void from_wire(char *name) *(l-1) = 0; } - -/* Pass a resource record's rdata field through the currently-initailized digest algorithm. - - We must pass the record in DNS wire format, but if the record contains domain names, - they must be uncompressed. This makes things very tricky, because */ -static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pktlen, - unsigned char *rdata) +/* Implement RFC1982 wrapped compare for 32-bit numbers */ +static int serial_compare_32(unsigned long s1, unsigned long s2) { - size_t len; - unsigned char *p; - unsigned short total; - unsigned char tmpbuf[MAXDNAME]; /* TODO: reuse part of daemon->namebuff */ - RDataCForm cf1, cf2; + if (s1 == s2) + return SERIAL_EQ; - /* Initialize two iterations over the canonical form*/ - rdata_cform_init(&cf1, header, pktlen, rdata, sigtype, tmpbuf); - cf2 = cf1; + if ((s1 < s2 && (s2 - s1) < (1UL<<31)) || + (s1 > s2 && (s1 - s2) > (1UL<<31))) + return SERIAL_LT; + if ((s1 < s2 && (s2 - s1) > (1UL<<31)) || + (s1 > s2 && (s1 - s2) < (1UL<<31))) + return SERIAL_GT; + return SERIAL_UNDEF; +} - /* Iteration 1: go through the canonical record and count the total octects. - This number might be different from the non-canonical rdata length - because of domain names compression. */ - total = 0; - while ((p = rdata_cform_next(&cf1, &len))) - total += len; - if (rdata_cform_error(&cf1)) +/* Check whether today/now is between date_start and date_end */ +static int check_date_range(unsigned long date_start, unsigned long date_end) +{ + unsigned long curtime = time(0); + + /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ + return serial_compare_32(curtime, date_start) == SERIAL_GT + && serial_compare_32(curtime, date_end) == SERIAL_LT; +} + +static u16 *get_desc(int type) +{ + /* List of RRtypes which include domains in the data. + 0 -> domain + integer -> no of plain bytes + -1 -> end + + zero is not a valid RRtype, so the final entry is returned for + anything which needs no mangling. + */ + + static u16 rr_desc[] = + { + T_NS, 0, -1, + T_MD, 0, -1, + T_MF, 0, -1, + T_CNAME, 0, -1, + T_SOA, 0, 0, -1, + T_MB, 0, -1, + T_MG, 0, -1, + T_MR, 0, -1, + T_PTR, 0, -1, + T_MINFO, 0, 0, -1, + T_MX, 2, 0, -1, + T_RP, 0, 0, -1, + T_AFSDB, 2, 0, -1, + T_RT, 2, 0, -1, + T_SIG, 18, 0, -1, + T_PX, 2, 0, 0, -1, + T_NXT, 0, -1, + T_KX, 2, 0, -1, + T_SRV, 6, 0, -1, + T_DNAME, 0, -1, + T_RRSIG, 18, 0, -1, + T_NSEC, 0, -1, + 0, -1 /* wildcard/catchall */ + }; + + u16 *p = rr_desc; + + while (*p != type && *p != 0) + while (*p++ != (u16)-1); + + return p+1; +} + +/* Return bytes of canonicalised rdata, when the return value is zero, the remaining + data, pointed to by *p, should be used raw. */ +static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, + unsigned char **p, u16 **desc) +{ + int d = **desc; + + (*desc)++; + + /* No more data needs mangling */ + if (d == (u16)-1) return 0; - - /* Iteration 2: process the canonical record through the hash function */ - total = htons(total); - digestalg_add_data(&total, 2); - - while ((p = rdata_cform_next(&cf2, &len))) - digestalg_add_data(p, len); - - return 1; -} - -size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr) -{ - unsigned char *p; - char types[20]; - querystr("dnssec", types, type); - - if (addr->sa.sa_family == AF_INET) - log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); -#ifdef HAVE_IPV6 + if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) + /* domain-name, canonicalise */ + return to_wire(buff); else - log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types); -#endif - - header->qdcount = htons(1); - header->ancount = htons(0); - header->nscount = htons(0); - header->arcount = htons(0); - - header->hb3 = HB3_RD; - SET_OPCODE(header, QUERY); - header->hb4 = HB4_CD; - - /* ID filled in later */ - - p = (unsigned char *)(header+1); - - p = do_rfc1035_name(p, name); - *p++ = 0; - PUTSHORT(type, p); - PUTSHORT(class, p); - - return add_do_bit(header, p - (unsigned char *)header, end); + { + /* plain data preceding a domain-name, don't run off the end of the data */ + if ((end - *p) < d) + d = end - *p; + + if (d != 0) + { + memcpy(buff, *p, d); + *p += d; + } + + return d; + } } + +/* Bubble sort the RRset into the canonical order. + Note that the byte-streams from two RRs may get unsynced: consider + RRs which have two domain-names at the start and then other data. + The domain-names may have different lengths in each RR, but sort equal + + ------------ + |abcde|fghi| + ------------ + |abcd|efghi| + ------------ + + leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. +*/ + +static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) +{ + int swap, quit, i; + do + { + for (swap = 0, i = 0; i < rrsetidx-1; i++) + { + int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; + u16 *dp1, *dp2; + unsigned char *end1, *end2; + unsigned char *p1 = skip_name(rrset[i], header, plen, 10); + unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); + + p1 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen1, p1); + end1 = p1 + rdlen1; + + p2 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen2, p2); + end2 = p2 + rdlen2; + + dp1 = dp2 = rr_desc; + + for (quit = 0, left1 = 0, left2 = 0; !quit;) + { + if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0) + { + quit = 1; + len1 = end1 - p1; + memcpy(buff1 + left1, p1, len1); + } + len1 += left1; + + if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0) + { + quit = 1; + len2 = end2 - p2; + memcpy(buff2 + left2, p2, len2); + } + len2 += left2; + + if (len1 > len2) + { + left1 = len1 - len2; + left2 = 0; + len = len2; + } + else + { + left2 = len2 - len1; + left1 = 0; + len = len1; + } + + rc = memcmp(buff1, buff2, len); + + if (rc == 1 || (rc == 0 && quit && len2 > len1)) + { + unsigned char *tmp = rrset[i+1]; + rrset[i+1] = rrset[i]; + rrset[i] = tmp; + swap = quit = 1; + } + } + } + } while (swap); +} + +/* Validate a single RRset (class, type, name) in the supplied DNS reply + Return code: + STAT_SECURE if it validates. + STAT_INSECURE can't validate (no RRSIG, bad packet). + STAT_BOGUS signature is wrong. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + + if key is non-NULL, use that key, which has the algo and tag given in the params of those names, + otherwise find the key in the cache. +*/ +static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, + int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) +{ + static unsigned char **rrset = NULL, **sigs = NULL; + static int rrset_sz = 0, sig_sz = 0; + + unsigned char *p; + int rrsetidx, sigidx, res, rdlen, j; + struct crec *crecp = NULL; + int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; + u16 *rr_desc = get_desc(type); + + if (!(p = skip_questions(header, plen))) + return STAT_INSECURE; + + /* look for an RRSIG record for this RRset and get pointers to each record */ + for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); + j != 0; j--) + { + unsigned char *pstart, *pdata; + int stype, sclass, sttl; + + pstart = p; + + if (!(res = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(stype, p); + GETSHORT(sclass, p); + GETLONG(sttl, p); + + pdata = p; + + GETSHORT(rdlen, p); + + (void)sttl; + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_INSECURE; /* bad packet */ + + if (res == 1 && sclass == class) + { + if (stype == type) + { + if (rrsetidx == rrset_sz) + { + unsigned char **new; + + /* expand */ + if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **)))) + return STAT_INSECURE; + + if (rrset) + { + memcpy(new, rrset, rrset_sz * sizeof(unsigned char **)); + free(rrset); + } + + rrset = new; + rrset_sz += 5; + } + rrset[rrsetidx++] = pstart; + } + + if (stype == T_RRSIG) + { + if (sigidx == sig_sz) + { + unsigned char **new; + + /* expand */ + if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **)))) + return STAT_INSECURE; + + if (sigs) + { + memcpy(new, sigs, sig_sz * sizeof(unsigned char **)); + free(sigs); + } + + sigs = new; + sig_sz += 5; + } + sigs[sigidx++] = pdata; + } + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; + } + + /* RRset empty, no RRSIGs */ + if (rrsetidx == 0 || sigidx == 0) + return STAT_INSECURE; + + /* Sort RRset records into canonical order. + Note that at this point keyname and name buffs are + unused, and used as workspace by the sort. */ + sort_rrset(header, plen, rr_desc, rrsetidx, rrset, name, keyname); + + /* Now try all the sigs to try and find one which validates */ + for (j = 0; j sig = p; + alg->siglen = rdlen - (p - psav); + + nsigttl = htonl(orig_ttl); + + digestalg_begin(alg->vtbl->digest_algo); + digestalg_add_data(psav, 18); + wire_len = to_wire(keyname); + digestalg_add_data(keyname, wire_len); + from_wire(keyname); + + /* TODO wildcard rules : 4035 5.3.2 */ + for (i = 0; i < rrsetidx; ++i) + { + int seg; + unsigned char *end, *cp; + u16 len, *dp; + + p = rrset[i]; + if (!extract_name(header, plen, &p, name, 1, 10)) + return STAT_INSECURE; + wire_len = to_wire(name); + digestalg_add_data(name, wire_len); + from_wire(name); /* leave name unchanged on exit */ + digestalg_add_data(p, 4); /* class and type */ + digestalg_add_data(&nsigttl, 4); + + p += 8; /* skip class, type, ttl */ + GETSHORT(rdlen, p); + end = p + rdlen; + + /* canonicalise rdata and calculate length of same, use name buffer as workspace */ + cp = p; + dp = rr_desc; + for (len = 0; (seg = get_rdata(header, plen, end, name, &cp, &dp)) != 0; len += seg); + len += end - cp; + len = htons(len); + digestalg_add_data(&len, 2); + + /* Now canonicalise again and digest. */ + cp = p; + dp = rr_desc; + while ((seg = get_rdata(header, plen, end, name, &cp, &dp))) + digestalg_add_data(name, seg); + if (cp != end) + digestalg_add_data(cp, end - cp); + + /* namebuff used for workspace, above, restore for next loop + and to leave unchanged on exit */ + p = (unsigned char*)(rrset[i]); + extract_name(header, plen, &p, name, 1, 0); + } + + memcpy(alg->digest, digestalg_final(), digestalg_len()); + + if (key) + { + if (algo_in == algo && keytag_in == key_tag && + alg->vtbl->verify(alg, key, keylen)) + return STAT_SECURE; + } + else + { + /* iterate through all possible keys 4035 5.3.1 */ + for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) + if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag && + alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid)) + return STAT_SECURE; + } + } + + return STAT_BOGUS; +} + /* The DNS packet is expected to contain the answer to a DNSKEY query. - Leave name of qury in name. + Leave name of query in name. Put all DNSKEYs in the answer which are valid into the cache. return codes: STAT_INSECURE bad packet, no DNSKEYs in reply. @@ -531,12 +508,13 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; struct blockdata *key; - if (ntohs(header->qdcount) != 1) - return STAT_INSECURE; - - if (!extract_name(header, plen, &p, name, 1, 4)) - return STAT_INSECURE; - + if (ntohs(header->qdcount) != 1 || + !extract_name(header, plen, &p, name, 1, 4)) + { + strcpy(name, ""); + return STAT_INSECURE; + } + GETSHORT(qtype, p); GETSHORT(qclass, p); @@ -641,7 +619,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch return STAT_BOGUS; } - /* The DNS packet is expected to contain the answer to a DS query Leave name of DS query in name. Put all DSs in the answer which are valid into the cache. @@ -659,12 +636,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char int qtype, qclass, val, j, gotone; struct blockdata *key; - if (ntohs(header->qdcount) != 1) - return STAT_INSECURE; - - if (!extract_name(header, plen, &p, name, 1, 4)) - return STAT_INSECURE; - + if (ntohs(header->qdcount) != 1 || + !extract_name(header, plen, &p, name, 1, 4)) + { + strcpy(name, ""); + return STAT_INSECURE; + } + GETSHORT(qtype, p); GETSHORT(qclass, p); @@ -735,179 +713,6 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char return STAT_SECURE; } - - -/* Validate a single RRset (class, type, name) in the supplied DNS reply - Return code: - STAT_SECURE if it validates. - STAT_INSECURE can't validate (no RRSIG, bad packet). - STAT_BOGUS signature is wrong. - STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) - - if key is non-NULL, use that key, which has the algo and tag given in the params of those names, - otherwise find the key in the cache. -*/ -int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, - int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) -{ - unsigned char *p; - int rrsetidx, sigidx, res, rdlen, j; - struct crec *crecp = NULL; - void *rrset[MAXRRSET], *sigs[MAXRRSET]; /* TODO: max RRset size? */ - int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; - - if (!(p = skip_questions(header, plen))) - return STAT_INSECURE; - - /* look for an RRSIG record for this RRset and get pointers to each record */ - for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); - j != 0; j--) - { - unsigned char *pstart; - int stype, sclass, sttl; - - if (!(res = extract_name(header, plen, &p, name, 0, 10))) - return STAT_INSECURE; /* bad packet */ - - pstart = p; - - GETSHORT(stype, p); - GETSHORT(sclass, p); - GETLONG(sttl, p); - GETSHORT(rdlen, p); - - (void)sttl; - - if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_INSECURE; /* bad packet */ - - if (res == 1 && sclass == class) - { - if (stype == type) - { - rrset[rrsetidx++] = pstart; - if (rrsetidx == MAXRRSET) - return STAT_INSECURE; /* RRSET too big TODO */ - } - - if (stype == T_RRSIG) - { - sigs[sigidx++] = pstart; - if (sigidx == MAXRRSET) - return STAT_INSECURE; /* RRSET too big TODO */ - } - } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_INSECURE; - } - - /* RRset empty, no RRSIGs */ - if (rrsetidx == 0 || sigidx == 0) - return STAT_INSECURE; - - /* Now try all the sigs to try and find one which validates */ - for (j = 0; j sig = p; - alg->siglen = rdlen - (p - psav); - - ntype = htons(type); - nclass = htons(class); - nsigttl = htonl(orig_ttl); - - digestalg_begin(alg->vtbl->digest_algo); - digestalg_add_data(psav, 18); - wire_len = to_wire(keyname); - digestalg_add_data(keyname, wire_len); - from_wire(keyname); - - /* TODO wildcard rules : 4035 5.3.2 */ - for (i = 0; i < rrsetidx; ++i) - { - p = (unsigned char*)(rrset[i]); - - wire_len = to_wire(name); - digestalg_add_data(name, wire_len); - from_wire(name); - digestalg_add_data(&ntype, 2); - digestalg_add_data(&nclass, 2); - digestalg_add_data(&nsigttl, 4); - - p += 8; - if (!digestalg_add_rdata(type, header, plen, p)) - return STAT_INSECURE; - } - - memcpy(alg->digest, digestalg_final(), digestalg_len()); - - if (key) - { - if (algo_in == algo && keytag_in == key_tag && - alg->vtbl->verify(alg, key, keylen)) - return STAT_SECURE; - } - else - { - /* iterate through all possible keys 4035 5.3.1 */ - for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) - if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag && - alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid)) - return STAT_SECURE; - } - } - - return STAT_BOGUS; -} - - /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class) { @@ -987,5 +792,39 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen) } } +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr) +{ + unsigned char *p; + char types[20]; + + querystr("dnssec", types, type); + if (addr->sa.sa_family == AF_INET) + log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); +#ifdef HAVE_IPV6 + else + log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types); +#endif + + header->qdcount = htons(1); + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + + header->hb3 = HB3_RD; + SET_OPCODE(header, QUERY); + header->hb4 = HB4_CD; + + /* ID filled in later */ + + p = (unsigned char *)(header+1); + + p = do_rfc1035_name(p, name); + *p++ = 0; + PUTSHORT(type, p); + PUTSHORT(class, p); + + return add_do_bit(header, p - (unsigned char *)header, end); +} + #endif /* HAVE_DNSSEC */ diff --git a/src/rfc1035.c b/src/rfc1035.c index 7d48910..0b254e3 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -493,7 +493,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t else if (is_sign && i == arcount - 1 && class == C_ANY && - (type == T_SIG || type == T_TSIG)) + type == T_TSIG) *is_sign = 1; }