diff options
Diffstat (limited to 'contrib/bind9/lib/dns/validator.c')
-rw-r--r-- | contrib/bind9/lib/dns/validator.c | 770 |
1 files changed, 737 insertions, 33 deletions
diff --git a/contrib/bind9/lib/dns/validator.c b/contrib/bind9/lib/dns/validator.c index 685434b..c62b714 100644 --- a/contrib/bind9/lib/dns/validator.c +++ b/contrib/bind9/lib/dns/validator.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2008 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2004-2009 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 2000-2003 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any @@ -15,18 +15,17 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: validator.c,v 1.119.18.41.2.1 2009/03/17 02:23:49 marka Exp $ */ - -/*! \file */ +/* $Id: validator.c,v 1.164.12.9 2009/05/07 23:47:12 tbox Exp $ */ #include <config.h> +#include <isc/base32.h> #include <isc/mem.h> #include <isc/print.h> +#include <isc/sha2.h> #include <isc/string.h> #include <isc/task.h> #include <isc/util.h> -#include <isc/sha2.h> #include <dns/db.h> #include <dns/ds.h> @@ -37,6 +36,7 @@ #include <dns/message.h> #include <dns/ncache.h> #include <dns/nsec.h> +#include <dns/nsec3.h> #include <dns/rdata.h> #include <dns/rdatastruct.h> #include <dns/rdataset.h> @@ -89,7 +89,7 @@ #define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC) #define VALATTR_SHUTDOWN 0x0001 /*%< Shutting down. */ -#define VALATTR_CANCELED 0x0002 /*%< Cancelled. */ +#define VALATTR_CANCELED 0x0002 /*%< Canceled. */ #define VALATTR_TRIEDVERIFY 0x0004 /*%< We have found a key and * have attempted a verify. */ #define VALATTR_INSECURITY 0x0010 /*%< Attempting proveunsecure. */ @@ -98,16 +98,23 @@ /*! * NSEC proofs to be looked for. */ -#define VALATTR_NEEDNOQNAME 0x0100 -#define VALATTR_NEEDNOWILDCARD 0x0200 -#define VALATTR_NEEDNODATA 0x0400 +#define VALATTR_NEEDNOQNAME 0x00000100 +#define VALATTR_NEEDNOWILDCARD 0x00000200 +#define VALATTR_NEEDNODATA 0x00000400 /*! * NSEC proofs that have been found. */ -#define VALATTR_FOUNDNOQNAME 0x1000 -#define VALATTR_FOUNDNOWILDCARD 0x2000 -#define VALATTR_FOUNDNODATA 0x4000 +#define VALATTR_FOUNDNOQNAME 0x00001000 +#define VALATTR_FOUNDNOWILDCARD 0x00002000 +#define VALATTR_FOUNDNODATA 0x00004000 +#define VALATTR_FOUNDCLOSEST 0x00008000 + +/* + * + */ +#define VALATTR_FOUNDOPTOUT 0x00010000 +#define VALATTR_FOUNDUNKNOWN 0x00020000 #define NEEDNODATA(val) ((val->attributes & VALATTR_NEEDNODATA) != 0) #define NEEDNOQNAME(val) ((val->attributes & VALATTR_NEEDNOQNAME) != 0) @@ -250,10 +257,20 @@ static isc_boolean_t isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, isc_result_t dbresult) { - dns_rdataset_t set; + dns_fixedname_t fixed; + dns_label_t hashlabel; + dns_name_t nsec3name; + dns_rdata_nsec3_t nsec3; dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t set; + int order; + int scope; isc_boolean_t found; + isc_buffer_t buffer; isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + unsigned char owner[NSEC3_MAX_HASH_LENGTH]; + unsigned int length; REQUIRE(dbresult == DNS_R_NXRRSET || dbresult == DNS_R_NCACHENXRRSET); @@ -263,6 +280,8 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, else { result = dns_ncache_getrdataset(rdataset, name, dns_rdatatype_nsec, &set); + if (result == ISC_R_NOTFOUND) + goto trynsec3; if (result != ISC_R_SUCCESS) return (ISC_FALSE); } @@ -274,9 +293,78 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, if (result == ISC_R_SUCCESS) { dns_rdataset_current(&set, &rdata); found = dns_nsec_typepresent(&rdata, dns_rdatatype_ns); + dns_rdata_reset(&rdata); } dns_rdataset_disassociate(&set); return (found); + + trynsec3: + /* + * Iterate over the ncache entry. + */ + found = ISC_FALSE; + dns_name_init(&nsec3name, NULL); + dns_fixedname_init(&fixed); + dns_name_downcase(name, dns_fixedname_name(&fixed), NULL); + name = dns_fixedname_name(&fixed); + result = dns_rdataset_first(rdataset); + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_ncache_current(rdataset, &nsec3name, &set); + if (set.type != dns_rdatatype_nsec3) { + dns_rdataset_disassociate(&set); + continue; + } + dns_name_getlabel(&nsec3name, 0, &hashlabel); + isc_region_consume(&hashlabel, 1); + isc_buffer_init(&buffer, owner, sizeof(owner)); + result = isc_base32hex_decoderegion(&hashlabel, &buffer); + if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&set); + continue; + } + for (result = dns_rdataset_first(&set); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&set)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(&set, &rdata); + (void)dns_rdata_tostruct(&rdata, &nsec3, NULL); + if (nsec3.hash != 1) + continue; + length = isc_iterated_hash(hash, nsec3.hash, + nsec3.iterations, nsec3.salt, + nsec3.salt_length, + name->ndata, name->length); + if (length != isc_buffer_usedlength(&buffer)) + continue; + order = memcmp(hash, owner, length); + if (order == 0) { + found = dns_nsec3_typepresent(&rdata, + dns_rdatatype_ns); + dns_rdataset_disassociate(&set); + return (found); + } + if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) + continue; + /* + * Does this optout span cover the name? + */ + scope = memcmp(owner, nsec3.next, nsec3.next_length); + if ((scope < 0 && order > 0 && + memcmp(hash, nsec3.next, length) < 0) || + (scope >= 0 && (order > 0 || + memcmp(hash, nsec3.next, length) < 0))) + { + dns_rdataset_disassociate(&set); + return (ISC_TRUE); + } + } + dns_rdataset_disassociate(&set); + } + return (found); } /*% @@ -767,10 +855,317 @@ nsecnoexistnodata(dns_validator_t *val, dns_name_t* name, dns_name_t *nsecname, return (ISC_R_SUCCESS); } +static isc_result_t +nsec3noexistnodata(dns_validator_t *val, dns_name_t* name, + dns_name_t *nsec3name, dns_rdataset_t *nsec3set, + dns_name_t *zonename, isc_boolean_t *exists, + isc_boolean_t *data, isc_boolean_t *optout, + isc_boolean_t *unknown, isc_boolean_t *setclosest, + isc_boolean_t *setnearest, dns_name_t *closest, + dns_name_t *nearest) +{ + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fzone; + dns_fixedname_t qfixed; + dns_label_t hashlabel; + dns_name_t *qname; + dns_name_t *zone; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata = DNS_RDATA_INIT; + int order; + int scope; + isc_boolean_t atparent; + isc_boolean_t first; + isc_boolean_t ns; + isc_boolean_t soa; + isc_buffer_t buffer; + isc_result_t answer = ISC_R_IGNORE; + isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + unsigned char owner[NSEC3_MAX_HASH_LENGTH]; + unsigned int length; + unsigned int qlabels; + unsigned int zlabels; + + REQUIRE((exists == NULL && data == NULL) || + (exists != NULL && data != NULL)); + REQUIRE(nsec3set != NULL && nsec3set->type == dns_rdatatype_nsec3); + REQUIRE((setclosest == NULL && closest == NULL) || + (setclosest != NULL && closest != NULL)); + REQUIRE((setnearest == NULL && nearest == NULL) || + (setnearest != NULL && nearest != NULL)); + + result = dns_rdataset_first(nsec3set); + if (result != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), + "failure processing NSEC3 set"); + return (result); + } + + dns_rdataset_current(nsec3set, &rdata); + + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + if (result != ISC_R_SUCCESS) + return (result); + + validator_log(val, ISC_LOG_DEBUG(3), "looking for relevant NSEC3"); + + dns_fixedname_init(&fzone); + zone = dns_fixedname_name(&fzone); + zlabels = dns_name_countlabels(nsec3name); + + /* + * NSEC3 records must have two or more labels to be valid. + */ + if (zlabels < 2) + return (ISC_R_IGNORE); + + /* + * Strip off the NSEC3 hash to get the zone. + */ + zlabels--; + dns_name_split(nsec3name, zlabels, NULL, zone); + + /* + * If not below the zone name we can ignore this record. + */ + if (!dns_name_issubdomain(name, zone)) + return (ISC_R_IGNORE); + + /* + * Is this zone the same or deeper than the current zone? + */ + if (dns_name_countlabels(zonename) == 0 || + dns_name_issubdomain(zone, zonename)) + dns_name_copy(zone, zonename, NULL); + + if (!dns_name_equal(zone, zonename)) + return (ISC_R_IGNORE); + + /* + * Are we only looking for the most enclosing zone? + */ + if (exists == NULL || data == NULL) + return (ISC_R_SUCCESS); + + /* + * Only set unknown once we are sure that this NSEC3 is from + * the deepest covering zone. + */ + if (!dns_nsec3_supportedhash(nsec3.hash)) { + if (unknown != NULL) + *unknown = ISC_TRUE; + return (ISC_R_IGNORE); + } + + /* + * Recover the hash from the first label. + */ + dns_name_getlabel(nsec3name, 0, &hashlabel); + isc_region_consume(&hashlabel, 1); + isc_buffer_init(&buffer, owner, sizeof(owner)); + result = isc_base32hex_decoderegion(&hashlabel, &buffer); + if (result != ISC_R_SUCCESS) + return (result); + + /* + * The hash lengths should match. If not ignore the record. + */ + if (isc_buffer_usedlength(&buffer) != nsec3.next_length) + return (ISC_R_IGNORE); + + /* + * Work out what this NSEC3 covers. + * Inside (<0) or outside (>=0). + */ + scope = memcmp(owner, nsec3.next, nsec3.next_length); + + /* + * Prepare to compute all the hashes. + */ + dns_fixedname_init(&qfixed); + qname = dns_fixedname_name(&qfixed); + dns_name_downcase(name, qname, NULL); + qlabels = dns_name_countlabels(qname); + first = ISC_TRUE; + + while (qlabels >= zlabels) { + length = isc_iterated_hash(hash, nsec3.hash, nsec3.iterations, + nsec3.salt, nsec3.salt_length, + qname->ndata, qname->length); + /* + * The computed hash length should match. + */ + if (length != nsec3.next_length) { + validator_log(val, ISC_LOG_DEBUG(3), + "ignoring NSEC bad length %u vs %u", + length, nsec3.next_length); + return (ISC_R_IGNORE); + } + + order = memcmp(hash, owner, length); + if (first && order == 0) { + /* + * The hashes are the same. + */ + atparent = dns_rdatatype_atparent(val->event->type); + ns = dns_nsec3_typepresent(&rdata, dns_rdatatype_ns); + soa = dns_nsec3_typepresent(&rdata, dns_rdatatype_soa); + if (ns && !soa) { + if (!atparent) { + /* + * This NSEC record is from somewhere + * higher in the DNS, and at the + * parent of a delegation. It can not + * be legitimately used here. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "ignoring parent NSEC3"); + return (ISC_R_IGNORE); + } + } else if (atparent && ns && soa) { + /* + * This NSEC record is from the child. + * It can not be legitimately used here. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "ignoring child NSEC3"); + return (ISC_R_IGNORE); + } + if (val->event->type == dns_rdatatype_cname || + val->event->type == dns_rdatatype_nxt || + val->event->type == dns_rdatatype_nsec || + val->event->type == dns_rdatatype_key || + !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname)) { + *exists = ISC_TRUE; + *data = dns_nsec3_typepresent(&rdata, + val->event->type); + validator_log(val, ISC_LOG_DEBUG(3), + "NSEC3 proves name exists (owner) " + "data=%d", *data); + return (ISC_R_SUCCESS); + } + validator_log(val, ISC_LOG_DEBUG(3), + "NSEC3 proves CNAME exists"); + return (ISC_R_IGNORE); + } + + if (order == 0 && + dns_nsec3_typepresent(&rdata, dns_rdatatype_ns) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_soa)) + { + /* + * This NSEC3 record is from somewhere higher in + * the DNS, and at the parent of a delegation. + * It can not be legitimately used here. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "ignoring parent NSEC3"); + return (ISC_R_IGNORE); + } + + /* + * Potential closest encloser. + */ + if (order == 0) { + if (closest != NULL && + (dns_name_countlabels(closest) == 0 || + dns_name_issubdomain(qname, closest)) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ds) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_dname) && + (dns_nsec3_typepresent(&rdata, dns_rdatatype_soa) || + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ns))) + { + + dns_name_format(qname, namebuf, + sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), + "NSEC3 indicates potential " + "closest encloser: '%s'", + namebuf); + dns_name_copy(qname, closest, NULL); + *setclosest = ISC_TRUE; + } + dns_name_format(qname, namebuf, sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), + "NSEC3 at super-domain %s", namebuf); + return (answer); + } + + /* + * Find if the name does not exist. + * + * We continue as we need to find the name closest to the + * closest encloser that doesn't exist. + * + * We also need to continue to ensure that we are not + * proving the non-existence of a record in a sub-zone. + * If that would be the case we will return ISC_R_IGNORE + * above. + */ + if ((scope < 0 && order > 0 && + memcmp(hash, nsec3.next, length) < 0) || + (scope >= 0 && (order > 0 || + memcmp(hash, nsec3.next, length) < 0))) + { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(qname, namebuf, sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), "NSEC3 proves " + "name does not exist: '%s'", namebuf); + if (nearest != NULL && + (dns_name_countlabels(nearest) == 0 || + dns_name_issubdomain(nearest, qname))) { + dns_name_copy(qname, nearest, NULL); + *setnearest = ISC_TRUE; + } +#if 0 + /* + * The closest encloser may be the zone name. + */ + if (closest != NULL && + dns_name_countlabels(closest) == 0 && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ds) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_dname) && + (dns_nsec3_typepresent(&rdata, dns_rdatatype_soa) || + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ns))) + { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(zone, namebuf, + sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), + "NSEC3 potential closest " + "encloser from zone name: '%s'", + namebuf); + dns_name_copy(zone, closest, NULL); + *setclosest = ISC_TRUE; + } +#endif + *exists = ISC_FALSE; + *data = ISC_FALSE; + if (optout != NULL) { + if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0) + validator_log(val, ISC_LOG_DEBUG(3), + "NSEC3 indicates optout"); + *optout = + ISC_TF(nsec3.flags & DNS_NSEC3FLAG_OPTOUT); + } + answer = ISC_R_SUCCESS; + } + + qlabels--; + if (qlabels > 0) + dns_name_split(qname, qlabels, NULL, qname); + first = ISC_FALSE; + } + return (answer); +} + /*% * Callback for when NSEC records have been validated. * - * Looks for NOQNAME and NODATA proofs. + * Looks for NOQNAME, NODATA and OPTOUT proofs. * * Resumes nsecvalidate. */ @@ -779,6 +1174,7 @@ authvalidated(isc_task_t *task, isc_event_t *event) { dns_validatorevent_t *devent; dns_validator_t *val; dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; isc_boolean_t want_destroy; isc_result_t result; isc_boolean_t exists, data; @@ -788,6 +1184,7 @@ authvalidated(isc_task_t *task, isc_event_t *event) { devent = (dns_validatorevent_t *)event; rdataset = devent->rdataset; + sigrdataset = devent->sigrdataset; val = devent->ev_arg; result = devent->result; dns_validator_destroy(&val->subvalidator); @@ -834,11 +1231,18 @@ authvalidated(isc_task_t *task, isc_event_t *event) { } if (!exists) { val->attributes |= VALATTR_FOUNDNOQNAME; + val->attributes |= VALATTR_FOUNDCLOSEST; + /* + * The NSEC noqname proof also contains + * the closest encloser. + + */ if (NEEDNOQNAME(val)) proofs[DNS_VALIDATOR_NOQNAMEPROOF] = devent->name; } } + result = nsecvalidate(val, ISC_TRUE); if (result != DNS_R_WAIT) validator_done(val, result); @@ -992,13 +1396,25 @@ view_find(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type) { * the validation process will stall if looping was to occur. */ static inline isc_boolean_t -check_deadlock(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type) { +check_deadlock(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) +{ dns_validator_t *parent; for (parent = val; parent != NULL; parent = parent->parent) { if (parent->event != NULL && parent->event->type == type && - dns_name_equal(parent->event->name, name)) + dns_name_equal(parent->event->name, name) && + /* + * As NSEC3 records are meta data you sometimes + * need to prove a NSEC3 record which says that + * itself doesn't exist. + */ + (parent->event->type != dns_rdatatype_nsec3 || + rdataset == NULL || sigrdataset == NULL || + parent->event->message == NULL || + parent->event->rdataset != NULL || + parent->event->sigrdataset != NULL)) { validator_log(val, ISC_LOG_DEBUG(3), "continuing validation would lead to " @@ -1021,7 +1437,7 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, if (dns_rdataset_isassociated(&val->fsigrdataset)) dns_rdataset_disassociate(&val->fsigrdataset); - if (check_deadlock(val, name, type)) + if (check_deadlock(val, name, type, NULL, NULL)) return (DNS_R_NOVALIDSIG); validator_logcreate(val, name, type, caller, "fetch"); @@ -1044,7 +1460,7 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, { isc_result_t result; - if (check_deadlock(val, name, type)) + if (check_deadlock(val, name, type, rdataset, sigrdataset)) return (DNS_R_NOVALIDSIG); validator_logcreate(val, name, type, caller, "validator"); @@ -1128,7 +1544,7 @@ get_dst_key(dns_validator_t *val, dns_rdata_rrsig_t *siginfo, } /*% - * Get the key that genertated this signature. + * Get the key that generated this signature. */ static isc_result_t get_key(dns_validator_t *val, dns_rdata_rrsig_t *siginfo) { @@ -1141,7 +1557,7 @@ get_key(dns_validator_t *val, dns_rdata_rrsig_t *siginfo) { * Is the signer name appropriate for this signature? * * The signer name must be at the same level as the owner name - * or closer to the the DNS root. + * or closer to the DNS root. */ namereln = dns_name_fullcompare(val->event->name, &siginfo->signer, &order, &nlabels); @@ -1163,6 +1579,23 @@ get_key(dns_validator_t *val, dns_rdata_rrsig_t *siginfo) { */ if (dns_rdatatype_atparent(val->event->rdataset->type)) return (DNS_R_CONTINUE); + } else { + /* + * SOA and NS RRsets can only be signed by a key with + * the same name. + */ + if (val->event->rdataset->type == dns_rdatatype_soa || + val->event->rdataset->type == dns_rdatatype_ns) { + const char *typename; + + if (val->event->rdataset->type == dns_rdatatype_soa) + typename = "SOA"; + else + typename = "NS"; + validator_log(val, ISC_LOG_DEBUG(3), + "%s signer mismatch", typename); + return (DNS_R_CONTINUE); + } } /* @@ -1620,6 +2053,7 @@ dlv_validatezonekey(dns_validator_t *val) { break; } if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&trdataset); validator_log(val, ISC_LOG_DEBUG(3), "no DNSKEY matching DLV"); continue; @@ -1734,6 +2168,10 @@ validatezonekey(dns_validator_t *val) { &sigrdata); result = dns_rdata_tostruct(&sigrdata, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (!dns_name_equal(val->event->name, &sig.signer)) + continue; + result = dns_keytable_findkeynode(val->keytable, val->event->name, sig.algorithm, @@ -1957,6 +2395,7 @@ validatezonekey(dns_validator_t *val) { break; } if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&trdataset); validator_log(val, ISC_LOG_DEBUG(3), "no DNSKEY matching DS"); continue; @@ -1974,7 +2413,11 @@ validatezonekey(dns_validator_t *val) { if (ds.key_tag != sig.keyid || ds.algorithm != sig.algorithm) continue; - + if (!dns_name_equal(val->event->name, &sig.signer)) { + validator_log(val, ISC_LOG_DEBUG(3), + "DNSKEY signer mismatch"); + continue; + } dstkey = NULL; result = dns_dnssec_keyfromrdata(val->event->name, &keyrdata, @@ -2044,7 +2487,8 @@ start_positive_validation(dns_validator_t *val) { * \li ISC_R_SUCCESS */ static isc_result_t -checkwildcard(dns_validator_t *val) { +checkwildcard(dns_validator_t *val, dns_rdatatype_t type, dns_name_t *zonename) +{ dns_name_t *name, *wild; dns_message_t *message = val->event->message; isc_result_t result; @@ -2052,6 +2496,13 @@ checkwildcard(dns_validator_t *val) { char namebuf[DNS_NAME_FORMATSIZE]; wild = dns_fixedname_name(&val->wild); + + if (dns_name_countlabels(wild) == 0) { + validator_log(val, ISC_LOG_DEBUG(3), + "in checkwildcard: no wildcard to check"); + return (ISC_R_SUCCESS); + } + dns_name_format(wild, namebuf, sizeof(namebuf)); validator_log(val, ISC_LOG_DEBUG(3), "in checkwildcard: %s", namebuf); @@ -2068,9 +2519,8 @@ checkwildcard(dns_validator_t *val) { rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { - if (rdataset->type != dns_rdatatype_nsec) + if (rdataset->type != type) continue; - val->nsecset = rdataset; for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL; @@ -2086,7 +2536,8 @@ checkwildcard(dns_validator_t *val) { if (rdataset->trust != dns_trust_secure) continue; - if (((val->attributes & VALATTR_NEEDNODATA) != 0 || + if (rdataset->type == dns_rdatatype_nsec && + ((val->attributes & VALATTR_NEEDNODATA) != 0 || (val->attributes & VALATTR_NEEDNOWILDCARD) != 0) && (val->attributes & VALATTR_FOUNDNODATA) == 0 && (val->attributes & VALATTR_FOUNDNOWILDCARD) == 0 && @@ -2108,6 +2559,31 @@ checkwildcard(dns_validator_t *val) { name; return (ISC_R_SUCCESS); } + + if (rdataset->type == dns_rdatatype_nsec3 && + ((val->attributes & VALATTR_NEEDNODATA) != 0 || + (val->attributes & VALATTR_NEEDNOWILDCARD) != 0) && + (val->attributes & VALATTR_FOUNDNODATA) == 0 && + (val->attributes & VALATTR_FOUNDNOWILDCARD) == 0 && + nsec3noexistnodata(val, wild, name, rdataset, + zonename, &exists, &data, + NULL, NULL, NULL, NULL, NULL, + NULL) == ISC_R_SUCCESS) + { + dns_name_t **proofs = val->event->proofs; + if (exists && !data) + val->attributes |= VALATTR_FOUNDNODATA; + if (exists && !data && NEEDNODATA(val)) + proofs[DNS_VALIDATOR_NODATAPROOF] = + name; + if (!exists) + val->attributes |= + VALATTR_FOUNDNOWILDCARD; + if (!exists && NEEDNOQNAME(val)) + proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = + name; + return (ISC_R_SUCCESS); + } } } if (result == ISC_R_NOMORE) @@ -2115,6 +2591,170 @@ checkwildcard(dns_validator_t *val) { return (result); } + +static isc_result_t +findnsec3proofs(dns_validator_t *val) { + dns_name_t *name; + dns_message_t *message = val->event->message; + isc_result_t result; + isc_boolean_t exists, data, optout, unknown; + isc_boolean_t setclosest, setnearest; + dns_fixedname_t fclosest, fnearest, fzonename; + dns_name_t *closest, *nearest, *zonename; + dns_name_t **proofs = val->event->proofs; + + dns_fixedname_init(&fclosest); + dns_fixedname_init(&fnearest); + dns_fixedname_init(&fzonename); + closest = dns_fixedname_name(&fclosest); + nearest = dns_fixedname_name(&fnearest); + zonename = dns_fixedname_name(&fzonename); + + for (result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) + { + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type != dns_rdatatype_nsec3) + continue; + + for (sigrdataset = ISC_LIST_HEAD(name->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == dns_rdatatype_nsec3) + break; + } + if (sigrdataset == NULL) + continue; + + if (rdataset->trust != dns_trust_secure) + continue; + + result = nsec3noexistnodata(val, val->event->name, + name, rdataset, + zonename, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL); + if (result != ISC_R_IGNORE && result != ISC_R_SUCCESS) + return (result); + } + } + if (result != ISC_R_NOMORE) + result = ISC_R_SUCCESS; + + if (dns_name_countlabels(zonename) == 0) + return (ISC_R_SUCCESS); + + for (result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) + { + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type != dns_rdatatype_nsec3) + continue; + + for (sigrdataset = ISC_LIST_HEAD(name->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == dns_rdatatype_nsec3) + break; + } + if (sigrdataset == NULL) + continue; + + if (rdataset->trust != dns_trust_secure) + continue; + + /* + * We process all NSEC3 records to find the closest + * encloser and nearest name to the closest encloser. + */ + setclosest = setnearest = ISC_FALSE; + optout = ISC_FALSE; + unknown = ISC_FALSE; + result = nsec3noexistnodata(val, val->event->name, + name, rdataset, + zonename, &exists, + &data, &optout, &unknown, + &setclosest, &setnearest, + closest, nearest); + if (setclosest) + proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name; + if (unknown) + val->attributes |= VALATTR_FOUNDUNKNOWN; + if (result != ISC_R_SUCCESS) + continue; + if (exists && !data && NEEDNODATA(val)) { + val->attributes |= VALATTR_FOUNDNODATA; + proofs[DNS_VALIDATOR_NODATAPROOF] = name; + } + if (!exists && setnearest) { + val->attributes |= VALATTR_FOUNDNOQNAME; + proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name; + if (optout) + val->attributes |= VALATTR_FOUNDOPTOUT; + } + } + } + if (result != ISC_R_NOMORE) + result = ISC_R_SUCCESS; + + /* + * To know we have a valid noqname and optout proofs we need to also + * have a valid closest encloser. Otherwise we could still be looking + * at proofs from the parent zone. + */ + if (dns_name_countlabels(closest) > 0 && + dns_name_countlabels(nearest) == + dns_name_countlabels(closest) + 1 && + dns_name_issubdomain(nearest, closest)) + { + val->attributes |= VALATTR_FOUNDCLOSEST; + result = dns_name_concatenate(dns_wildcardname, closest, + dns_fixedname_name(&val->wild), + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } else { + val->attributes &= ~VALATTR_FOUNDNOQNAME; + val->attributes &= ~VALATTR_FOUNDOPTOUT; + proofs[DNS_VALIDATOR_NOQNAMEPROOF] = NULL; + } + + /* + * Do we need to check for the wildcard? + */ + if ((val->attributes & VALATTR_FOUNDNOQNAME) != 0 && + (val->attributes & VALATTR_FOUNDCLOSEST) != 0 && + (((val->attributes & VALATTR_NEEDNODATA) != 0 && + (val->attributes & VALATTR_FOUNDNODATA) == 0) || + (val->attributes & VALATTR_NEEDNOWILDCARD) != 0)) { + result = checkwildcard(val, dns_rdatatype_nsec3, zonename); + if (result != ISC_R_SUCCESS) + return (result); + } + return (result); +} + /*% * Prove a negative answer is good or that there is a NOQNAME when the * answer is from a wildcard. @@ -2220,7 +2860,10 @@ nsecvalidate(dns_validator_t *val, isc_boolean_t resume) { if ((val->attributes & VALATTR_NEEDNODATA) == 0 && (val->attributes & VALATTR_NEEDNOWILDCARD) == 0 && (val->attributes & VALATTR_NEEDNOQNAME) != 0) { - if ((val->attributes & VALATTR_FOUNDNOQNAME) != 0) { + if ((val->attributes & VALATTR_FOUNDNOQNAME) == 0) + findnsec3proofs(val); + if ((val->attributes & VALATTR_FOUNDNOQNAME) != 0 && + (val->attributes & VALATTR_FOUNDCLOSEST) != 0) { validator_log(val, ISC_LOG_DEBUG(3), "noqname proof found"); validator_log(val, ISC_LOG_DEBUG(3), @@ -2228,34 +2871,57 @@ nsecvalidate(dns_validator_t *val, isc_boolean_t resume) { val->event->rdataset->trust = dns_trust_secure; val->event->sigrdataset->trust = dns_trust_secure; return (ISC_R_SUCCESS); + } else if ((val->attributes & VALATTR_FOUNDOPTOUT) != 0 && + dns_name_countlabels(dns_fixedname_name(&val->wild)) + != 0) { + validator_log(val, ISC_LOG_DEBUG(3), + "optout proof found"); + val->event->optout = ISC_TRUE; + markanswer(val); + return (ISC_R_SUCCESS); + } else if ((val->attributes & VALATTR_FOUNDUNKNOWN) != 0) { + validator_log(val, ISC_LOG_DEBUG(3), + "unknown NSEC3 hash algorithm found"); + markanswer(val); + return (ISC_R_SUCCESS); } validator_log(val, ISC_LOG_DEBUG(3), "noqname proof not found"); return (DNS_R_NOVALIDNSEC); } + if ((val->attributes & VALATTR_FOUNDNOQNAME) == 0 && + (val->attributes & VALATTR_FOUNDNODATA) == 0) + findnsec3proofs(val); + /* * Do we need to check for the wildcard? */ if ((val->attributes & VALATTR_FOUNDNOQNAME) != 0 && + (val->attributes & VALATTR_FOUNDCLOSEST) != 0 && (((val->attributes & VALATTR_NEEDNODATA) != 0 && (val->attributes & VALATTR_FOUNDNODATA) == 0) || (val->attributes & VALATTR_NEEDNOWILDCARD) != 0)) { - result = checkwildcard(val); + result = checkwildcard(val, dns_rdatatype_nsec, NULL); if (result != ISC_R_SUCCESS) return (result); } if (((val->attributes & VALATTR_NEEDNODATA) != 0 && - (val->attributes & VALATTR_FOUNDNODATA) != 0) || + ((val->attributes & VALATTR_FOUNDNODATA) != 0 || + (val->attributes & VALATTR_FOUNDOPTOUT) != 0)) || ((val->attributes & VALATTR_NEEDNOQNAME) != 0 && (val->attributes & VALATTR_FOUNDNOQNAME) != 0 && (val->attributes & VALATTR_NEEDNOWILDCARD) != 0 && - (val->attributes & VALATTR_FOUNDNOWILDCARD) != 0)) { + (val->attributes & VALATTR_FOUNDNOWILDCARD) != 0 && + (val->attributes & VALATTR_FOUNDCLOSEST) != 0)) { + if ((val->attributes & VALATTR_FOUNDOPTOUT) != 0) + val->event->optout = ISC_TRUE; validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) found"); return (ISC_R_SUCCESS); } + findnsec3proofs(val); validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) not found"); @@ -2380,7 +3046,7 @@ dlvfetched(isc_task_t *task, isc_event_t *event) { } /*% - * Start the DLV lookup proccess. + * Start the DLV lookup process. * * Returns * \li ISC_R_SUCCESS @@ -2424,7 +3090,7 @@ startfinddlvsep(dns_validator_t *val, dns_name_t *unsecure) { validator_log(val, ISC_LOG_DEBUG(3), "DLV %s found", namebuf); dlv_validator_start(val); return (DNS_R_WAIT); - } + } validator_log(val, ISC_LOG_DEBUG(3), "DLV %s found with no supported " "algorithms", namebuf); markanswer(val); @@ -2566,9 +3232,13 @@ proveunsecure(dns_validator_t *val, isc_boolean_t have_ds, isc_boolean_t resume) dns_name_t *secroot; dns_name_t *tname; char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_t *found; + dns_fixedname_t fixedfound; dns_fixedname_init(&fixedsecroot); secroot = dns_fixedname_name(&fixedsecroot); + dns_fixedname_init(&fixedfound); + found = dns_fixedname_name(&fixedfound); if (val->havedlvsep) dns_name_copy(dns_fixedname_name(&val->dlvsep), secroot, NULL); else { @@ -2676,6 +3346,28 @@ proveunsecure(dns_validator_t *val, isc_boolean_t have_ds, isc_boolean_t resume) goto out; return (DNS_R_WAIT); } + /* + * Zones using NSEC3 don't return a NSEC RRset so + * we need to use dns_view_findzonecut2 to find + * the zone cut. + */ + if (result == DNS_R_NXRRSET && + !dns_rdataset_isassociated(&val->frdataset) && + dns_view_findzonecut2(val->view, tname, found, + 0, 0, ISC_FALSE, ISC_FALSE, + NULL, NULL) == ISC_R_SUCCESS && + dns_name_equal(tname, found)) { + if (val->mustbesecure) { + validator_log(val, ISC_LOG_WARNING, + "must be secure failure"); + return (DNS_R_MUSTBESECURE); + } + if (val->view->dlv == NULL || DLVTRIED(val)) { + markanswer(val); + return (ISC_R_SUCCESS); + } + return (startfinddlvsep(val, tname)); + } if (val->frdataset.trust < dns_trust_secure) { /* * This shouldn't happen, since the negative @@ -2775,6 +3467,15 @@ proveunsecure(dns_validator_t *val, isc_boolean_t have_ds, isc_boolean_t resume) return (DNS_R_WAIT); } } + +/* + if ((val->attributes & VALATTR_NEEDOPTOUT) == 0 && + val->event->message != NULL) { + val->attributes |= VALATTR_NEEDOPTOUT; + return (nsecvalidate(val, ISC_FALSE)); + } +*/ + validator_log(val, ISC_LOG_DEBUG(3), "insecurity proof failed"); return (DNS_R_NOTINSECURE); /* Couldn't complete insecurity proof */ @@ -2808,7 +3509,7 @@ dlv_validator_start(dns_validator_t *val) { /*% * Start the validation process. * - * Attempt to valididate the answer based on the category it appears to + * Attempt to validate the answer based on the category it appears to * fall in. * \li 1. secure positive answer. * \li 2. unsecure positive answer. @@ -2829,7 +3530,7 @@ validator_start(isc_task_t *task, isc_event_t *event) { vevent = (dns_validatorevent_t *)event; val = vevent->validator; - /* If the validator has been cancelled, val->event == NULL */ + /* If the validator has been canceled, val->event == NULL */ if (val->event == NULL) return; @@ -2955,6 +3656,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, event->sigrdataset = sigrdataset; event->message = message; memset(event->proofs, 0, sizeof(event->proofs)); + event->optout = ISC_FALSE; result = isc_mutex_init(&val->lock); if (result != ISC_R_SUCCESS) goto cleanup_event; @@ -2984,6 +3686,8 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_init(&val->frdataset); dns_rdataset_init(&val->fsigrdataset); dns_fixedname_init(&val->wild); + dns_fixedname_init(&val->nearest); + dns_fixedname_init(&val->closest); ISC_LINK_INIT(val, link); val->magic = VALIDATOR_MAGIC; |