From 7987b687dcbf6adbff26fc0819c6aef232e31a92 Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Fri, 14 Sep 2007 11:15:42 +0000 Subject: [PATCH] NSEC3. git-svn-id: file:///svn/unbound/trunk@613 be551aaa-1e26-0410-a405-d3ace91eadb9 --- doc/Changelog | 5 + validator/val_nsec.c | 10 +- validator/val_nsec3.c | 322 ++++++++++++++++++++++++++++++++++++++++-- validator/val_nsec3.h | 97 +++++++++++++ validator/validator.c | 128 ++++++++++++++--- 5 files changed, 520 insertions(+), 42 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index fc96e5254..cbc6e3202 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +14 September 2007: Wouter + - nsec3 nodata proof, nods proof, wildcard proof. + - nsec3 support for cname chain ending in noerror or nodata. + - validator calls nsec3 proof routines if no NSECs prove anything. + 13 September 2007: Wouter - nsec3 find matching and covering, ce proof, prove namerror msg. diff --git a/validator/val_nsec.c b/validator/val_nsec.c index a18a1c62b..74320096a 100644 --- a/validator/val_nsec.c +++ b/validator/val_nsec.c @@ -151,11 +151,13 @@ val_nsec_proves_no_ds(struct ub_packed_rrset_key* nsec, /* this proof may also work if qname is a subdomain */ log_assert(query_dname_compare(nsec->rk.dname, qinfo->qname) == 0); - if(nsec_has_type(nsec, LDNS_RR_TYPE_SOA) || - nsec_has_type(nsec, LDNS_RR_TYPE_DS)) { + if(nsec_has_type(nsec, LDNS_RR_TYPE_SOA) && qinfo->qname_len != 1) { /* SOA present means that this is the NSEC from the child, - * not the parent (so it is the wrong one). - * DS present means that there should have been a positive + * not the parent (so it is the wrong one). */ + return sec_status_bogus; + } + if(nsec_has_type(nsec, LDNS_RR_TYPE_DS)) { + /* DS present means that there should have been a positive * response to the DS query, so there is something wrong. */ return sec_status_bogus; } diff --git a/validator/val_nsec3.c b/validator/val_nsec3.c index a6e444b9f..e048fef44 100644 --- a/validator/val_nsec3.c +++ b/validator/val_nsec3.c @@ -151,6 +151,18 @@ nsec3_unknown_flags(struct ub_packed_rrset_key* rrset, int r) return (int)(d->rr_data[r][2+1] & NSEC3_UNKNOWN_FLAGS); } +/** return if nsec3 RR has the optout flag */ +static int +nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r) +{ + 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+2) + return 0; /* malformed */ + return (int)(d->rr_data[r][2+1] & NSEC3_OPTOUT); +} + /** return nsec3 RR algorithm */ static int nsec3_get_algo(struct ub_packed_rrset_key* rrset, int r) @@ -886,6 +898,8 @@ next_closer(uint8_t* qname, size_t qnamelen, uint8_t* ce, * @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. + * If set true, and the return value is true, then you can be + * certain that the ce.nc_rrset and ce.nc_rr are set properly. * @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. @@ -963,6 +977,39 @@ nsec3_ce_wildcard(struct region* region, uint8_t* ce, size_t celen, return nm; } +/** Do the name error proof */ +static enum sec_status +nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo) +{ + struct ce_response ce; + uint8_t* wc; + size_t wclen; + struct ub_packed_rrset_key* wc_rrset; + int wc_rr; + + /* 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; +} + enum sec_status nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, @@ -970,11 +1017,139 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, { rbtree_t ct; struct nsec3_filter flt; + + 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(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo); +} + +/* + * No code to handle qtype=NSEC3 specially. + * This existed in early drafts, but was later (-05) removed. + */ + +/** Do the nodata proof */ +static enum sec_status +nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo) +{ struct ce_response ce; uint8_t* wc; size_t wclen; - struct ub_packed_rrset_key* wc_rrset; - int wc_rr; + struct ub_packed_rrset_key* rrset; + int rr; + + if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len, + &rrset, &rr)) { + /* cases 1 and 2 */ + if(nsec3_has_type(rrset, rr, qinfo->qtype)) { + verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " + "proved that type existed, bogus"); + return sec_status_bogus; + } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { + verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " + "proved that a CNAME existed, bogus"); + return sec_status_bogus; + } + + /* + * If type DS: filter_init zone find already found a parent + * zone, so this nsec3 is from a parent zone. + * o can be not a delegation (unusual query for normal name, + * no DS anyway, but we can verify that). + * o can be a delegation (which is the usual DS check). + * o may not have the SOA bit set (only the top of the + * zone, which must have been above the name, has that). + * Except for the root; which is checked by itself. + * + * If not type DS: matching nsec3 must not be a delegation. + */ + if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 + && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { + verbose(VERB_ALGO, "proveNodata: apex NSEC3 " + "abused for no DS proof, bogus"); + return sec_status_bogus; + } else if(qinfo->qtype != LDNS_RR_TYPE_DS && + nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && + !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { + verbose(VERB_ALGO, "proveNodata: matching " + "NSEC3 is a delegation, bogus"); + return sec_status_bogus; + } + return sec_status_secure; + } + + /* For cases 3 - 5, we need the proven closest encloser, and it + * can't match qname. Although, at this point, we know that it + * won't since we just checked that. */ + if(!nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce)) { + verbose(VERB_ALGO, "proveNodata: did not match qname, " + "nor found a proven closest encloser."); + return sec_status_bogus; + } + + /* Case 3: removed */ + + /* Case 4: */ + log_assert(ce.ce); + wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); + if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) { + /* found wildcard */ + if(nsec3_has_type(rrset, rr, qinfo->qtype)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wildcard had qtype, bogus"); + return sec_status_bogus; + } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wildcard had a CNAME, bogus"); + return sec_status_bogus; + } + if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 + && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wildcard for no DS proof has a SOA, bogus"); + return sec_status_bogus; + } else if(qinfo->qtype != LDNS_RR_TYPE_DS && + nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && + !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wilcard is a delegation, bogus"); + return sec_status_bogus; + } + return sec_status_secure; + } + + /* Case 5: */ + if(qinfo->qtype != LDNS_RR_TYPE_DS) { + verbose(VERB_ALGO, "proveNodata: could not find matching " + "NSEC3, nor matching wildcard, and qtype is not DS " + "-- no more options, bogus."); + return sec_status_bogus; + } + + /* We need to make sure that the covering NSEC3 is opt-out. */ + log_assert(ce.nc_rrset); + if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { + verbose(VERB_ALGO, "proveNodata: covering NSEC3 was not " + "opt-out in an opt-out DS NOERROR/NODATA case."); + return sec_status_bogus; + } + return sec_status_secure; +} + +enum sec_status +nsec3_prove_nodata(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; if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) return sec_status_bogus; /* no valid NSEC3s, bogus */ @@ -984,25 +1159,142 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, return sec_status_bogus; /* no RRs */ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ + return nsec3_do_prove_nodata(env, &flt, &ct, qinfo); +} - /* 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. */ +enum sec_status +nsec3_prove_wildcard(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, uint8_t* wc) +{ + rbtree_t ct; + struct nsec3_filter flt; + struct ce_response ce; + uint8_t* nc; + size_t nc_len; + size_t wclen; + (void)dname_count_size_labels(wc, &wclen); + + 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(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + + /* We know what the (purported) closest encloser is by just + * looking at the supposed generating wildcard. */ + memset(&ce, 0, sizeof(ce)); + ce.ce = wc; + ce.ce_len = wclen; + dname_remove_label(&ce.ce, &ce.ce_len); + + /* Now we still need to prove that the original data did not exist. + * 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, "proveWildcard: did not find a covering " + "NSEC3 that covered the next closer name."); + return sec_status_bogus; + } + return sec_status_secure; +} + +enum sec_status +nsec3_prove_nods(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; + struct ub_packed_rrset_key* rrset; + int rr; + log_assert(qinfo->qtype == LDNS_RR_TYPE_DS); + + 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(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + + /* Look for a matching NSEC3 to qname -- this is the normal + * NODATA case. */ + if(find_matching_nsec3(env, &flt, &ct, qinfo->qname, qinfo->qname_len, + &rrset, &rr)) { + /* If the matching NSEC3 has the SOA bit set, it is from + * the wrong zone (the child instead of the parent). If + * it has the DS bit set, then we were lied to. */ + if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA) && + qinfo->qname_len != 1) { + verbose(VERB_ALGO, "nsec3 provenods: NSEC3 is from" + " child zone, bogus"); + return sec_status_bogus; + } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) { + verbose(VERB_ALGO, "nsec3 provenods: NSEC3 has qtype" + " DS, bogus"); + return sec_status_bogus; + } + /* If the NSEC3 RR doesn't have the NS bit set, then + * this wasn't a delegation point. */ + if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS)) + return sec_status_indeterminate; + /* Otherwise, this proves no DS. */ + return sec_status_secure; + } + + /* Otherwise, we are probably in the opt-out case. */ if(!nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce)) { - verbose(VERB_ALGO, "nsec3 nameerror proof: failed to prove " - "a closest encloser"); + verbose(VERB_ALGO, "nsec3 provenods: did not match qname, " + "nor found a proven 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."); + /* we had the closest encloser proof, then we need to check that the + * covering NSEC3 was opt-out -- the proveClosestEncloser step already + * checked to see if the closest encloser was a delegation or DNAME. + */ + log_assert(ce.nc_rrset); + if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { + verbose(VERB_ALGO, "nsec3 provenods: covering NSEC3 was not " + "opt-out in an opt-out DS NOERROR/NODATA case."); return sec_status_bogus; } return sec_status_secure; } + +enum sec_status +nsec3_prove_nxornodata(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, int* nodata) +{ + rbtree_t ct; + struct nsec3_filter flt; + *nodata = 0; + + 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(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + + /* try nxdomain and nodata after another, while keeping the + * hash cache intact */ + + if(nsec3_do_prove_nameerror(env, &flt, &ct, qinfo)==sec_status_secure) + return sec_status_secure; + if(nsec3_do_prove_nodata(env, &flt, &ct, qinfo)==sec_status_secure) { + *nodata = 1; + return sec_status_secure; + } + return sec_status_bogus; +} diff --git a/validator/val_nsec3.h b/validator/val_nsec3.h index d6af5539f..af83e6f0f 100644 --- a/validator/val_nsec3.h +++ b/validator/val_nsec3.h @@ -114,4 +114,101 @@ 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); +/** + * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA + * status. There are a number of different variants to this: + * + * 1) Normal NODATA -- qname is matched to an NSEC3 record, type is not + * present. + * + * 2) ENT NODATA -- because there must be NSEC3 record for + * empty-non-terminals, this is the same as #1. + * + * 3) NSEC3 ownername NODATA -- qname matched an existing, lone NSEC3 + * ownername, but qtype was not NSEC3. NOTE: as of nsec-05, this case no + * longer exists. + * + * 4) Wildcard NODATA -- A wildcard matched the name, but not the type. + * + * 5) Opt-In DS NODATA -- the qname is covered by an opt-in span and qtype == + * DS. (or maybe some future record with the same parent-side-only property) + * + * @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 proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_nodata(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); + + +/** + * Prove that a positive wildcard match was appropriate (no direct match + * RRset). + * + * @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. + * @param wc: The purported wildcard that matched. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_wildcard(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, uint8_t* wc); + +/** + * Prove that a DS response either had no DS, or wasn't a delegation point. + * + * Fundamentally there are two cases here: normal NODATA and Opt-In NODATA. + * + * @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 proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + * or if there was no DS in an insecure (i.e., opt-in) way, + * INDETERMINATE if it was clear that this wasn't a delegation point. + */ +enum sec_status +nsec3_prove_nods(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); + +/** + * Prove NXDOMAIN or NODATA. + * + * @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. + * @param nodata: if return value is secure, this indicates if nodata or + * nxdomain was proven. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_nxornodata(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, int* nodata); + #endif /* VALIDATOR_VAL_NSEC3_H */ diff --git a/validator/validator.c b/validator/validator.c index 00354177d..8617faf10 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -440,12 +440,17 @@ validate_msg_signatures(struct module_env* env, struct val_env* ve, * * The answer and authority RRsets must already be 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_positive_response(struct query_info* qchase, - struct reply_info* chase_reply) +validate_positive_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) { uint8_t* wc = NULL; int wc_NSEC_ok = 0; @@ -497,8 +502,16 @@ validate_positive_response(struct query_info* qchase, * proven, and we have NSEC3 records, try to prove it using the NSEC3 * records. */ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { - /* TODO NSEC3 positive wildcard proof */ - /* possibly: wc_NSEC_ok = 1; */ + enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey, wc); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "Positive wildcard response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) + wc_NSEC_ok = 1; } /* If after all this, we still haven't proven the positive wildcard @@ -523,12 +536,17 @@ validate_positive_response(struct query_info* qchase, * * The answer and authority RRsets must already be 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_nodata_response(struct query_info* qchase, - struct reply_info* chase_reply) +validate_nodata_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) { /* Since we are here, there must be nothing in the ANSWER section to * validate. */ @@ -586,8 +604,15 @@ validate_nodata_response(struct query_info* qchase, } if(!has_valid_nsec && nsec3s_seen) { - /* TODO handle NSEC3 proof here */ - /* and set has_valid_nsec=1; if so */ + enum sec_status sec = nsec3_prove_nodata(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "NODATA response is insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) + has_valid_nsec = 1; } if(!has_valid_nsec) { @@ -748,12 +773,17 @@ validate_any_response(struct query_info* qchase, * * The answer and authority rrsets must already be 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_cname_response(struct query_info* qchase, - struct reply_info* chase_reply) +validate_cname_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) { uint8_t* wc = NULL; int wc_NSEC_ok = 0; @@ -816,8 +846,16 @@ validate_cname_response(struct query_info* qchase, * proven, and we have NSEC3 records, try to prove it using the NSEC3 * records. */ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { - /* TODO NSEC3 positive wildcard proof */ - /* possibly: wc_NSEC_ok = 1; */ + enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey, wc); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "wildcard CNAME response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) + wc_NSEC_ok = 1; } /* If after all this, we still haven't proven the positive wildcard @@ -841,12 +879,17 @@ validate_cname_response(struct query_info* qchase, * * The answer and authority rrsets must already be 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_cname_noanswer_response(struct query_info* qchase, - struct reply_info* chase_reply) +validate_cname_noanswer_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) { int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/ uint8_t* ce = NULL; /* for wildcard nodata responses. This is the @@ -914,8 +957,20 @@ validate_cname_noanswer_response(struct query_info* qchase, return; } if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) { - /* TODO handle NSEC3 proof here */ - /* and set nodata_valid_nsec=1; if so */ + int nodata; + enum sec_status sec = nsec3_prove_nxornodata(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey, &nodata); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "CNAMEchain to noanswer response " + "is insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) { + if(nodata) + nodata_valid_nsec = 1; + else nxdomain_valid_nsec = 1; + } } if(!nodata_valid_nsec && !nxdomain_valid_nsec) { @@ -1234,13 +1289,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, switch(subtype) { case VAL_CLASS_POSITIVE: verbose(VERB_ALGO, "Validating a positive response"); - validate_positive_response(&vq->qchase, - vq->chase_reply); + validate_positive_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); break; case VAL_CLASS_NODATA: verbose(VERB_ALGO, "Validating a nodata response"); - validate_nodata_response(&vq->qchase, vq->chase_reply); + validate_nodata_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); break; case VAL_CLASS_NAMEERROR: @@ -1251,14 +1307,15 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, case VAL_CLASS_CNAME: verbose(VERB_ALGO, "Validating a cname response"); - validate_cname_response(&vq->qchase, vq->chase_reply); + validate_cname_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); break; case VAL_CLASS_CNAMENOANSWER: verbose(VERB_ALGO, "Validating a cname noanswer " "response"); - validate_cname_noanswer_response(&vq->qchase, - vq->chase_reply); + validate_cname_noanswer_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); break; case VAL_CLASS_REFERRAL: @@ -1662,7 +1719,32 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, break; } - /* Or it could be using NSEC3. TODO */ + sec = nsec3_prove_nods(qstate->env, ve, + msg->rep->rrsets + msg->rep->an_numrrsets, + msg->rep->ns_numrrsets, qinfo, vq->key_entry); + switch(sec) { + case sec_status_secure: + verbose(VERB_ALGO, "NSEC3s for the " + "referral proved no DS."); + *ke = key_entry_create_null(qstate->region, + qinfo->qname, qinfo->qname_len, + qinfo->qclass, proof_ttl); + return (*ke) != NULL; + case sec_status_indeterminate: + verbose(VERB_ALGO, "NSEC3s for the " + "referral proved no delegation"); + *ke = NULL; + return 1; + case sec_status_bogus: + verbose(VERB_DETAIL, "NSEC3s for the " + "referral did not prove no DS."); + goto return_bogus; + case sec_status_insecure: + case sec_status_unchecked: + default: + /* NSEC3 proof did not work */ + break; + } /* Apparently, no available NSEC/NSEC3 proved NODATA, so * this is BOGUS. */ -- 2.47.2