diff options
Diffstat (limited to 'validator')
| -rw-r--r-- | validator/val_neg.c | 14 | ||||
| -rw-r--r-- | validator/val_sigcrypt.c | 12 | ||||
| -rw-r--r-- | validator/val_sigcrypt.h | 2 | ||||
| -rw-r--r-- | validator/val_utils.c | 25 | ||||
| -rw-r--r-- | validator/val_utils.h | 11 | ||||
| -rw-r--r-- | validator/validator.c | 95 |
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 */ |
