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. * The *. has already been removed from the wc name. */ memset(&ce, 0, sizeof(ce)); ce.ce = wc; ce.ce_len = wclen; /* 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; } if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "proveWildcard: NSEC3 optout"); return sec_status_insecure; } return sec_status_secure; }
/** 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; enum sec_status sec; /* 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. */ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); if(sec != sec_status_secure) { if(sec == sec_status_bogus) verbose(VERB_ALGO, "nsec3 nameerror proof: failed " "to prove a closest encloser"); else verbose(VERB_ALGO, "nsec3 nameerror proof: closest " "nsec3 is an insecure delegation"); return sec; } log_nametypeclass(VERB_ALGO, "nsec3 namerror: proven ce=", ce.ce,0,0); /* 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; } if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "nsec3 nameerror proof: nc has optout"); return sec_status_insecure; } return sec_status_secure; }
/** neg cache nsec3 proof procedure*/ static struct dns_msg* neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, int qlabs, ldns_buffer* buf, struct rrset_cache* rrset_cache, struct regional* region, uint32_t now, uint8_t* topname) { struct dns_msg* msg; struct val_neg_data* data; uint8_t hashnc[SHA_DIGEST_LENGTH]; size_t nclen; struct ub_packed_rrset_key* ce_rrset, *nc_rrset; struct nsec3_cached_hash c; uint8_t nc_b32[257]; /* for NSEC3 ; determine the closest encloser for which we * can find an exact match. Remember the hashed lower name, * since that is the one we need a closest match for. * If we find a match straight away, then it becomes NODATA. * Otherwise, NXDOMAIN or if OPTOUT, an insecure delegation. * Also check that parameters are the same on closest encloser * and on closest match. */ if(!zone->nsec3_hash) return NULL; /* not nsec3 zone */ if(!(data=neg_find_nsec3_ce(zone, qname, qname_len, qlabs, buf, hashnc, &nclen))) { return NULL; } /* grab the ce rrset */ ce_rrset = grab_nsec(rrset_cache, data->name, data->len, LDNS_RR_TYPE_NSEC3, zone->dclass, 0, region, 1, LDNS_RR_TYPE_DS, now); if(!ce_rrset) return NULL; if(!neg_params_ok(zone, ce_rrset)) return NULL; if(nclen == 0) { /* exact match, just check the type bits */ /* need: -SOA, -DS, +NS */ if(nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_SOA) || nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_DS) || !nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_NS)) return NULL; if(!(msg = dns_msg_create(qname, qname_len, LDNS_RR_TYPE_DS, zone->dclass, region, 1))) return NULL; /* TTL reduced in grab_nsec */ if(!dns_msg_authadd(msg, region, ce_rrset, 0)) return NULL; return msg; } /* optout is not allowed without knowing the trust-anchor in use, * otherwise the optout could spoof away that anchor */ if(!topname) return NULL; /* if there is no exact match, it must be in an optout span * (an existing DS implies an NSEC3 must exist) */ nc_rrset = neg_nsec3_getnc(zone, hashnc, nclen, rrset_cache, region, now, nc_b32, sizeof(nc_b32)); if(!nc_rrset) return NULL; if(!neg_params_ok(zone, nc_rrset)) return NULL; if(!nsec3_has_optout(nc_rrset, 0)) return NULL; c.hash = hashnc; c.hash_len = nclen; c.b32 = nc_b32+1; c.b32_len = (size_t)nc_b32[0]; if(nsec3_covers(zone->name, &c, nc_rrset, 0, buf)) { /* nc_rrset covers the next closer name. * ce_rrset equals a closer encloser. * nc_rrset is optout. * No need to check wildcard for type DS */ /* capacity=3: ce + nc + soa(if needed) */ if(!(msg = dns_msg_create(qname, qname_len, LDNS_RR_TYPE_DS, zone->dclass, region, 3))) return NULL; /* now=0 because TTL was reduced in grab_nsec */ if(!dns_msg_authadd(msg, region, ce_rrset, 0)) return NULL; if(!dns_msg_authadd(msg, region, nc_rrset, 0)) return NULL; return msg; } return NULL; }
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, char** reason) { 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)) { *reason = "no valid NSEC3s"; return sec_status_bogus; /* no valid NSEC3s, bogus */ } if(!list_is_secure(env, ve, list, num, kkey, reason)) return sec_status_bogus; /* not all NSEC3 records secure */ rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) { *reason = "no NSEC3 records"; 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"); *reason = "NSEC3 from child zone"; 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"); *reason = "NSEC3 has DS in bitmap"; 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) != sec_status_secure) { /* an insecure delegation *above* the qname does not prove * anything about this qname exactly, and bogus is bogus */ verbose(VERB_ALGO, "nsec3 provenods: did not match qname, " "nor found a proven closest encloser."); *reason = "no NSEC3 closest encloser"; return sec_status_bogus; } /* robust extra check */ if(!ce.nc_rrset) { verbose(VERB_ALGO, "nsec3 nods proof: no next closer nsec3"); *reason = "no NSEC3 next closer"; return sec_status_bogus; } /* 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."); *reason = "covering NSEC3 was not opt-out in an opt-out " "DS NOERROR/NODATA case"; return sec_status_bogus; } /* RFC5155 section 9.2: if nc has optout then no AD flag set */ return sec_status_insecure; }
/** 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* rrset; int rr; enum sec_status sec; 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) && !dname_is_root(qinfo->qname)) { 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)) { if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) { verbose(VERB_ALGO, "proveNodata: matching " "NSEC3 is insecure delegation"); return sec_status_insecure; } 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. */ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); if(sec == sec_status_bogus) { verbose(VERB_ALGO, "proveNodata: did not match qname, " "nor found a proven closest encloser."); return sec_status_bogus; } else if(sec==sec_status_insecure && qinfo->qtype!=LDNS_RR_TYPE_DS){ verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure " "delegation."); return sec_status_insecure; } /* 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; } /* everything is peachy keen, except for optout spans */ if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard is in optout range, insecure"); return sec_status_insecure; } return sec_status_secure; } /* Case 5: */ /* Due to forwarders, cnames, and other collating effects, we * can see the ordinary unsigned data from a zone beneath an * insecure delegation under an optout here */ if(!ce.nc_rrset) { verbose(VERB_ALGO, "nsec3 nodata proof: no next closer nsec3"); 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)) { if(qinfo->qtype == LDNS_RR_TYPE_DS) verbose(VERB_ALGO, "proveNodata: covering NSEC3 was not " "opt-out in an opt-out DS NOERROR/NODATA case."); else verbose(VERB_ALGO, "proveNodata: could not find matching " "NSEC3, nor matching wildcard, nor optout NSEC3 " "-- no more options, bogus."); return sec_status_bogus; } /* RFC5155 section 9.2: if nc has optout then no AD flag set */ return sec_status_insecure; }