summaryrefslogtreecommitdiff
path: root/validator
diff options
context:
space:
mode:
Diffstat (limited to 'validator')
-rw-r--r--validator/val_neg.c14
-rw-r--r--validator/val_sigcrypt.c12
-rw-r--r--validator/val_sigcrypt.h2
-rw-r--r--validator/val_utils.c25
-rw-r--r--validator/val_utils.h11
-rw-r--r--validator/validator.c95
6 files changed, 150 insertions, 9 deletions
diff --git a/validator/val_neg.c b/validator/val_neg.c
index bc3a83aeb4c9..66fd818994ed 100644
--- a/validator/val_neg.c
+++ b/validator/val_neg.c
@@ -1066,11 +1066,7 @@ grab_nsec(struct rrset_cache* rrset_cache, uint8_t* qname, size_t qname_len,
qname, qname_len, qtype, qclass, flags, now, 0);
struct packed_rrset_data* d;
if(!k) return NULL;
- d = (struct packed_rrset_data*)k->entry.data;
- if(d->ttl < now) {
- lock_rw_unlock(&k->entry.lock);
- return NULL;
- }
+ d = k->entry.data;
/* only secure or unchecked records that have signatures. */
if( ! ( d->security == sec_status_secure ||
(d->security == sec_status_unchecked &&
@@ -1293,6 +1289,8 @@ neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len,
if(!(msg = dns_msg_create(qname, qname_len,
LDNS_RR_TYPE_DS, zone->dclass, region, 1)))
return NULL;
+ /* The cache response means recursion is available. */
+ msg->rep->flags |= BIT_RA;
/* TTL reduced in grab_nsec */
if(!dns_msg_authadd(msg, region, ce_rrset, 0))
return NULL;
@@ -1327,6 +1325,8 @@ neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len,
if(!(msg = dns_msg_create(qname, qname_len,
LDNS_RR_TYPE_DS, zone->dclass, region, 3)))
return NULL;
+ /* The cache response means recursion is available. */
+ msg->rep->flags |= BIT_RA;
/* now=0 because TTL was reduced in grab_nsec */
if(!dns_msg_authadd(msg, region, ce_rrset, 0))
return NULL;
@@ -1417,6 +1417,8 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len,
qinfo->qtype, qinfo->qclass, region, 2)))
return NULL;
+ /* The cache response means recursion is available. */
+ msg->rep->flags |= BIT_RA;
if(!dns_msg_authadd(msg, region, nsec, 0))
return NULL;
if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL))
@@ -1430,6 +1432,8 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len,
qinfo->qtype, qinfo->qclass, region, 3)))
return NULL;
+ /* The cache response means recursion is available. */
+ msg->rep->flags |= BIT_RA;
if(!(ce = nsec_closest_encloser(qinfo->qname, nsec)))
return NULL;
dname_count_size_labels(ce, &ce_len);
diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c
index 86de6fb8e80f..9f27f9cc9bb7 100644
--- a/validator/val_sigcrypt.c
+++ b/validator/val_sigcrypt.c
@@ -1570,6 +1570,18 @@ dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf,
*reason_bogus = LDNS_EDE_NO_ZONE_KEY_BIT_SET;
return sec_status_bogus;
}
+ if((dnskey_get_flags(dnskey, dnskey_idx) & LDNS_KEY_REVOKE_KEY) &&
+ /* The REVOKE key is allowed to check sigs on itself. */
+ !(ntohs(rrset->rk.type) == LDNS_RR_TYPE_DNSKEY &&
+ query_dname_compare(rrset->rk.dname, dnskey->rk.dname)==0)
+ ) {
+ verbose(VERB_QUERY, "verify: dnskey has REVOKE bit set, "
+ "not usable for data validation per RFC 5011 s2.1");
+ *reason = "dnskey revoked";
+ if(reason_bogus)
+ *reason_bogus = LDNS_EDE_DNSKEY_MISSING;
+ return sec_status_bogus;
+ }
if(dnskey_get_protocol(dnskey, dnskey_idx) != LDNS_DNSSEC_KEYPROTO) {
/* RFC 4034 says DNSKEY PROTOCOL MUST be 3 */
diff --git a/validator/val_sigcrypt.h b/validator/val_sigcrypt.h
index 1fac8bde0e91..c1e2658e4fd1 100644
--- a/validator/val_sigcrypt.h
+++ b/validator/val_sigcrypt.h
@@ -311,7 +311,7 @@ enum sec_status dnskey_verify_rrset(struct module_env* env, struct val_env* ve,
* pass false at start. pass old value only for same rrset and same
* signature (but perhaps different key) for reuse.
* @param reason: if bogus, a string returned, fixed or alloced in scratch.
- * @param reason_bogus: EDE (8914) code paired with the reason of failure.
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
* @param section: section of packet where this rrset comes from.
* @param qstate: qstate with region.
* @return secure if this key signs this signature. unchecked on error or
diff --git a/validator/val_utils.c b/validator/val_utils.c
index 549264d76a1f..411a63b25c5b 100644
--- a/validator/val_utils.c
+++ b/validator/val_utils.c
@@ -1310,6 +1310,7 @@ val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c,
/* DS rrset exists. Return it to the validator immediately*/
struct ub_packed_rrset_key* copy = packed_rrset_copy_region(
rrset, region, *env->now);
+ struct packed_rrset_data* d = copy->entry.data;
lock_rw_unlock(&rrset->entry.lock);
if(!copy)
return NULL;
@@ -1319,6 +1320,7 @@ val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c,
msg->rep->rrsets[0] = copy;
msg->rep->rrset_count++;
msg->rep->an_numrrsets++;
+ UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl);
return msg;
}
/* lookup in rrset and negative cache for NSEC/NSEC3 */
@@ -1332,3 +1334,26 @@ val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c,
env->scratch_buffer, *env->now, 0, topname, env->cfg);
return msg;
}
+
+int derive_cname_from_dname(struct ub_packed_rrset_key* cname,
+ struct ub_packed_rrset_key* dname, uint8_t* out, size_t outlen)
+{
+ size_t prefix_len;
+ uint8_t* dname_target = NULL;
+ size_t dname_target_len = 0;
+ if(!dname_strict_subdomain_c(cname->rk.dname, dname->rk.dname))
+ return 0; /* Invalid: CNAME owner must be subdomain */
+ get_cname_target(dname, &dname_target, &dname_target_len);
+ if(!dname_target || !dname_target_len)
+ return 0; /* DNAME malformed */
+ if(cname->rk.dname_len < dname->rk.dname_len)
+ return 0; /* Not possible, due to subdomain, but check */
+ if(cname->rk.dname_len == 0)
+ return 0; /* Not possible, but check */
+ prefix_len = cname->rk.dname_len - dname->rk.dname_len;
+ if(prefix_len + dname_target_len > outlen)
+ return 0; /* Buffer too small */
+ memmove(out, cname->rk.dname, prefix_len);
+ memmove(out+prefix_len, dname_target, dname_target_len);
+ return 1;
+}
diff --git a/validator/val_utils.h b/validator/val_utils.h
index 4fe38c1bb995..e0c6499024e8 100644
--- a/validator/val_utils.h
+++ b/validator/val_utils.h
@@ -427,4 +427,15 @@ int val_favorite_ds_algo(struct ub_packed_rrset_key* ds_rrset);
struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen,
uint16_t c, struct regional* region, uint8_t* topname);
+/**
+ * Derive expected CNAME target from DNAME substitution per RFC 6672 s3.1
+ * @param cname: CNAME RRset, (e.g., b.d.a005.test CNAME 'some cname target')
+ * @param dname: DNAME RRset, (e.g., d.a005.test DNAME tgt.a005.test)
+ * @param out: Output buffer for expected CNAME target
+ * @param outlen: Output buffer size
+ * @return: 1 on success, 0 on error
+ */
+int derive_cname_from_dname(struct ub_packed_rrset_key* cname,
+ struct ub_packed_rrset_key* dname, uint8_t* out, size_t outlen);
+
#endif /* VALIDATOR_VAL_UTILS_H */
diff --git a/validator/validator.c b/validator/validator.c
index 5817fc8085a2..e7992b6e3711 100644
--- a/validator/validator.c
+++ b/validator/validator.c
@@ -496,7 +496,7 @@ generate_request(struct module_qstate* qstate, int id, uint8_t* name,
struct mesh_state* sub = NULL;
fptr_ok(fptr_whitelist_modenv_add_sub(
qstate->env->add_sub));
- if(!(*qstate->env->add_sub)(qstate, &ask,
+ if(!(*qstate->env->add_sub)(qstate, &ask, NULL,
(uint16_t)(BIT_RD|flags), 0, valrec, newq, &sub)){
log_err("Could not generate request: out of memory");
return 0;
@@ -505,7 +505,7 @@ generate_request(struct module_qstate* qstate, int id, uint8_t* name,
else {
fptr_ok(fptr_whitelist_modenv_attach_sub(
qstate->env->attach_sub));
- if(!(*qstate->env->attach_sub)(qstate, &ask,
+ if(!(*qstate->env->attach_sub)(qstate, &ask, NULL,
(uint16_t)(BIT_RD|flags), 0, valrec, newq)){
log_err("Could not generate request: out of memory");
return 0;
@@ -710,6 +710,37 @@ validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq,
((struct packed_rrset_data*)chase_reply->rrsets[i-1]->entry.data)->security == sec_status_secure &&
dname_strict_subdomain_c(s->rk.dname, chase_reply->rrsets[i-1]->rk.dname)
) {
+ /* Check that the CNAME target matches the DNAME
+ * derivation. Zone changes during the redirection
+ * lookups or looped DNAMEs can have such a CNAME. */
+ uint8_t expected_target[LDNS_MAX_DOMAINLEN];
+ uint8_t* cname_target = NULL;
+ size_t cname_target_len = 0;
+ get_cname_target(s, &cname_target, &cname_target_len);
+ if(!cname_target ||
+ !derive_cname_from_dname(s, /* CNAME RRset */
+ chase_reply->rrsets[i-1], /* DNAME RRset */
+ expected_target, /* Output buffer */
+ sizeof(expected_target))) {
+ verbose(VERB_ALGO, "DNAME CNAME derivation failed");
+ errinf_ede(qstate, "DNAME CNAME derivation failed", reason_bogus);
+ errinf_origin(qstate, qstate->reply_origin);
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, reason_bogus);
+ return 0;
+ }
+ if(query_dname_compare(cname_target, expected_target) != 0) {
+ verbose(VERB_ALGO, "CNAME target mismatch: not synthesized from DNAME");
+ errinf_ede(qstate, "CNAME target mismatch: not synthesized from DNAME", reason_bogus);
+ errinf_dname(qstate, ", for", s->rk.dname);
+ errinf_dname(qstate, "CNAME", cname_target);
+ errinf(qstate, ",");
+ errinf_origin(qstate, qstate->reply_origin);
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, reason_bogus);
+ return 0;
+ }
+
/* CNAME was synthesized by our own iterator */
/* since the DNAME verified, mark the CNAME as secure */
((struct packed_rrset_data*)s->entry.data)->security =
@@ -2699,7 +2730,9 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id,
if(!needs_validation(qstate, qstate->return_rcode,
qstate->return_msg)) {
/* no need to validate this */
- if(qstate->return_msg)
+ /* For valrec responses, leave at sec_status_unchecked,
+ * no security status has been requested for it. */
+ if(qstate->return_msg && !qstate->is_valrec)
qstate->return_msg->rep->security =
sec_status_indeterminate;
qstate->ext_state[id] = module_finished;
@@ -3090,6 +3123,62 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf,
sizeof(reasonbuf));
if(sec == sec_status_secure) {
+ /* Check for wildcard expansion */
+ uint8_t* wc = NULL;
+ size_t wl = 0;
+
+ if(!val_rrset_wildcard(cname, &wc, &wl)) {
+ verbose(VERB_ALGO, "CNAME has inconsistent wildcard signatures");
+ reason = "wildcard CNAME inconsistent signatures";
+ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+
+ if(wc != NULL) {
+ /* Wildcard expansion detected - require NSEC proof */
+ /* So this is a wildcard CNAME response to DS.
+ * If the wildcard is bogus then we have bogus.
+ * If the wildcard is true, then there is
+ * not a referral point here or lower,
+ * that can be insecure,
+ * and also no DS records, here or lower. */
+ /* For a valid chain, to DS, but this
+ * wildcard CNAME happens in a middle label,
+ * then that can not happen, because there is
+ * data under that label, and thus the wildcard
+ * should not expand.
+ * If we are going to the wildcard, that also
+ * does not expand the wildcard, when above it.
+ * So for valids lookup chains to DS, no
+ * wildcard CNAME is expected on middle labels.
+ * For lookups to an insecure point, the
+ * delegation is information under the label,
+ * and thus the wildcard does not expand.
+ * So, no insecure point is possible.
+ * Can not get a valid chain of trust, or
+ * to a delegation point for insecure.
+ * Or the wildcard, its nxdomain for the qname
+ * proof, is invalid, in which case this is
+ * a bogus reply.
+ * If this was a lookup where a wildcard
+ * expansion is genuinely expected, eg,
+ * a dnssec valid wildcard query, then the
+ * lookup should go to the right point, and
+ * not into the wildcard under the zone name.
+ * For insecure, or wildcard missing
+ * signatures, it would have to have found
+ * the DS or insecure point earlier, in the
+ * downwards search.
+ * So for missing signatures, it turns the
+ * missing signatures into a failure to the
+ * wildcard CNAME, as the reported log.
+ */
+ verbose(VERB_ALGO, "wildcard CNAME in chain of trust means no DS can be found and it is also not a delegation point that can be insecure");
+ reason = "wildcard CNAME in chain of trust means no DS found and it is also not a delegation point that can be insecure";
+ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+
verbose(VERB_ALGO, "CNAME validated, "
"proof that DS does not exist");
/* and that it is not a referral point */