]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
NSEC3.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Fri, 14 Sep 2007 11:15:42 +0000 (11:15 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Fri, 14 Sep 2007 11:15:42 +0000 (11:15 +0000)
git-svn-id: file:///svn/unbound/trunk@613 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
validator/val_nsec.c
validator/val_nsec3.c
validator/val_nsec3.h
validator/validator.c

index fc96e5254f7a791fe95f403e928e7c51ce04f447..cbc6e3202d4ef888ccda2012bfb6e6804ef94941 100644 (file)
@@ -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.
 
index a18a1c62b7877f5474cbd8087a08ad351eb774a0..74320096a79403485103d022f39b9fbeb9f79a83 100644 (file)
@@ -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;
        }
index a6e444b9f3a59c7b02022840ce0df812697f0aa8..e048fef44051fcb633537ef2f3b33743e2e44e2f 100644 (file)
@@ -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;
+}
index d6af5539fe24bb8c9a4096e2c24dab5337ba743b..af83e6f0f291f64cbc5b2de8a004c55dc1fc307c 100644 (file)
@@ -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 */
index 00354177de1e5b883dfd5ba9b2d1b475c9a913a7..8617faf103c6d20f9b30c97c13d73b41311b7386 100644 (file)
@@ -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. */