コード例 #1
0
/**
 * 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.
 * 	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 bogus if no closest encloser could be proven.
 * 	secure if a closest encloser could be proven, ce is set.
 * 	insecure if the closest-encloser candidate turns out to prove
 * 		that an insecure delegation exists above the qname.
 */
static enum sec_status
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 sec_status_bogus;
	}
	log_nametypeclass(VERB_ALGO, "ce candidate", ce->ce, 0, 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 sec_status_bogus;
		}
		/* otherwise, we need to nothing else to prove that qname 
		 * is its own closest encloser. */
		return sec_status_secure;
	}

	/* 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)) {
		if(!nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DS)) {
			verbose(VERB_ALGO, "nsec3 proveClosestEncloser: "
				"closest encloser is insecure delegation");
			return sec_status_insecure;
		}
		verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest "
			"encloser was a delegation, bad");
		return sec_status_bogus;
	}
	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 sec_status_bogus;
	}
	
	/* 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 sec_status_bogus;
	}
	return sec_status_secure;
}
コード例 #2
0
ファイル: val_neg.c プロジェクト: stasic/debian-unbound
/**
 * Check that an NSEC3 rrset does not have a type set.
 * None of the nsec3s in a hash-collision are allowed to have the type.
 * (since we do not know which one is the nsec3 looked at, flags, ..., we
 * ignore the cached item and let it bypass negative caching).
 * @param k: the nsec3 rrset to check.
 * @param t: type to check
 * @return true if no RRs have the type.
 */
static int nsec3_no_type(struct ub_packed_rrset_key* k, uint16_t t)
{
	int count = (int)((struct packed_rrset_data*)k->entry.data)->count;
	int i;
	for(i=0; i<count; i++)
		if(nsec3_has_type(k, i, t))
			return 0;
	return 1;
}
コード例 #3
0
ファイル: val_neg.c プロジェクト: stasic/debian-unbound
/** 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;
}
コード例 #4
0
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;
}
コード例 #5
0
/** 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;
}