diff options
author | Simon Kelley <simon@thekelleys.org.uk> | 2013-12-31 13:50:39 +0000 |
---|---|---|
committer | Simon Kelley <simon@thekelleys.org.uk> | 2013-12-31 13:50:39 +0000 |
commit | c3e0b9b6e75001ea2d24a4f67537ed77d0f0210c (patch) | |
tree | cce5aff03d1fd76694384f8a4e5ea2c8a62c97c9 | |
parent | 963c380d13ef77aa61b69960c738d90fdd68980c (diff) | |
download | dnsmasq-c3e0b9b6e75001ea2d24a4f67537ed77d0f0210c.tar.gz |
backup
-rw-r--r-- | src/cache.c | 20 | ||||
-rw-r--r-- | src/dns-protocol.h | 6 | ||||
-rw-r--r-- | src/dnsmasq.c | 6 | ||||
-rw-r--r-- | src/dnsmasq.h | 14 | ||||
-rw-r--r-- | src/dnssec.c | 359 | ||||
-rw-r--r-- | src/forward.c | 35 | ||||
-rw-r--r-- | src/rfc1035.c | 7 |
7 files changed, 413 insertions, 34 deletions
diff --git a/src/cache.c b/src/cache.c index 1338681..cfbeae3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -26,7 +26,7 @@ static union bigname *big_free = NULL; static int bignames_left, hash_size; static int uid = 1; #ifdef HAVE_DNSSEC -static struct keydata *keyblock_free = NULL; +static struct blockdata *keyblock_free = NULL; #endif /* type->string mapping: this is also used by the name-hash function as a mixing table. */ @@ -198,7 +198,7 @@ static void cache_free(struct crec *crecp) } #ifdef HAVE_DNSSEC else if (crecp->flags & (F_DNSKEY | F_DS)) - keydata_free(crecp->addr.key.keydata); + blockdata_free(crecp->addr.key.keydata); #endif } @@ -1361,10 +1361,10 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) } #ifdef HAVE_DNSSEC -struct keydata *keydata_alloc(char *data, size_t len) +struct blockdata *blockdata_alloc(char *data, size_t len) { - struct keydata *block, *ret = NULL; - struct keydata **prev = &ret; + struct blockdata *block, *ret = NULL; + struct blockdata **prev = &ret; size_t blen; while (len > 0) @@ -1375,12 +1375,12 @@ struct keydata *keydata_alloc(char *data, size_t len) keyblock_free = block->next; } else - block = whine_malloc(sizeof(struct keydata)); + block = whine_malloc(sizeof(struct blockdata)); if (!block) { /* failed to alloc, free partial chain */ - keydata_free(ret); + blockdata_free(ret); return NULL; } @@ -1396,7 +1396,7 @@ struct keydata *keydata_alloc(char *data, size_t len) return ret; } -size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt) +size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt) { if (*p == NULL) *p = (*key)->key; @@ -1411,9 +1411,9 @@ size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt) return MIN(cnt, (*key)->key + KEYBLOCK_LEN - (*p)); } -void keydata_free(struct keydata *blocks) +void blockdata_free(struct blockdata *blocks) { - struct keydata *tmp; + struct blockdata *tmp; if (blocks) { diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 07cc768..efecb1c 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -140,3 +140,9 @@ struct dns_header { GETLONG(var, ptr); \ (len) -= 4; \ } while (0) + +#define CHECK_LEN(header, pp, plen, len) \ + ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) + +#define ADD_RDLEN(header, pp, plen, len) \ + (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1)) diff --git a/src/dnsmasq.c b/src/dnsmasq.c index f5ce7df..3e5f51e 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -81,8 +81,14 @@ int main (int argc, char **argv) umask(022); /* known umask, create leases and pid files as 0644 */ read_opts(argc, argv, compile_opts); + +#ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) if (daemon->doctors) exit(1); /* TODO */ + + daemon->keyname = safe_malloc(MAXDNAME); +#endif + if (daemon->edns_pktsz < PACKETSZ) daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALID) ? EDNS_PKTSZ : PACKETSZ; daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 241f9a5..8a3541a 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -335,8 +335,8 @@ union bigname { union bigname *next; /* freelist */ }; -struct keydata { - struct keydata *next; +struct blockdata { + struct blockdata *next; unsigned char key[KEYBLOCK_LEN]; }; @@ -528,6 +528,7 @@ struct frec { unsigned int crc; time_t time; #ifdef HAVE_DNSSEC + int class; struct blockdata *stash; /* Saved reply, whilst we validate */ size_t stash_len; struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ @@ -900,6 +901,9 @@ extern struct daemon { char *packet; /* packet buffer */ int packet_buff_sz; /* size of above */ char *namebuff; /* MAXDNAME size buffer */ +#ifdef HAVE_DNSSEC + char *keyname; /* MAXDNAME size buffer */ +#endif unsigned int local_answer, queries_forwarded, auth_answer; struct frec *frec_list; struct serverfd *sfds; @@ -1030,7 +1034,11 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ -int dnssec_validate(int flags, struct dns_header *header, size_t plen); +size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type); +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); +int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, int *class); /* util.c */ void rand_init(void); diff --git a/src/dnssec.c b/src/dnssec.c index a1f7856..4a91137 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -21,11 +21,15 @@ /* 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 +static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen); + /* Implement RFC1982 wrapped compare for 32-bit numbers */ static int serial_compare_32(unsigned long s1, unsigned long s2) { @@ -494,7 +498,359 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk return 1; } +size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type) +{ + unsigned char *p; + + 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 = 0; + + /* ID filled in later */ + + p = (unsigned char *)(header+1); + + p = do_rfc1035_name(p, name); + PUTSHORT(type, p); + PUTSHORT(class, p); + + return add_do_bit(header, p - (unsigned char *)header, ((char *) header) + PACKETSZ); +} + +/* The DNS packet is expected to contain the answer to a DNSKEY query + Put all DNSKEYs in the answer which are valid into the cache. + return codes: + STAT_INSECURE bad packet, no DNSKEYs in reply. + STAT_SECURE At least one valid DNSKEY found and in cache. + STAT_BOGUS At least one DNSKEY found, which fails validation. + STAT_NEED_DS DS records to validate a key not found, name in namebuff +*/ +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +{ + unsigned char *p; + struct crec *crecp, *recp1; + int j, qtype, qclass, ttl, rdlen, flags, protocol, algo, gotone; + struct blockdata *key; + + if (ntohs(header->qdcount) != 1) + return STAT_INSECURE; + + if (!extract_name(header, plen, &p, name, 1, 4)) + return STAT_INSECURE; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qtype != T_DNSKEY || qclass != class) + return STAT_INSECURE; + + cache_start_insert(); + + for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) + { + /* Ensure we have type, class TTL and length */ + if (!extract_name(header, plen, &p, name, 1, 10)) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (qclass != class || qtype != T_DNSKEY || rdlen < 4) + { + /* skip all records other than DNSKEY */ + p += rdlen; + continue; + } + + crecp = cache_find_by_name(NULL, name, now, F_DS); + + /* length at least covers flags, protocol and algo now. */ + GETSHORT(flags, p); + protocol = *p++; + algo = *p++; + + /* See if we have cached a DS record which validates this key */ + for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) + if (recp1->addr.key.algo == algo && is_supported_digest(recp1->addr.key.digest)) + break; + + /* DS record needed to validate key is missing, return name of DS in namebuff */ + if (!recp1) + return STAT_NEED_DS; + else + { + int valid = 1; + /* calculate digest of canonicalised DNSKEY data using digest in (recp1->addr.key.digest) + and see if it equals digest stored in recp1 + */ + + if (!valid) + return STAT_BOGUS; + } + + if ((key = blockdata_alloc((char*)p, rdlen))) + { + + /* We've proved that the KEY is OK, store it in the cache */ + if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY))) + { + crecp->uid = rdlen; + crecp->addr.key.keydata = key; + crecp->addr.key.algo = algo; + crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); + gotone = 1; + } + } + + } + + cache_end_insert(); + + + return gotone ? STAT_SECURE : STAT_INSECURE; +} +/* The DNS packet is expected to contain the answer to a DS query + Put all DSs in the answer which are valid into the cache. + return codes: + STAT_INSECURE bad packet, no DNSKEYs in reply. + STAT_SECURE At least one valid DS found and in cache. + STAT_BOGUS At least one DS found, which fails validation. + STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname +*/ + +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +{ + unsigned char *p = (unsigned char *)(header+1); + struct crec *crecp, *recp1; + 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; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qtype != T_DS || qclass != class) + return STAT_INSECURE; + + val = validate_rrset(header, plen, class, T_DS, name, keyname); + + /* failed to validate or missing key. */ + if (val != STAT_SECURE) + return val; + + cache_start_insert(); + + for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) + { + int ttl, rdlen, rc, algo; + + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + /* check type, class and name, skip if not in DS rrset */ + if (qclass != class || qtype != T_DS || rc == 2) + { + p += rdlen; + continue; + } + + if ((key = blockdata_alloc((char*)p, rdlen))) + { + + /* We've proved that the DS is OK, store it in the cache */ + if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS))) + { + crecp->uid = rdlen; + crecp->addr.key.keydata = key; + crecp->addr.key.algo = algo; + crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); + gotone = 1; + } + } + + } + + cache_end_insert(); + + + return gotone ? STAT_SECURE : STAT_INSECURE; +} + + +/* 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) +*/ +int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname) +{ + unsigned char *p, *psav, *sig; + int rrsetidx, res, sigttl, sig_data_len, j; + struct crec *crecp; + void *rrset[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, sig = NULL, j = ntohs(header->ancount) + ntohs(header->nscount); + j != 0; j--) + { + unsigned char *pstart = p; + int stype, sclass, sttl, rdlen; + + if (!(res = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(stype, p); + GETSHORT(sclass, p); + GETLONG(sttl, p); + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_INSECURE; /* bad packet */ + + if (res == 2 || htons(stype) != T_RRSIG || htons(sclass) != class) + continue; + + if (htons(stype) == type) + { + rrset[rrsetidx++] = pstart; + if (rrsetidx == MAXRRSET) + return STAT_INSECURE; /* RRSET too big TODO */ + } + + if (htons(stype) == T_RRSIG) + { + /* name matches, RRSIG for correct class */ + /* enough data? */ + if (rdlen < 18) + return STAT_INSECURE; + + 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)) + { + /* 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. */ + for (crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY); + crecp; + crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) + if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag) + break; + + /* No, abort for now whilst we get it */ + if (!crecp) + return STAT_NEED_KEY; + + /* Save point to signature data */ + sig = p; + sig_data_len = rdlen - (p - psav); + sigttl = sttl; + + /* next record */ + p = psav; + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; + } + } + + /* Didn't find RRSIG or RRset is empty */ + if (!sig || rrsetidx == 0) + return STAT_INSECURE; + + /* OK, we have an RRSIG and an RRset and we have a the DNSKEY that validates them. */ + + /* 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); + + /* Now initialize the signature verification algorithm and process the whole + RRset */ + VerifyAlgCtx *alg = verifyalg_alloc(algo); + if (!alg) + return STAT_INSECURE; + + alg->sig = sig; + alg->siglen = sig_data_len; + + u16 ntype = htons(type); + u16 nclass = htons(class); + u32 nsigttl = htonl(sigttl); + + /* TODO: we shouldn't need to convert this to wire here. Best solution would be: + - Use process_name() instead of extract_name() everywhere in dnssec code + - Convert from wire format to representation format only for querying/storing cache + */ + unsigned char owner_wire[MAXCDNAME]; + int owner_wire_len = convert_domain_to_wire(name, owner_wire); + + digestalg_begin(alg->vtbl->digest_algo); + digestalg_add_data(sigrdata, 18+signer_name_rdlen); + for (i = 0; i < rrsetidx; ++i) + { + p = (unsigned char*)(rrset[i]); + + digestalg_add_data(owner_wire, owner_wire_len); + digestalg_add_data(&ntype, 2); + digestalg_add_data(&nclass, 2); + digestalg_add_data(&nsigttl, 4); + + p += 8; + if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p)) + return 0; + } + int digest_len = digestalg_len(); + memcpy(alg->digest, digestalg_final(), digest_len); + + if (alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp_uid)) + return STAT_SECURE; + + return STAT_INSECURE; +} + + +#if 0 static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, unsigned char *reply, int count, char *owner, int sigclass, int sigrdlen, unsigned char *sig, @@ -624,6 +980,7 @@ static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_d return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid); } + static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, unsigned char *reply, int count, char *owner, int sigclass, int sigrdlen, unsigned char *sig) @@ -663,6 +1020,8 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, } } +#endif /* comment out */ + /* Compute keytag (checksum to quickly index a key). See RFC4034 */ static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen) { diff --git a/src/forward.c b/src/forward.c index 97f8800..7e422e3 100644 --- a/src/forward.c +++ b/src/forward.c @@ -678,15 +678,14 @@ void reply_query(int fd, int family, time_t now) if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) { int status; - char rrbitmap[256/8]; int class; - if (forward->flags && FREC_DNSSKEY_QUERY) - status = dnssec_validate_by_ds(header, n, daemon->namebuff, &class); - else if (forward->flags && FREC_DS_QUERY) - status = dnssec_validate_dnskey(header, n, daemon->namebuff, &class); + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); else - status = dnssec_validate_reply(&rrbitmap, header, n, daemon->namebuff, &class); + status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); /* Can't validate, as we're missing key data. Put this answer aside, whilst we get that. */ @@ -717,7 +716,7 @@ void reply_query(int fd, int family, time_t now) } new->crc = questions_crc(header, nn, daemon->namebuff); new->new_id = get_id(new->crc); - header->id = htons(new->id); + header->id = htons(new->new_id); /* Don't resend this. */ daemon->srv_save = NULL; @@ -751,22 +750,30 @@ void reply_query(int fd, int family, time_t now) and validate them with the new data. Failure to find needed data here is an internal error. Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ - while (forward->flags & FREC_DNSSEC_QUERY) + while (forward->dependent) { - if (status == STAT_SECURE) - extract_dnssec_replies(); + struct frec *prev = forward->dependent; free_frec(forward); - forward = forward->dependent; + forward = prev; blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header); n = forward->stash_len; if (status == STAT_SECURE) { - status = dnssec_validate(forward->flags, header, n); - if (status == STAT_NEED_DS || status == STAT_NEED_KEY) - my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + status = dnssec_validate_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); } } + /* All DNSKEY and DS records done and in cache, now finally validate original + answer, provided last DNSKEY is OK. */ + if (status == STAT_SECURE) + status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); + if (status == STAT_SECURE) cache_secure = 1; /* TODO return SERVFAIL here */ diff --git a/src/rfc1035.c b/src/rfc1035.c index ab0b239..e547782 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -16,13 +16,6 @@ #include "dnsmasq.h" - -#define CHECK_LEN(header, pp, plen, len) \ - ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) - -#define ADD_RDLEN(header, pp, plen, len) \ - (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1)) - int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, char *name, int isExtract, int extrabytes) { |