From: Wouter Wijngaards Date: Mon, 20 Aug 2007 14:27:11 +0000 (+0000) Subject: nxdomain validation. X-Git-Tag: release-0.5~107 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0f9ae7acd865547c56c1a7eefdbd405cc96c96fc;p=thirdparty%2Funbound.git nxdomain validation. git-svn-id: file:///svn/unbound/trunk@534 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 44fa414cc..3f2db5acd 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,7 +1,7 @@ 18 August 2007: Wouter - process DNSKEY response in FINDKEY state. - validate and positive validation, positive wildcard NSEC validation. - - nodata validation. + - nodata validation, nxdomain validation. 17 August 2007: Wouter - work on DS2KE routine. diff --git a/validator/val_nsec.c b/validator/val_nsec.c index 29a55ecbe..8a45599c1 100644 --- a/validator/val_nsec.c +++ b/validator/val_nsec.c @@ -330,19 +330,21 @@ val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec, uint8_t* qname) return 0; } - /* see if this nsec is the only nsec */ if(query_dname_compare(owner, next) == 0) { - /* only zone.name NSEC zone.name, disproves everything else */ - return 1; + /* this nsec is the only nsec */ + /* zone.name NSEC zone.name, disproves everything else */ + /* but only for subdomains of that zone */ + if(dname_strict_subdomain_c(qname, next)) + return 1; } - /* see if this nsec is the last nsec */ - if(dname_canonical_compare(owner, next) > 0) { + else if(dname_canonical_compare(owner, next) > 0) { /* this is the last nsec, ....(bigger) NSEC zonename(smaller) */ - /* the names after the last (owner) name do not exist */ - if(dname_canonical_compare(owner, qname) < 0) + /* the names after the last (owner) name do not exist + * there are no names before the zone name in the zone + * but the qname must be a subdomain of the zone name(next). */ + if(dname_canonical_compare(owner, qname) < 0 && + dname_strict_subdomain_c(qname, next)) return 1; - /* there are no names before the zone name in the zone */ - /* if(dname_canonical_compare(qname, next) < 0) return 1; */ } else { /* regular NSEC, (smaller) NSEC (larger) */ if(dname_canonical_compare(owner, qname) < 0 && @@ -387,3 +389,38 @@ int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec, } return 1; } + +int +val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, + size_t qnamelen) +{ + /* Determine if a NSEC record proves the non-existence of a + * wildcard that could have produced qname. */ + int labs; + int i; + uint8_t* ce = nsec_closest_encloser(qname, nsec); + uint8_t* strip; + size_t striplen; + uint8_t buf[LDNS_MAX_DOMAINLEN+3]; + if(!ce) + return 0; + /* we can subtract the closest encloser count - since that is the + * largest shared topdomain with owner and next NSEC name, + * because the NSEC is no proof for names shorter than the owner + * and next names. */ + labs = dname_count_labels(qname) - dname_count_labels(ce); + + for(i=labs; i>0; i--) { + /* i is number of labels to strip off qname, prepend * wild */ + strip = qname; + striplen = qnamelen; + dname_remove_labels(&strip, &striplen, i); + buf[0] = 1; + buf[1] = (uint8_t)'*'; + memmove(buf+2, strip, striplen); + if(val_nsec_proves_name_error(nsec, buf)) { + return 1; + } + } + return 0; +} diff --git a/validator/val_nsec.h b/validator/val_nsec.h index 296f9356c..d511f59f4 100644 --- a/validator/val_nsec.h +++ b/validator/val_nsec.h @@ -123,4 +123,15 @@ int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec, uint8_t* nsec_closest_encloser(uint8_t* qname, struct ub_packed_rrset_key* nsec); +/** + * Determine if the given NSEC proves that a wildcard match does not exist. + * + * @param nsec: the nsec RRset. + * @param qname: the name queried for. + * @param qnamelen: length of qname. + * @return true if proven. + */ +int val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, + size_t qnamelen); + #endif /* VALIDATOR_VAL_NSEC_H */ diff --git a/validator/validator.c b/validator/validator.c index dcfc38e3c..d26096938 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -451,9 +451,7 @@ validate_nodata_response(struct module_env* env, struct val_env* ve, if(val_nsec_proves_name_error(s, qchase->qname)) { ce = nsec_closest_encloser(qchase->qname, s); } - } - - if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { nsec3s_seen = 1; } } @@ -491,12 +489,81 @@ validate_nodata_response(struct module_env* env, struct val_env* ve, chase_reply->security = sec_status_secure; } -/** validate NAME ERROR (nxdomain) response */ +/** + * Validate a NAMEERROR signed response -- a response that has a NXDOMAIN + * Rcode. This consists of verifying the authority section rrsets and making + * certain that the authority section NSEC proves that the qname doesn't + * exist and the covering wildcard also doesn't exist.. + * + * Note that by the time this method is called, the process of finding the + * trusted DNSKEY rrset that signs this response must already have been + * completed. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param key_entry: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ static void validate_nameerror_response(struct module_env* env, struct val_env* ve, struct query_info* qchase, struct reply_info* chase_reply, struct key_entry_key* key_entry) { + /* FIXME: should we check to see if there is anything in the answer + * section? if so, what should the result be? */ + + int has_valid_nsec = 0; + int has_valid_wnsec = 0; + int nsec3s_seen = 0; + struct ub_packed_rrset_key* s; + enum sec_status sec; + size_t i; + + for(i=chase_reply->an_numrrsets; ian_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + sec = val_verify_rrset_entry(env, ve, s, key_entry); + if(sec != sec_status_secure) { + log_nametypeclass(VERB_ALGO, "NameError response has " + "failed AUTHORITY rrset: ", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(val_nsec_proves_name_error(s, qchase->qname)) + has_valid_nsec = 1; + if(val_nsec_proves_no_wc(s, qchase->qname, + qchase->qname_len)) + has_valid_wnsec = 1; + } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) + nsec3s_seen = 1; + } + + if(!has_valid_nsec || !has_valid_wnsec) { + /* TODO: use NSEC3 proof */ + } + + /* If the message fails to prove either condition, it is bogus. */ + if(!has_valid_nsec) { + verbose(VERB_ALGO, "NameError response has failed to prove: " + "qname does not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + if(!has_valid_wnsec) { + verbose(VERB_ALGO, "NameError response has failed to prove: " + "covering wildcard does not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + /* Otherwise, we consider the message secure. */ + verbose(VERB_ALGO, "successfully validated NAME ERROR response."); + chase_reply->security = sec_status_secure; } /** validate positive ANY response */