From: Wouter Wijngaards Date: Thu, 13 Sep 2007 15:02:33 +0000 (+0000) Subject: nsec3 work, prove name error. X-Git-Tag: release-0.5~31 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=facde2ca101f5846865047cba9e816c705745af6;p=thirdparty%2Funbound.git nsec3 work, prove name error. git-svn-id: file:///svn/unbound/trunk@610 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 9cc95b7c4..fc96e5254 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +13 September 2007: Wouter + - nsec3 find matching and covering, ce proof, prove namerror msg. + 12 September 2007: Wouter - fixup of manual page warnings, like for NSD bugreport. - nsec3 work, config, max iterations, filter, and hash cache. diff --git a/testcode/unitverify.c b/testcode/unitverify.c index f3bd2eec4..1a4c358a9 100644 --- a/testcode/unitverify.c +++ b/testcode/unitverify.c @@ -313,6 +313,13 @@ dstest_file(const char* fname) ldns_buffer_free(buf); } +/** helper for unittest of NSEC routines */ +static int +unitest_nsec_has_type_rdata(char* bitmap, size_t len, uint16_t type) +{ + return nsecbitmap_has_type_rdata((uint8_t*)bitmap, len, type); +} + /** Test NSEC type bitmap routine */ static void nsectest() diff --git a/validator/val_nsec.c b/validator/val_nsec.c index e344a8828..a18a1c62b 100644 --- a/validator/val_nsec.c +++ b/validator/val_nsec.c @@ -46,10 +46,10 @@ #include "util/data/msgreply.h" #include "util/data/dname.h" -/** Check type present in NSEC typemap with bitmap arg */ -static int -nsec_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type) +int +nsecbitmap_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type) { + /* Check type present in NSEC typemap with bitmap arg */ /* bitmasks for determining type-lowerbits presence */ uint8_t masks[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; uint8_t type_window = type>>8; @@ -82,12 +82,6 @@ nsec_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type) return 0; } -int -unitest_nsec_has_type_rdata(char* bitmap, size_t len, uint16_t type) -{ - return nsec_has_type_rdata((uint8_t*)bitmap, len, type); -} - /** * Check if type is present in the NSEC typemap * @param nsec: the nsec RRset. @@ -107,7 +101,7 @@ nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type) len = dname_valid(d->rr_data[0]+2, d->rr_len[0]-2); if(!len) return 0; - return nsec_has_type_rdata(d->rr_data[0]+2+len, + return nsecbitmap_has_type_rdata(d->rr_data[0]+2+len, d->rr_len[0]-2-len, type); } @@ -426,6 +420,8 @@ val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, strip = qname; striplen = qnamelen; dname_remove_labels(&strip, &striplen, i); + if(striplen > LDNS_MAX_DOMAINLEN-2) + continue; /* too long to prepend wildcard */ buf[0] = 1; buf[1] = (uint8_t)'*'; memmove(buf+2, strip, striplen); diff --git a/validator/val_nsec.h b/validator/val_nsec.h index d511f59f4..43f39a0aa 100644 --- a/validator/val_nsec.h +++ b/validator/val_nsec.h @@ -74,8 +74,15 @@ enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env, struct reply_info* rep, struct key_entry_key* kkey, uint32_t* proof_ttl); -/** Unit test call to test function for nsec typemap check */ -int unitest_nsec_has_type_rdata(char* bitmap, size_t len, uint16_t type); +/** + * nsec typemap check, takes an NSEC-type bitmap as argument, checks for type. + * @param bitmap: pointer to the bitmap part of wireformat rdata. + * @param len: length of the bitmap, in bytes. + * @param type: the type (in host order) to check for. + * @return true if the type bit was set in the bitmap. false if not, or + * if the bitmap was malformed in some way. + */ +int nsecbitmap_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type); /** * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also diff --git a/validator/val_nsec3.c b/validator/val_nsec3.c index 4e21ef63b..2ecfff672 100644 --- a/validator/val_nsec3.c +++ b/validator/val_nsec3.c @@ -46,9 +46,25 @@ #include "validator/val_kentry.h" #include "util/region-allocator.h" #include "util/rbtree.h" +#include "util/module.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" #include "util/data/msgreply.h" +/* we include nsec.h for the bitmap_has_type function */ +#include "validator/val_nsec.h" + +/** + * This function we get from ldns-compat or from base system + * it returns the number of data bytes stored at the target, or <0 on error. + */ +int b32_ntop_extended_hex(uint8_t const *src, size_t srclength, + char *target, size_t targsize); +/** + * This function we get from ldns-compat or from base system + * it returns the number of data bytes stored at the target, or <0 on error. + */ +int b32_pton_extended_hex(char const *src, size_t hashed_owner_str_len, + uint8_t *target, size_t targsize); /** * The NSEC3 hash result storage. @@ -89,11 +105,11 @@ struct ce_response { /** NSEC3 record that proved ce. rrset */ struct ub_packed_rrset_key* ce_rrset; /** NSEC3 record that proved ce. rr number */ - size_t ce_rr; + int ce_rr; /** NSEC3 record that proved nc. rrset */ struct ub_packed_rrset_key* nc_rrset; /** NSEC3 record that proved nc. rr*/ - size_t nc_rr; + int nc_rr; }; /** @@ -201,6 +217,61 @@ nsec3_get_salt(struct ub_packed_rrset_key* rrset, int r, return 1; } +/** return nsec3 RR next hashed owner name */ +static int +nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r, + uint8_t** next, size_t* nextlen) +{ + size_t saltlen; + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+5) { + *next = 0; + *nextlen = 0; + return 0; /* malformed */ + } + saltlen = (size_t)d->rr_data[r][2+4]; + if(d->rr_len[r] < 2+5+saltlen+1) { + *next = 0; + *nextlen = 0; + return 0; /* malformed */ + } + *nextlen = (size_t)d->rr_data[r][2+5+saltlen]; + if(d->rr_len[r] < 2+5+saltlen+1+*nextlen) { + *next = 0; + *nextlen = 0; + return 0; /* malformed */ + } + *next = d->rr_data[r]+2+5+saltlen+1; + return 1; +} + +/** see if NSEC3 RR contains given type */ +static int +nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type) +{ + uint8_t* bitmap; + size_t bitlen, skiplen; + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + skiplen = 2+4; + /* skip salt */ + if(d->rr_len[r] < skiplen+1) + return 0; /* malformed, too short */ + skiplen += 1+(size_t)d->rr_len[skiplen]; + /* skip next hashed owner */ + if(d->rr_len[r] < skiplen+1) + return 0; /* malformed, too short */ + skiplen += 1+(size_t)d->rr_len[skiplen]; + if(d->rr_len[r] < skiplen) + return 0; /* malformed, too short */ + bitlen = d->rr_len[r] - skiplen; + bitmap = d->rr_data[r]+skiplen; + return nsecbitmap_has_type_rdata(bitmap, bitlen, type); +} + /** * Iterate through NSEC3 list, per RR * This routine gives the next RR in the list (or sets rrset null). @@ -470,10 +541,6 @@ nsec3_calc_hash(struct region* region, ldns_buffer* buf, return 1; } -/** This function we get from ldns-compat or from base system */ -int b32_ntop_extended_hex(uint8_t const *src, size_t srclength, - char *target, size_t targsize); - /** perform b32 encoding of hash */ static int nsec3_calc_b32(struct region* region, ldns_buffer* buf, @@ -549,34 +616,391 @@ nsec3_hash_name(rbtree_t* table, struct region* region, ldns_buffer* buf, return 1; } +/** + * compare a label lowercased + */ +static int +label_compare_lower(uint8_t* lab1, uint8_t* lab2, size_t lablen) +{ + size_t i; + for(i=0; irk.dname; + /* compare, does hash of name based on params in this NSEC3 + * match the owner name of this NSEC3? + * name must be: base32 . zone name + * so; first label must not be root label (not zero length), + * and match the b32 encoded hash length, + * and the label content match the b32 encoded hash + * and the rest must be the zone name. + */ + if(hash->b32_len != 0 && (size_t)nm[0] == hash->b32_len && + label_compare_lower(nm+1, hash->b32, hash->b32_len) == 0 && + query_dname_compare(nm+(size_t)nm[0]+1, flt->zone) == 0) { + return 1; + } + return 0; +} + /** * Find matching NSEC3 * Find the NSEC3Record that matches a hash of a name. + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param nm: name to look for. + * @param nmlen: length of name. + * @param rrset: nsec3 that matches is returned here. + * @param rr: rr number in nsec3 rrset that matches. + * @return true if a matching NSEC3 is found, false if not. */ +static int +find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, uint8_t* nm, size_t nmlen, + struct ub_packed_rrset_key** rrset, int* rr) +{ + size_t i_rs; + int i_rr; + struct ub_packed_rrset_key* s; + struct nsec3_cached_hash* hash; + int r; + + /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ + for(s=filter_first(flt, &i_rs, &i_rr); s; + s=filter_next(flt, &i_rs, &i_rr)) { + /* get name hashed for this NSEC3 RR */ + r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer, + s, i_rr, nm, nmlen, &hash); + if(r == 0) { + log_err("nsec3: malloc failure"); + break; /* alloc failure */ + } else if(r < 0) + continue; /* malformed NSEC3 */ + else if(nsec3_hash_matches_owner(flt, hash, s)) { + *rrset = s; /* rrset with this name */ + *rr = i_rr; /* matches hash with these parameters */ + return 1; + } + } + *rrset = NULL; + *rr = 0; + return 0; +} /** * nsec3Covers * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record * covers the hash. Covers specifically means that the hash is in between * the owner and next hashes and does not equal either. + * + * @param flt: the NSEC3 RR filter, contains zone name. + * @param hash: the hash of the name + * @param rrset: the rrset of the NSEC3. + * @param rr: which rr in the rrset. + * @param buf: temporary buffer. + * @return true if covers, false if not. */ +static int +nsec3_covers(struct nsec3_filter* flt, struct nsec3_cached_hash* hash, + struct ub_packed_rrset_key* rrset, int rr, ldns_buffer* buf) +{ + uint8_t* next, *owner; + size_t nextlen; + int len; + if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen)) + return 0; /* malformed RR proves nothing */ + + /* check the owner name is a hashed value . apex + * base32 encoded values must have equal length. + * hash_value and next hash value must have equal length. */ + if(nextlen != hash->hash_len || hash->hash_len==0||hash->b32_len==0|| + (size_t)*rrset->rk.dname != hash->b32_len || + query_dname_compare(rrset->rk.dname+1+ + (size_t)*rrset->rk.dname, flt->zone) != 0) + return 0; /* bad lengths or owner name */ + + /* This is the "normal case: owner < next and owner < hash < next */ + if(label_compare_lower(rrset->rk.dname+1, hash->b32, + hash->b32_len) < 0 && + memcmp(hash->hash, next, nextlen) < 0) + return 1; + + /* convert owner name from text to binary */ + ldns_buffer_clear(buf); + owner = ldns_buffer_begin(buf); + len = b32_pton_extended_hex((char*)rrset->rk.dname+1, hash->b32_len, + owner, ldns_buffer_limit(buf)); + if(len<1) + return 0; /* bad owner name in some way */ + if((size_t)len != hash->hash_len || (size_t)len != nextlen) + return 0; /* wrong length */ + + /* this is the end of zone case: next <= owner && + * (hash > owner || hash < next) + * this also covers the only-apex case of next==owner. + */ + if(memcmp(next, owner, nextlen) <= 0 && + ( memcmp(hash->hash, owner, nextlen) > 0 || + memcmp(hash->hash, next, nextlen) < 0)) { + return 1; + } + return 0; +} /** * findCoveringNSEC3 - * Given a pre-hashed name, find a covering NSEC3 from among a list of - * NSEC3s. + * Given a name, find a covering NSEC3 from among a list of NSEC3s. + * + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param nm: name to check if covered. + * @param nmlen: length of name. + * @param rrset: covering NSEC3 rrset is returned here. + * @param rr: rr of cover is returned here. + * @return true if a covering NSEC3 is found, false if not. */ +static int +find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, uint8_t* nm, size_t nmlen, + struct ub_packed_rrset_key** rrset, int* rr) +{ + size_t i_rs; + int i_rr; + struct ub_packed_rrset_key* s; + struct nsec3_cached_hash* hash; + int r; + + /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ + for(s=filter_first(flt, &i_rs, &i_rr); s; + s=filter_next(flt, &i_rs, &i_rr)) { + /* get name hashed for this NSEC3 RR */ + r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer, + s, i_rr, nm, nmlen, &hash); + if(r == 0) { + log_err("nsec3: malloc failure"); + break; /* alloc failure */ + } else if(r < 0) + continue; /* malformed NSEC3 */ + else if(nsec3_covers(flt, hash, s, i_rr, + env->scratch_buffer)) { + *rrset = s; /* rrset with this name */ + *rr = i_rr; /* covers hash with these parameters */ + return 1; + } + } + *rrset = NULL; + *rr = 0; + return 0; +} /** * findClosestEncloser * Given a name and a list of NSEC3s, find the candidate closest encloser. * This will be the first ancestor of 'name' (including itself) to have a * matching NSEC3 RR. + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param qinfo: query that is verified for. + * @param ce: closest encloser information is returned in here. + * @return true if a closest encloser candidate is found, false if not. + */ +static int +nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo, struct ce_response* ce) +{ + uint8_t* nm = qinfo->qname; + size_t nmlen = qinfo->qname_len; + + /* This scans from longest name to shortest, so the first match + * we find is the only viable candidate. */ + + /* (David:) FIXME: modify so that the NSEC3 matching the zone apex need + * not be present. (Mark Andrews idea). + * (Wouter:) But make sure you check for DNAME bit in zone apex, + * if the NSEC3 you find is the only NSEC3 in the zone, then this + * may be the case. */ + + while(dname_subdomain_c(nm, flt->zone)) { + if(find_matching_nsec3(env, flt, ct, nm, nmlen, + &ce->ce_rrset, &ce->ce_rr)) { + ce->ce = nm; + ce->ce_len = nmlen; + return 1; + } + dname_remove_label(&nm, &nmlen); + } + return 0; +} + +/** + * Given a qname and its proven closest encloser, calculate the "next + * closest" name. Basically, this is the name that is one label longer than + * the closest encloser that is still a subdomain of qname. + * + * @param qname: query name. + * @param qnamelen: length of qname. + * @param ce: closest encloser + * @param nm: result name. + * @param nmlen: length of nm. */ +static void +next_closer(uint8_t* qname, size_t qnamelen, uint8_t* ce, + uint8_t** nm, size_t* nmlen) +{ + int strip = dname_count_labels(qname) - dname_count_labels(ce) -1; + *nm = qname; + *nmlen = qnamelen; + if(strip>0) + dname_remove_labels(nm, nmlen, strip); +} /** * proveClosestEncloser * Given a List of nsec3 RRs, find and prove the closest encloser to qname. + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param qinfo: query that is verified for. + * @param prove_does_not_exist: If true, then if the closest encloser + * turns out to be qname, then null is returned. + * @param ce: closest encloser information is returned in here. + * @return false if no closest encloser could be proven. + * true if a closest encloser could be proven, ce is set. */ +static int +nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo, int prove_does_not_exist, + struct ce_response* ce) +{ + uint8_t* nc; + size_t nc_len; + /* robust: clean out ce, in case it gets abused later */ + memset(ce, 0, sizeof(*ce)); + + if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " + "not find a candidate for the closest encloser."); + return 0; + } + + if(query_dname_compare(ce->ce, qinfo->qname) == 0) { + if(prove_does_not_exist) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: " + "proved that qname existed, bad"); + return 0; + } + /* otherwise, we need to nothing else to prove that qname + * is its own closest encloser. */ + return 1; + } + + /* If the closest encloser is actually a delegation, then the + * response should have been a referral. If it is a DNAME, then + * it should have been a DNAME response. */ + if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_NS) && + !nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_SOA)) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " + "encloser was a delegation, bad"); + return 0; + } + if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DNAME)) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " + "encloser was a DNAME, bad"); + return 0; + } + + /* Otherwise, we need to show that the next closer name is covered. */ + next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len); + if(!find_covering_nsec3(env, flt, ct, nc, nc_len, + &ce->nc_rrset, &ce->nc_rr)) { + verbose(VERB_ALGO, "nsec3: Could not find proof that the " + "candidate encloser was the closest encloser"); + return 0; + } + return 1; +} + +/** allocate a wildcard for the closest encloser */ +static uint8_t* +nsec3_ce_wildcard(struct region* region, uint8_t* ce, size_t celen, + size_t* len) +{ + uint8_t* nm; + if(celen > LDNS_MAX_DOMAINLEN - 2) + return 0; /* too long */ + nm = (uint8_t*)region_alloc(region, celen+2); + if(!nm) { + log_err("nsec3 wildcard: out of memory"); + return 0; /* alloc failure */ + } + nm[0] = 1; + nm[1] = (uint8_t)'*'; /* wildcard label */ + memmove(nm+2, ce, celen); + *len = celen+2; + return nm; +} + +enum sec_status +nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey) +{ + rbtree_t ct; + struct nsec3_filter flt; + struct ce_response ce; + uint8_t* wc; + size_t wclen; + struct ub_packed_rrset_key* wc_rrset; + int wc_rr; + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ + rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + /* First locate and prove the closest encloser to qname. We will + * use the variant that fails if the closest encloser turns out + * to be qname. */ + if(!nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce)) { + verbose(VERB_ALGO, "nsec3 nameerror proof: failed to prove " + "a closest encloser"); + return sec_status_bogus; + } + + /* At this point, we know that qname does not exist. Now we need + * to prove that the wildcard does not exist. */ + log_assert(ce.ce); + wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); + if(!wc || !find_covering_nsec3(env, &flt, &ct, wc, wclen, + &wc_rrset, &wc_rr)) { + verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " + "that the applicable wildcard did not exist."); + return sec_status_bogus; + } + return sec_status_secure; +} diff --git a/validator/val_nsec3.h b/validator/val_nsec3.h index 1ae612f4f..d6af5539f 100644 --- a/validator/val_nsec3.h +++ b/validator/val_nsec3.h @@ -93,4 +93,25 @@ struct key_entry_key; /** The SHA1 hash algorithm for NSEC3 */ #define NSEC3_HASH_SHA1 0x01 +/** + * Determine if the set of NSEC3 records provided with a response prove NAME + * ERROR. This means that the NSEC3s prove a) the closest encloser exists, + * b) the direct child of the closest encloser towards qname doesn't exist, + * and c) *.closest encloser does not exist. + * + * @param env: module environment with temporary region and buffer. + * @param ve: validator environment, with iteration count settings. + * @param list: array of RRsets, some of which are NSEC3s. + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. + * @return: + * sec_status SECURE of the Name Error is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey); + #endif /* VALIDATOR_VAL_NSEC3_H */ diff --git a/validator/validator.c b/validator/validator.c index 47598c2e8..a334b491b 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -46,6 +46,7 @@ #include "validator/val_kentry.h" #include "validator/val_utils.h" #include "validator/val_nsec.h" +#include "validator/val_nsec3.h" #include "services/cache/dns.h" #include "util/data/dname.h" #include "util/module.h" @@ -610,12 +611,17 @@ validate_nodata_response(struct query_info* qchase, * * The answer and authority RRsets must have already been verified as secure. * + * @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 kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). */ static void -validate_nameerror_response(struct query_info* qchase, - struct reply_info* chase_reply) +validate_nameerror_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) { int has_valid_nsec = 0; int has_valid_wnsec = 0; @@ -637,7 +643,17 @@ validate_nameerror_response(struct query_info* qchase, } if(!has_valid_nsec || !has_valid_wnsec) { - /* TODO: use NSEC3 proof */ + /* use NSEC3 proof, both answer and auth rrsets, in case + * NSEC3s end up in the answer (due to qtype=NSEC3 or so) */ + chase_reply->security = nsec3_prove_nameerror(env, ve, + chase_reply->rrsets, chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets, qchase, kkey); + if(chase_reply->security != sec_status_secure) { + verbose(VERB_DETAIL, "NameError response failed nsec, " + "nsec3 proof was %s", sec_status_to_string( + chase_reply->security)); + return; + } } /* If the message fails to prove either condition, it is bogus. */ @@ -1229,8 +1245,8 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, case VAL_CLASS_NAMEERROR: verbose(VERB_ALGO, "Validating a nxdomain response"); - validate_nameerror_response(&vq->qchase, - vq->chase_reply); + validate_nameerror_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); break; case VAL_CLASS_CNAME: