DNSSEC consolidation.
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 @@
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 <assert.h>
-
-/* 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 @@
*(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 <sigidx; j++)
+ {
+ unsigned char *psav;
+ int i, wire_len;
+ VerifyAlgCtx *alg;
+ u32 nsigttl;
+
+ p = sigs[j];
+
+ GETSHORT(rdlen, p);
+
+ if (rdlen < 18)
+ return STAT_INSECURE; /* bad packet */
+
+ psav = p;
+
+ GETSHORT(type_covered, p);
+ algo = *p++;
+ labels = *p++;
+ GETLONG(orig_ttl, p);
+ GETLONG(sig_expiration, p);
+ GETLONG(sig_inception, p);
+ GETSHORT(key_tag, p);
+
+ if (type_covered != type ||
+ !check_date_range(sig_inception, sig_expiration) ||
+ !verifyalg_supported(algo))
+ {
+ /* covers wrong type or out of date - skip */
+ p = psav;
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return STAT_INSECURE;
+ continue;
+ }
+
+ if (!extract_name(header, plen, &p, keyname, 1, 0))
+ return STAT_INSECURE;
+
+ /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
+ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
+ return STAT_NEED_KEY;
+
+ alg = verifyalg_alloc(algo);
+ alg->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 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, "<none>");
+ return STAT_INSECURE;
+ }
+
GETSHORT(qtype, p);
GETSHORT(qclass, p);
@@ -641,7 +619,6 @@
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 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, "<none>");
+ return STAT_INSECURE;
+ }
+
GETSHORT(qtype, p);
GETSHORT(qclass, p);
@@ -735,179 +713,6 @@
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 <sigidx; j++)
- {
- unsigned char *psav;
- int i, wire_len;
- VerifyAlgCtx *alg;
- u16 ntype, nclass;
- u32 nsigttl;
-
- p = sigs[j] + 8; /* skip type, class and ttl */
-
- GETSHORT(rdlen, p);
-
- if (rdlen < 18)
- return STAT_INSECURE; /* bad packet */
-
- psav = p;
-
- GETSHORT(type_covered, p);
- algo = *p++;
- labels = *p++;
- GETLONG(orig_ttl, p);
- GETLONG(sig_expiration, p);
- GETLONG(sig_inception, p);
- GETSHORT(key_tag, p);
-
- if (type_covered != type ||
- !check_date_range(sig_inception, sig_expiration) ||
- !verifyalg_supported(algo))
- {
- /* covers wrong type or out of date - skip */
- p = psav;
- if (!ADD_RDLEN(header, p, plen, rdlen))
- return STAT_INSECURE;
- continue;
- }
-
- if (!extract_name(header, plen, &p, keyname, 1, 0))
- return STAT_INSECURE;
-
- /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
- if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
- return STAT_NEED_KEY;
-
- /* Sort RRset records in canonical order. */
- rrset_canonical_order_ctx.header = header;
- rrset_canonical_order_ctx.pktlen = plen;
- qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
-
- alg = verifyalg_alloc(algo);
- alg->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 @@
}
}
+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 @@
else if (is_sign &&
i == arcount - 1 &&
class == C_ANY &&
- (type == T_SIG || type == T_TSIG))
+ type == T_TSIG)
*is_sign = 1;
}