--- embedaddon/dnsmasq/src/dnssec.c 2016/11/02 09:57:01 1.1.1.2 +++ embedaddon/dnsmasq/src/dnssec.c 2021/03/17 00:56:46 1.1.1.3 @@ -1,5 +1,5 @@ /* dnssec.c is Copyright (c) 2012 Giovanni Bajo - and Copyright (c) 2012-2016 Simon Kelley + and Copyright (c) 2012-2020 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,332 +19,11 @@ #ifdef HAVE_DNSSEC -#include -#include -#ifndef NO_NETTLE_ECC -# include -# include -#endif -#include -#include - -/* Nettle-3.0 moved to a new API for DSA. We use a name that's defined in the new API - to detect Nettle-3, and invoke the backwards compatibility mode. */ -#ifdef dsa_params_init -#include -#endif - #define SERIAL_UNDEF -100 #define SERIAL_EQ 0 #define SERIAL_LT -1 #define SERIAL_GT 1 -/* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ -static char *ds_digest_name(int digest) -{ - switch (digest) - { - case 1: return "sha1"; - case 2: return "sha256"; - case 3: return "gosthash94"; - case 4: return "sha384"; - default: return NULL; - } -} - -/* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ -static char *algo_digest_name(int algo) -{ - switch (algo) - { - case 1: return "md5"; - case 3: return "sha1"; - case 5: return "sha1"; - case 6: return "sha1"; - case 7: return "sha1"; - case 8: return "sha256"; - case 10: return "sha512"; - case 12: return "gosthash94"; - case 13: return "sha256"; - case 14: return "sha384"; - default: return NULL; - } -} - -/* http://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ -static char *nsec3_digest_name(int digest) -{ - switch (digest) - { - case 1: return "sha1"; - default: return NULL; - } -} - -/* Find pointer to correct hash function in nettle library */ -static const struct nettle_hash *hash_find(char *name) -{ - int i; - - if (!name) - return NULL; - - for (i = 0; nettle_hashes[i]; i++) - { - if (strcmp(nettle_hashes[i]->name, name) == 0) - return nettle_hashes[i]; - } - - return NULL; -} - -/* expand ctx and digest memory allocations if necessary and init hash function */ -static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) -{ - static void *ctx = NULL; - static unsigned char *digest = NULL; - static unsigned int ctx_sz = 0; - static unsigned int digest_sz = 0; - - void *new; - - if (ctx_sz < hash->context_size) - { - if (!(new = whine_malloc(hash->context_size))) - return 0; - if (ctx) - free(ctx); - ctx = new; - ctx_sz = hash->context_size; - } - - if (digest_sz < hash->digest_size) - { - if (!(new = whine_malloc(hash->digest_size))) - return 0; - if (digest) - free(digest); - digest = new; - digest_sz = hash->digest_size; - } - - *ctxp = ctx; - *digestp = digest; - - hash->init(ctx); - - return 1; -} - -static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - unsigned char *p; - size_t exp_len; - - static struct rsa_public_key *key = NULL; - static mpz_t sig_mpz; - - (void)digest_len; - - if (key == NULL) - { - if (!(key = whine_malloc(sizeof(struct rsa_public_key)))) - return 0; - - nettle_rsa_public_key_init(key); - mpz_init(sig_mpz); - } - - if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL))) - return 0; - - key_len--; - if ((exp_len = *p++) == 0) - { - GETSHORT(exp_len, p); - key_len -= 2; - } - - if (exp_len >= key_len) - return 0; - - key->size = key_len - exp_len; - mpz_import(key->e, exp_len, 1, 1, 0, 0, p); - mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len); - - mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig); - - switch (algo) - { - case 1: - return nettle_rsa_md5_verify_digest(key, digest, sig_mpz); - case 5: case 7: - return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz); - case 8: - return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz); - case 10: - return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz); - } - - return 0; -} - -static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - unsigned char *p; - unsigned int t; - - static struct dsa_public_key *key = NULL; - static struct dsa_signature *sig_struct; - - (void)digest_len; - - if (key == NULL) - { - if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || - !(key = whine_malloc(sizeof(struct dsa_public_key)))) - return 0; - - nettle_dsa_public_key_init(key); - nettle_dsa_signature_init(sig_struct); - } - - if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL))) - return 0; - - t = *p++; - - if (key_len < (213 + (t * 24))) - return 0; - - mpz_import(key->q, 20, 1, 1, 0, 0, p); p += 20; - mpz_import(key->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - mpz_import(key->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - mpz_import(key->y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - - mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1); - mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21); - - (void)algo; - - return nettle_dsa_sha1_verify_digest(key, digest, sig_struct); -} - -#ifndef NO_NETTLE_ECC -static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len, - unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - unsigned char *p; - unsigned int t; - struct ecc_point *key; - - static struct ecc_point *key_256 = NULL, *key_384 = NULL; - static mpz_t x, y; - static struct dsa_signature *sig_struct; - - if (!sig_struct) - { - if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature)))) - return 0; - - nettle_dsa_signature_init(sig_struct); - mpz_init(x); - mpz_init(y); - } - - switch (algo) - { - case 13: - if (!key_256) - { - if (!(key_256 = whine_malloc(sizeof(struct ecc_point)))) - return 0; - - nettle_ecc_point_init(key_256, &nettle_secp_256r1); - } - - key = key_256; - t = 32; - break; - - case 14: - if (!key_384) - { - if (!(key_384 = whine_malloc(sizeof(struct ecc_point)))) - return 0; - - nettle_ecc_point_init(key_384, &nettle_secp_384r1); - } - - key = key_384; - t = 48; - break; - - default: - return 0; - } - - if (sig_len != 2*t || key_len != 2*t || - !(p = blockdata_retrieve(key_data, key_len, NULL))) - return 0; - - mpz_import(x, t , 1, 1, 0, 0, p); - mpz_import(y, t , 1, 1, 0, 0, p + t); - - if (!ecc_point_set(key, x, y)) - return 0; - - mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig); - mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t); - - return nettle_ecdsa_verify(key, digest_len, digest, sig_struct); -} -#endif - -static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - - /* Enure at runtime that we have support for this digest */ - if (!hash_find(algo_digest_name(algo))) - return NULL; - - /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */ - switch (algo) - { - case 1: case 5: case 7: case 8: case 10: - return dnsmasq_rsa_verify; - - case 3: case 6: - return dnsmasq_dsa_verify; - -#ifndef NO_NETTLE_ECC - case 13: case 14: - return dnsmasq_ecdsa_verify; -#endif - } - - return NULL; -} - -static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - - int (*func)(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo); - - func = verify_func(algo); - - if (!func) - return 0; - - return (*func)(key_data, key_len, sig, sig_len, digest, digest_len, algo); -} - /* Convert from presentation format to wire format, in place. Also map UC -> LC. Note that using extract_name to get presentation format @@ -358,7 +37,7 @@ static int verify(struct blockdata *key_data, unsigned character. In theory, if all the characters in a name were /000 or '.' or NAME_ESCAPE then all would have to be escaped, so the presentation format would be twice as long as the spec (1024). - The buffers are all delcared as 2049 (allowing for the trailing zero) + The buffers are all declared as 2049 (allowing for the trailing zero) for this reason. */ static int to_wire(char *name) @@ -424,15 +103,17 @@ static void from_wire(char *name) static int count_labels(char *name) { int i; - + char *p; + if (*name == 0) return 0; - for (i = 0; *name; name++) - if (*name == '.') + for (p = name, i = 0; *p; p++) + if (*p == '.') i++; - return i+1; + /* Don't count empty first label. */ + return *name == '.' ? i : i+1; } /* Implement RFC1982 wrapped compare for 32-bit numbers */ @@ -475,7 +156,7 @@ int setup_timestamp(void) if (difftime(timestamp_time, time(0)) <= 0) { /* time already OK, update timestamp, and do key checking from the start. */ - if (utime(daemon->timestamp_file, NULL) == -1) + if (utimes(daemon->timestamp_file, NULL) == -1) my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); daemon->back_to_the_future = 1; return 0; @@ -489,12 +170,14 @@ int setup_timestamp(void) int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666); if (fd != -1) { - struct utimbuf timbuf; + struct timeval tv[2]; close(fd); - timestamp_time = timbuf.actime = timbuf.modtime = 1420070400; /* 1-1-2015 */ - if (utime(daemon->timestamp_file, &timbuf) == 0) + timestamp_time = 1420070400; /* 1-1-2015 */ + tv[0].tv_sec = tv[1].tv_sec = timestamp_time; + tv[0].tv_usec = tv[1].tv_usec = 0; + if (utimes(daemon->timestamp_file, tv) == 0) goto check_and_exit; } } @@ -503,10 +186,8 @@ int setup_timestamp(void) } /* Check whether today/now is between date_start and date_end */ -static int check_date_range(u32 date_start, u32 date_end) +static int is_check_date(unsigned long curtime) { - unsigned long curtime = time(0); - /* Checking timestamps may be temporarily disabled */ /* If the current time if _before_ the timestamp @@ -519,157 +200,207 @@ static int check_date_range(u32 date_start, u32 date_e { if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0) { - if (utime(daemon->timestamp_file, NULL) != 0) + if (utimes(daemon->timestamp_file, NULL) != 0) my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); + my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSEC signature timestamps.")); daemon->back_to_the_future = 1; - set_option_bool(OPT_DNSSEC_TIME); + daemon->dnssec_no_time_check = 0; queue_event(EVENT_RELOAD); /* purge cache */ } - if (daemon->back_to_the_future == 0) - return 1; + return daemon->back_to_the_future; } - else if (option_bool(OPT_DNSSEC_TIME)) - return 1; - + else + return !daemon->dnssec_no_time_check; +} + +/* Check whether today/now is between date_start and date_end */ +static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end) +{ /* 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; } -/* 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, int bufflen, - unsigned char **p, u16 **desc) +/* Return bytes of canonicalised rrdata one by one. + Init state->ip with the RR, and state->end with the end of same. + Init state->op to NULL. + Init state->desc to RR descriptor. + Init state->buff with a MAXDNAME * 2 buffer. + + After each call which returns 1, state->op points to the next byte of data. + On returning 0, the end has been reached. +*/ +struct rdata_state { + u16 *desc; + size_t c; + unsigned char *end, *ip, *op; + char *buff; +}; + +static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state) { - int d = **desc; + int d; - /* No more data needs mangling */ - if (d == (u16)-1) + if (state->op && state->c != 1) { - /* If there's more data than we have space for, just return what fits, - we'll get called again for more chunks */ - if (end - *p > bufflen) - { - memcpy(buff, *p, bufflen); - *p += bufflen; - return bufflen; - } - - return 0; + state->op++; + state->c--; + return 1; } - - (*desc)++; - - if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) - /* domain-name, canonicalise */ - return to_wire(buff); - else - { - /* plain data preceding a domain-name, don't run off the end of the data */ - if ((end - *p) < d) - d = end - *p; + + while (1) + { + d = *(state->desc); - if (d != 0) + if (d == (u16)-1) { - memcpy(buff, *p, d); - *p += d; + /* all the bytes to the end. */ + if ((state->c = state->end - state->ip) != 0) + { + state->op = state->ip; + state->ip = state->end;; + } + else + return 0; } + else + { + state->desc++; + + if (d == (u16)0) + { + /* domain-name, canonicalise */ + int len; + + if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) || + (len = to_wire(state->buff)) == 0) + continue; + + state->c = len; + state->op = (unsigned char *)state->buff; + } + else + { + /* plain data preceding a domain-name, don't run off the end of the data */ + if ((state->end - state->ip) < d) + d = state->end - state->ip; + + if (d == 0) + continue; + + state->op = state->ip; + state->c = d; + state->ip += d; + } + } - return d; + return 1; } } -/* 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 +/* Bubble sort the RRset into the canonical order. */ - ------------ - |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) +static int 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; + int swap, i, j; 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; + int rdlen1, rdlen2; + struct rdata_state state1, state2; + /* Note that these have been determined to be OK previously, so we don't need to check for NULL return here. */ - unsigned char *p1 = skip_name(rrset[i], header, plen, 10); - unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); + state1.ip = skip_name(rrset[i], header, plen, 10); + state2.ip = skip_name(rrset[i+1], header, plen, 10); + state1.op = state2.op = NULL; + state1.buff = buff1; + state2.buff = buff2; + state1.desc = state2.desc = rr_desc; - p1 += 8; /* skip class, type, ttl */ - GETSHORT(rdlen1, p1); - end1 = p1 + rdlen1; + state1.ip += 8; /* skip class, type, ttl */ + GETSHORT(rdlen1, state1.ip); + if (!CHECK_LEN(header, state1.ip, plen, rdlen1)) + return rrsetidx; /* short packet */ + state1.end = state1.ip + rdlen1; - p2 += 8; /* skip class, type, ttl */ - GETSHORT(rdlen2, p2); - end2 = p2 + rdlen2; - - dp1 = dp2 = rr_desc; - - for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) + state2.ip += 8; /* skip class, type, ttl */ + GETSHORT(rdlen2, state2.ip); + if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) + return rrsetidx; /* short packet */ + state2.end = state2.ip + rdlen2; + + /* If the RR has no names in it then canonicalisation + is the identity function and we can compare + the RRs directly. If not we compare the + canonicalised RRs one byte at a time. */ + if (*rr_desc == (u16)-1) { - if (left1 != 0) - memmove(buff1, buff1 + len1 - left1, left1); + int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1; + int cmp = memcmp(state1.ip, state2.ip, rdmin); - if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) + if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin)) { - quit = 1; - len1 = end1 - p1; - memcpy(buff1 + left1, p1, len1); - } - len1 += left1; - - if (left2 != 0) - memmove(buff2, buff2 + len2 - left2, left2); - - if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - 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 = (len == 0) ? 0 : memcmp(buff1, buff2, len); - - if (rc > 0 || (rc == 0 && quit && len1 > len2)) - { unsigned char *tmp = rrset[i+1]; rrset[i+1] = rrset[i]; rrset[i] = tmp; - swap = quit = 1; + swap = 1; } - else if (rc < 0) - quit = 1; + else if (cmp == 0 && (rdlen1 == rdlen2)) + { + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; + } } + else + /* Comparing canonicalised RRs, byte-at-a-time. */ + while (1) + { + int ok1, ok2; + + ok1 = get_rdata(header, plen, &state1); + ok2 = get_rdata(header, plen, &state2); + + if (!ok1 && !ok2) + { + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; + break; + } + else if (ok1 && (!ok2 || *state1.op > *state2.op)) + { + unsigned char *tmp = rrset[i+1]; + rrset[i+1] = rrset[i]; + rrset[i] = tmp; + swap = 1; + break; + } + else if (ok2 && (!ok1 || *state2.op > *state1.op)) + break; + + /* arrive here when bytes are equal, go round the loop again + and compare the next ones. */ + } } } while (swap); + + return rrsetidx; } static unsigned char **rrset = NULL, **sigs = NULL; -/* Get pointers to RRset menbers and signature(s) for same. +/* Get pointers to RRset members and signature(s) for same. Check signatures, and return keyname associated in keyname. */ static int explore_rrset(struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname, int *sigcnt, int *rrcnt) @@ -680,7 +411,7 @@ static int explore_rrset(struct dns_header *header, si int gotkey = 0; if (!(p = skip_questions(header, plen))) - return STAT_BOGUS; + return 0; /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); @@ -692,14 +423,14 @@ static int explore_rrset(struct dns_header *header, si pstart = p; if (!(res = extract_name(header, plen, &p, name, 0, 10))) - return STAT_BOGUS; /* bad packet */ + return 0; /* bad packet */ GETSHORT(stype, p); GETSHORT(sclass, p); - p += 4; /* TTL */ - + pdata = p; + p += 4; /* TTL */ GETSHORT(rdlen, p); if (!CHECK_LEN(header, p, plen, rdlen)) @@ -762,7 +493,7 @@ static int explore_rrset(struct dns_header *header, si sigs[sigidx++] = pdata; } - p = pdata + 2; /* restore for ADD_RDLEN */ + p = pdata + 6; /* restore for ADD_RDLEN */ } } @@ -791,16 +522,22 @@ static int explore_rrset(struct dns_header *header, si Name is unchanged on exit. keyname is used as workspace and trashed. Call explore_rrset first to find and count RRs and sigs. + + ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate. */ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, - char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) + char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, + int algo_in, int keytag_in, unsigned long *ttl_out) { unsigned char *p; - int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag; + int rdlen, j, name_labels, algo, labels, key_tag; struct crec *crecp = NULL; u16 *rr_desc = rrfilter_desc(type); - u32 sig_expiration, sig_inception -; + u32 sig_expiration, sig_inception; + + unsigned long curtime = time(0); + int time_check = is_check_date(curtime); + if (wildcard_out) *wildcard_out = NULL; @@ -809,7 +546,7 @@ static int validate_rrset(time_t now, struct dns_heade /* Sort RRset records into canonical order. Note that at this point keyname and daemon->workspacename buffs are unused, and used as workspace by the sort. */ - sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); + rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); /* Now try all the sigs to try and find one which validates */ for (j = 0; j = 18 checked previously */ psav = p; @@ -836,16 +574,28 @@ static int validate_rrset(time_t now, struct dns_heade if (!extract_name(header, plen, &p, keyname, 1, 0)) return STAT_BOGUS; - if (!check_date_range(sig_inception, sig_expiration) || + if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) || labels > name_labels || !(hash = hash_find(algo_digest_name(algo))) || !hash_init(hash, &ctx, &digest)) continue; - + /* 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; - + + if (ttl_out) + { + /* 4035 5.3.3 rules on TTLs */ + if (orig_ttl < ttl) + ttl = orig_ttl; + + if (time_check && difftime(sig_expiration, curtime) < ttl) + ttl = difftime(sig_expiration, curtime); + + *ttl_out = ttl; + } + sig = p; sig_len = rdlen - (p - psav); @@ -855,14 +605,18 @@ static int validate_rrset(time_t now, struct dns_heade wire_len = to_wire(keyname); hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); from_wire(keyname); + +#define RRBUFLEN 128 /* Most RRs are smaller than this. */ for (i = 0; i < rrsetidx; ++i) { - int seg; - unsigned char *end, *cp; - u16 len, *dp; + int j; + struct rdata_state state; + u16 len; + unsigned char rrbuf[RRBUFLEN]; p = rrset[i]; + if (!extract_name(header, plen, &p, name, 1, 10)) return STAT_BOGUS; @@ -871,12 +625,11 @@ static int validate_rrset(time_t now, struct dns_heade /* if more labels than in RRsig name, hash *. 4035 5.3.2 */ if (labels < name_labels) { - int k; - for (k = name_labels - labels; k != 0; k--) + for (j = name_labels - labels; j != 0; j--) { while (*name_start != '.' && *name_start != 0) name_start++; - if (k != 1 && *name_start == '.') + if (j != 1 && *name_start == '.') name_start++; } @@ -891,30 +644,66 @@ static int validate_rrset(time_t now, struct dns_heade hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start); hash->update(ctx, 4, p); /* class and type */ hash->update(ctx, 4, (unsigned char *)&nsigttl); - - p += 8; /* skip class, type, ttl */ + + p += 8; /* skip type, class, ttl */ GETSHORT(rdlen, p); if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; - - end = p + rdlen; - - /* canonicalise rdata and calculate length of same, use name buffer as workspace. - Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ - cp = p; - dp = rr_desc; - for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); - len += end - cp; - len = htons(len); - hash->update(ctx, 2, (unsigned char *)&len); - - /* Now canonicalise again and digest. */ - cp = p; - dp = rr_desc; - while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) - hash->update(ctx, seg, (unsigned char *)name); - if (cp != end) - hash->update(ctx, end - cp, cp); + + /* Optimisation for RR types which need no cannonicalisation. + This includes DNSKEY DS NSEC and NSEC3, which are also long, so + it saves lots of calls to get_rdata, and avoids the pessimal + segmented insertion, even with a small rrbuf[]. + + If canonicalisation is not needed, a simple insertion into the hash works. + */ + if (*rr_desc == (u16)-1) + { + len = htons(rdlen); + hash->update(ctx, 2, (unsigned char *)&len); + hash->update(ctx, rdlen, p); + } + else + { + /* canonicalise rdata and calculate length of same, use + name buffer as workspace for get_rdata. */ + state.ip = p; + state.op = NULL; + state.desc = rr_desc; + state.buff = name; + state.end = p + rdlen; + + for (j = 0; get_rdata(header, plen, &state); j++) + if (j < RRBUFLEN) + rrbuf[j] = *state.op; + + len = htons((u16)j); + hash->update(ctx, 2, (unsigned char *)&len); + + /* If the RR is shorter than RRBUFLEN (most of them, in practice) + then we can just digest it now. If it exceeds RRBUFLEN we have to + go back to the start and do it in chunks. */ + if (j >= RRBUFLEN) + { + state.ip = p; + state.op = NULL; + state.desc = rr_desc; + + for (j = 0; get_rdata(header, plen, &state); j++) + { + rrbuf[j] = *state.op; + + if (j == RRBUFLEN - 1) + { + hash->update(ctx, RRBUFLEN, rrbuf); + j = -1; + } + } + } + + if (j != 0) + hash->update(ctx, j, rrbuf); + } } hash->digest(ctx, hash->digest_size, digest); @@ -958,11 +747,13 @@ int dnssec_validate_by_ds(time_t now, struct dns_heade { unsigned char *psave, *p = (unsigned char *)(header+1); struct crec *crecp, *recp1; - int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; + int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag; + unsigned long ttl, sig_ttl; struct blockdata *key; - struct all_addr a; + union all_addr a; if (ntohs(header->qdcount) != 1 || + RCODE(header) == SERVFAIL || RCODE(header) == REFUSED || !extract_name(header, plen, &p, name, 1, 4)) return STAT_BOGUS; @@ -1052,12 +843,12 @@ int dnssec_validate_by_ds(time_t now, struct dns_heade if (!(recp1->flags & F_NEG) && recp1->addr.ds.keylen == (int)hash->digest_size && - (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && + (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && sigcnt != 0 && rrcnt != 0 && validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, - NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) + NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE) { valid = 1; break; @@ -1084,6 +875,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_heade GETSHORT(qclass, p); GETLONG(ttl, p); GETSHORT(rdlen, p); + + /* TTL may be limited by sig. */ + if (sig_ttl < ttl) + ttl = sig_ttl; if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad packet */ @@ -1103,30 +898,27 @@ int dnssec_validate_by_ds(time_t now, struct dns_heade algo = *p++; keytag = dnskey_keytag(algo, flags, p, rdlen - 4); - /* Cache needs to known class for DNSSEC stuff */ - a.addr.dnssec.class = class; - if ((key = blockdata_alloc((char*)p, rdlen - 4))) { - if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) + a.key.keylen = rdlen - 4; + a.key.keydata = key; + a.key.algo = algo; + a.key.keytag = keytag; + a.key.flags = flags; + + if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)) { blockdata_free(key); return STAT_BOGUS; } else { - a.addr.log.keytag = keytag; - a.addr.log.algo = algo; - if (verify_func(algo)) + a.log.keytag = keytag; + a.log.algo = algo; + if (algo_digest_name(algo)) log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu"); else log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)"); - - recp1->addr.key.keylen = rdlen - 4; - recp1->addr.key.keydata = key; - recp1->addr.key.algo = algo; - recp1->addr.key.keytag = keytag; - recp1->addr.key.flags = flags; } } } @@ -1162,10 +954,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_heade 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); - int qtype, qclass, rc, i, neganswer, nons; + int qtype, qclass, rc, i, neganswer, nons, neg_ttl = 0; int aclass, atype, rdlen; unsigned long ttl; - struct all_addr a; + union all_addr a; if (ntohs(header->qdcount) != 1 || !(p = skip_name(p, header, plen, 4))) @@ -1177,12 +969,14 @@ int dnssec_validate_ds(time_t now, struct dns_header * if (qtype != T_DS || qclass != class) rc = STAT_BOGUS; else - rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); - /* Note dnssec_validate_reply() will have cached positive answers */ + rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); if (rc == STAT_INSECURE) - rc = STAT_BOGUS; - + { + my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name); + rc = STAT_BOGUS; + } + p = (unsigned char *)(header+1); extract_name(header, plen, &p, name, 1, 4); p += 4; /* qtype, qclass */ @@ -1221,8 +1015,7 @@ int dnssec_validate_ds(time_t now, struct dns_header * int algo, digest, keytag; unsigned char *psave = p; struct blockdata *key; - struct crec *crecp; - + if (rdlen < 4) return STAT_BOGUS; /* bad packet */ @@ -1230,31 +1023,28 @@ int dnssec_validate_ds(time_t now, struct dns_header * algo = *p++; digest = *p++; - /* Cache needs to known class for DNSSEC stuff */ - a.addr.dnssec.class = class; - if ((key = blockdata_alloc((char*)p, rdlen - 4))) { - if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) + a.ds.digest = digest; + a.ds.keydata = key; + a.ds.algo = algo; + a.ds.keytag = keytag; + a.ds.keylen = rdlen - 4; + + if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)) { blockdata_free(key); return STAT_BOGUS; } else { - a.addr.log.keytag = keytag; - a.addr.log.algo = algo; - a.addr.log.digest = digest; - if (hash_find(ds_digest_name(digest)) && verify_func(algo)) + a.log.keytag = keytag; + a.log.algo = algo; + a.log.digest = digest; + if (ds_digest_name(digest) && algo_digest_name(algo)) log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu"); else log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)"); - - crecp->addr.ds.digest = digest; - crecp->addr.ds.keydata = key; - crecp->addr.ds.algo = algo; - crecp->addr.ds.keytag = keytag; - crecp->addr.ds.keylen = rdlen - 4; } } @@ -1270,11 +1060,7 @@ int dnssec_validate_ds(time_t now, struct dns_header * else { int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; - unsigned long minttl = ULONG_MAX; - - if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) - return STAT_BOGUS; - + if (RCODE(header) == NXDOMAIN) flags |= F_NXDOMAIN; @@ -1283,55 +1069,15 @@ int dnssec_validate_ds(time_t now, struct dns_header * if (nons) flags &= ~F_DNSSECOK; - for (i = ntohs(header->nscount); i != 0; i--) - { - if (!(p = skip_name(p, header, plen, 0))) - return STAT_BOGUS; + cache_start_insert(); - GETSHORT(atype, p); - GETSHORT(aclass, p); - GETLONG(ttl, p); - GETSHORT(rdlen, p); - - if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_BOGUS; /* bad packet */ - - if (aclass != class || atype != T_SOA) - { - p += rdlen; - continue; - } - - if (ttl < minttl) - minttl = ttl; - - /* MNAME */ - if (!(p = skip_name(p, header, plen, 0))) - return STAT_BOGUS; - /* RNAME */ - if (!(p = skip_name(p, header, plen, 20))) - return STAT_BOGUS; - p += 16; /* SERIAL REFRESH RETRY EXPIRE */ - - GETLONG(ttl, p); /* minTTL */ - if (ttl < minttl) - minttl = ttl; - - break; - } + /* Use TTL from NSEC for negative cache entries */ + if (!cache_insert(name, NULL, class, now, neg_ttl, flags)) + return STAT_BOGUS; - if (i != 0) - { - cache_start_insert(); - - a.addr.dnssec.class = class; - if (!cache_insert(name, &a, now, ttl, flags)) - return STAT_BOGUS; - - cache_end_insert(); - - log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS"); - } + cache_end_insert(); + + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS"); } return STAT_OK; @@ -1402,8 +1148,8 @@ static int hostname_cmp(const char *a, const char *b) } } -static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, - char *workspace1, char *workspace2, char *name, int type, int *nons) +static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count, + char *workspace1_in, char *workspace2, char *name, int type, int *nons) { int i, rc, rdlen; unsigned char *p, *psave; @@ -1416,6 +1162,9 @@ static int prove_non_existence_nsec(struct dns_header /* Find NSEC record that proves name doesn't exist */ for (i = 0; i < nsec_count; i++) { + char *workspace1 = workspace1_in; + int sig_labels, name_labels; + p = nsecs[i]; if (!extract_name(header, plen, &p, workspace1, 1, 10)) return 0; @@ -1424,7 +1173,27 @@ static int prove_non_existence_nsec(struct dns_header psave = p; if (!extract_name(header, plen, &p, workspace2, 1, 10)) return 0; - + + /* If NSEC comes from wildcard expansion, use original wildcard + as name for computation. */ + sig_labels = *labels[i]; + name_labels = count_labels(workspace1); + + if (sig_labels < name_labels) + { + int k; + for (k = name_labels - sig_labels; k != 0; k--) + { + while (*workspace1 != '.' && *workspace1 != 0) + workspace1++; + if (k != 1 && *workspace1 == '.') + workspace1++; + } + + workspace1--; + *workspace1 = '*'; + } + rc = hostname_cmp(workspace1, name); if (rc == 0) @@ -1450,8 +1219,9 @@ static int prove_non_existence_nsec(struct dns_header return 0; /* If the SOA bit is set for a DS record, then we have the - DS from the wrong side of the delegation. */ - if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) + DS from the wrong side of the delegation. For the root DS, + this is expected. */ + if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) return 0; } @@ -1466,7 +1236,7 @@ static int prove_non_existence_nsec(struct dns_header if (offset < p[1] && (p[offset+2] & mask) != 0) return 0; - break; /* finshed checking */ + break; /* finished checking */ } rdlen -= p[1]; @@ -1556,7 +1326,7 @@ static int base32_decode(char *in, unsigned char *out) } static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type, - char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons) + char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons, int name_labels) { int i, hash_len, salt_len, base32_len, rdlen, flags; unsigned char *p, *psave; @@ -1611,8 +1381,9 @@ static int check_nsec3_coverage(struct dns_header *hea return 0; /* If the SOA bit is set for a DS record, then we have the - DS from the wrong side of the delegation. */ - if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) + DS from the wrong side of the delegation. For the root DS, + this is expected. */ + if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) return 0; } @@ -1624,7 +1395,7 @@ static int check_nsec3_coverage(struct dns_header *hea if (offset < p[1] && (p[offset+2] & mask) != 0) return 0; - break; /* finshed checking */ + break; /* finished checking */ } rdlen -= p[1]; @@ -1753,7 +1524,7 @@ static int prove_non_existence_nsec3(struct dns_header if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) return 0; - if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons)) + if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name))) return 1; /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" @@ -1798,7 +1569,7 @@ static int prove_non_existence_nsec3(struct dns_header if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) return 0; - if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) return 0; /* Finally, check that there's no seat of wildcard synthesis */ @@ -1813,40 +1584,51 @@ static int prove_non_existence_nsec3(struct dns_header if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) return 0; - if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) return 0; } return 1; } -static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons) +static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl) { - static unsigned char **nsecset = NULL; - static int nsecset_sz = 0; + static unsigned char **nsecset = NULL, **rrsig_labels = NULL; + static int nsecset_sz = 0, rrsig_labels_sz = 0; int type_found = 0; - unsigned char *p = skip_questions(header, plen); + unsigned char *auth_start, *p = skip_questions(header, plen); int type, class, rdlen, i, nsecs_found; + unsigned long ttl; /* Move to NS section */ if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) return 0; + + auth_start = p; - for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) + for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++) { unsigned char *pstart = p; - if (!(p = skip_name(p, header, plen, 10))) + if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) return 0; - + GETSHORT(type, p); GETSHORT(class, p); - p += 4; /* TTL */ + GETLONG(ttl, p); GETSHORT(rdlen, p); if (class == qclass && (type == T_NSEC || type == T_NSEC3)) { + if (nsec_ttl) + { + /* Limit TTL with sig TTL */ + if (daemon->rr_status[ntohs(header->ancount) + i] < ttl) + ttl = daemon->rr_status[ntohs(header->ancount) + i]; + *nsec_ttl = ttl; + } + /* No mixed NSECing 'round here, thankyouverymuch */ if (type_found != 0 && type_found != type) return 0; @@ -1856,7 +1638,69 @@ static int prove_non_existence(struct dns_header *head if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) return 0; - nsecset[nsecs_found++] = pstart; + if (type == T_NSEC) + { + /* If we're looking for NSECs, find the corresponding SIGs, to + extract the labels value, which we need in case the NSECs + are the result of wildcard expansion. + Note that the NSEC may not have been validated yet + so if there are multiple SIGs, make sure the label value + is the same in all, to avoid be duped by a rogue one. + If there are no SIGs, that's an error */ + unsigned char *p1 = auth_start; + int res, j, rdlen1, type1, class1; + + if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) + return 0; + + rrsig_labels[nsecs_found] = NULL; + + for (j = ntohs(header->nscount); j != 0; j--) + { + if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) + return 0; + + GETSHORT(type1, p1); + GETSHORT(class1, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen1, p1); + + if (!CHECK_LEN(header, p1, plen, rdlen1)) + return 0; + + if (res == 1 && class1 == qclass && type1 == T_RRSIG) + { + int type_covered; + unsigned char *psav = p1; + + if (rdlen1 < 18) + return 0; /* bad packet */ + + GETSHORT(type_covered, p1); + + if (type_covered == T_NSEC) + { + p1++; /* algo */ + + /* labels field must be the same in every SIG we find. */ + if (!rrsig_labels[nsecs_found]) + rrsig_labels[nsecs_found] = p1; + else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ + return 0; + } + p1 = psav; + } + + if (!ADD_RDLEN(header, p1, plen, rdlen1)) + return 0; + } + + /* Must have found at least one sig. */ + if (!rrsig_labels[nsecs_found]) + return 0; + } + + nsecset[nsecs_found++] = pstart; } if (!ADD_RDLEN(header, p, plen, rdlen)) @@ -1864,7 +1708,7 @@ static int prove_non_existence(struct dns_header *head } if (type_found == T_NSEC) - return prove_non_existence_nsec(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); + return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); else if (type_found == T_NSEC3) return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons); else @@ -1909,7 +1753,7 @@ static int zone_status(char *name, int class, char *ke if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS))) return STAT_NEED_DS; - /* F_DNSSECOK misused in DS cache records to non-existance of NS record. + /* F_DNSSECOK misused in DS cache records to non-existence of NS record. F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here, but that's because there's no NS record either, ie this isn't the start of a zone. We only prove that the DNS tree below a node is unsigned when @@ -1932,8 +1776,8 @@ static int zone_status(char *name, int class, char *ke do { if (crecp->uid == (unsigned int)class && - hash_find(ds_digest_name(crecp->addr.ds.digest)) && - verify_func(crecp->addr.ds.algo)) + ds_digest_name(crecp->addr.ds.digest) && + algo_digest_name(crecp->addr.ds.algo)) break; } while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS))); @@ -1962,18 +1806,41 @@ static int zone_status(char *name, int class, char *ke STAT_INSECURE at least one RRset not validated, because in unsigned zone. STAT_BOGUS signature is wrong, bad packet, no validation where there should be. STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class) - STAT_NEED_DS need DS to complete validation (name is returned in keyname) + STAT_NEED_DS need DS to complete validation (name is returned in keyname) + + daemon->rr_status points to a char array which corressponds to the RRs in the + answer and auth sections. This is set to 1 for each RR which is validated, and 0 for any which aren't. + + When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section. + Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode + is the nons argument is non-NULL. */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, - int *class, int check_unsigned, int *neganswer, int *nons) + int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl) { static unsigned char **targets = NULL; static int target_sz = 0; unsigned char *ans_start, *p1, *p2; - int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx; - int i, j, rc; + int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; + int i, j, rc = STAT_INSECURE; + int secure = STAT_SECURE; + /* extend rr_status if necessary */ + if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount)) + { + unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(header->ancount) + ntohs(header->nscount) + 64)); + + if (!new) + return STAT_BOGUS; + + free(daemon->rr_status); + daemon->rr_status = new; + daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 64; + } + + memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz); + if (neganswer) *neganswer = 0; @@ -2030,6 +1897,9 @@ int dnssec_validate_reply(time_t now, struct dns_heade for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) { + if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1)) + return STAT_BOGUS; + if (!extract_name(header, plen, &p1, name, 1, 10)) return STAT_BOGUS; /* bad packet */ @@ -2039,140 +1909,168 @@ int dnssec_validate_reply(time_t now, struct dns_heade GETSHORT(rdlen1, p1); /* Don't try and validate RRSIGs! */ - if (type1 != T_RRSIG) + if (type1 == T_RRSIG) + continue; + + /* Check if we've done this RRset already */ + for (p2 = ans_start, j = 0; j < i; j++) { - /* Check if we've done this RRset already */ - for (p2 = ans_start, j = 0; j < i; j++) - { - if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) - return STAT_BOGUS; /* bad packet */ - - GETSHORT(type2, p2); - GETSHORT(class2, p2); - p2 += 4; /* TTL */ - GETSHORT(rdlen2, p2); - - if (type2 == type1 && class2 == class1 && rc == 1) - break; /* Done it before: name, type, class all match. */ - - if (!ADD_RDLEN(header, p2, plen, rdlen2)) - return STAT_BOGUS; - } + if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + GETSHORT(type2, p2); + GETSHORT(class2, p2); + p2 += 4; /* TTL */ + GETSHORT(rdlen2, p2); + + if (type2 == type1 && class2 == class1 && rc == 1) + break; /* Done it before: name, type, class all match. */ + + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_BOGUS; + } + + /* Done already: copy the validation status */ + if (j != i) + daemon->rr_status[i] = daemon->rr_status[j]; + else + { /* Not done, validate now */ - if (j == i) + int sigcnt, rrcnt; + char *wildname; + + if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) + return STAT_BOGUS; + + /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */ + if (sigcnt == 0) { - int sigcnt, rrcnt; - char *wildname; - - if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) - return STAT_BOGUS; - - /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */ - if (sigcnt == 0) + /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */ + if (type1 == T_NSEC || type1 == T_NSEC3) + rc = STAT_INSECURE; + else if (nons && i >= ntohs(header->ancount)) + /* If we're validating a DS reply, rather than looking for the value of AD bit, + we only care that NSEC and NSEC3 RRs in the auth section are signed. + Return SECURE even if others (SOA....) are not. */ + rc = STAT_SECURE; + else { - if (check_unsigned) + /* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */ + if (check_unsigned && i < ntohs(header->ancount)) { rc = zone_status(name, class1, keyname, now); if (rc == STAT_SECURE) rc = STAT_BOGUS; - if (class) - *class = class1; /* Class for NEED_DS or NEED_KEY */ + if (class) + *class = class1; /* Class for NEED_DS or NEED_KEY */ } else rc = STAT_INSECURE; - return rc; + if (rc != STAT_INSECURE) + return rc; } - + } + else + { /* explore_rrset() gives us key name from sigs in keyname. Can't overwrite name here. */ strcpy(daemon->workspacename, keyname); rc = zone_status(daemon->workspacename, class1, keyname, now); - - if (rc != STAT_SECURE) + + if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) { - /* Zone is insecure, don't need to validate RRset */ if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ return rc; - } + } - rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0); - - if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + /* Zone is insecure, don't need to validate RRset */ + if (rc == STAT_SECURE) { - if (class) - *class = class1; /* Class for DS or DNSKEY */ - return rc; - } - else - { + unsigned long sig_ttl; + rc = validate_rrset(now, header, plen, class1, type1, sigcnt, + rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl); + + if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + { + if (class) + *class = class1; /* Class for DS or DNSKEY */ + return rc; + } + /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */ - + + /* Note that RR is validated */ + daemon->rr_status[i] = sig_ttl; + /* Note if we've validated either the answer to the question or the target of a CNAME. Any not noted will need NSEC or to be in unsigned space. */ - for (j = 0; j sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); -#ifdef HAVE_IPV6 - else - log_query(F_NOEXTRA | 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); @@ -2227,7 +2117,7 @@ size_t dnssec_generate_query(struct dns_header *header p = (unsigned char *)(header+1); - p = do_rfc1035_name(p, name); + p = do_rfc1035_name(p, name, NULL); *p++ = 0; PUTSHORT(type, p); PUTSHORT(class, p); @@ -2238,37 +2128,6 @@ size_t dnssec_generate_query(struct dns_header *header PUTSHORT(edns_pktsz, p); return ret; -} - -unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) -{ - int q; - unsigned int len; - unsigned char *p = (unsigned char *)(header+1); - const struct nettle_hash *hash; - void *ctx; - unsigned char *digest; - - if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) - return NULL; - - for (q = ntohs(header->qdcount); q != 0; q--) - { - if (!extract_name(header, plen, &p, name, 1, 4)) - break; /* bad packet */ - - len = to_wire(name); - hash->update(ctx, len, (unsigned char *)name); - /* CRC the class and type as well */ - hash->update(ctx, 4, p); - - p += 4; - if (!CHECK_LEN(header, p, plen, 0)) - break; /* bad packet */ - } - - hash->digest(ctx, hash->digest_size, digest); - return digest; } #endif /* HAVE_DNSSEC */