CNAME validation.

git-svn-id: file:///svn/unbound/trunk@542 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2007-08-23 15:23:45 +00:00
parent a7001366a1
commit b54a0400ab
5 changed files with 621 additions and 52 deletions

View file

@ -1,3 +1,12 @@
23 August 2007: Wouter
- CNAME handling - move needs_validation to before val_new().
val_new() setups the chase-reply to be an edited copy of the msg.
new classification, and find signer can find for it.
removal of unsigned crap from additional, and query restart for
cname.
- refuse to follow wildcarded DNAMEs when validating.
But you can query for qtype ANY, or qtype DNAME and validate that.
22 August 2007: Wouter
- bogus TTL.
- review - use val_error().

View file

@ -49,7 +49,8 @@
#include "util/net_help.h"
enum val_classification
val_classify_response(struct query_info* qinf, struct reply_info* rep)
val_classify_response(struct query_info* qinf, struct reply_info* rep,
size_t skip)
{
int rcode = (int)FLAGS_GET_RCODE(rep->flags);
size_t i;
@ -60,6 +61,10 @@ val_classify_response(struct query_info* qinf, struct reply_info* rep)
return VAL_CLASS_NAMEERROR;
log_assert(rcode == LDNS_RCODE_NOERROR);
/* next check if the skip into the answer section shows no answer */
if(skip>0 && rep->an_numrrsets <= skip)
return VAL_CLASS_CNAMENOANSWER;
/* Next is NODATA */
if(rep->an_numrrsets == 0)
return VAL_CLASS_NODATA;
@ -74,7 +79,7 @@ val_classify_response(struct query_info* qinf, struct reply_info* rep)
/* Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
* qtype=CNAME, this will yield a CNAME response. */
for(i=0; i<rep->an_numrrsets; i++) {
for(i=skip; i<rep->an_numrrsets; i++) {
if(ntohs(rep->rrsets[i]->rk.type) == qinf->qtype)
return VAL_CLASS_POSITIVE;
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME)
@ -135,17 +140,57 @@ val_find_rrset_signer(struct ub_packed_rrset_key* rrset, uint8_t** sname,
sname, slen);
}
void
val_find_signer(struct query_info* qinf, struct reply_info* rep,
uint8_t** signer_name, size_t* signer_len)
/**
* Find best signer name in this set of rrsigs.
* @param rrset: which rrsigs to look through.
* @param qinf: the query name that needs validation.
* @param signer_name: the best signer_name. Updated if a better one is found.
* @param signer_len: length of signer name.
* @param matchcount: count of current best name (starts at 0 for no match).
* Updated if match is improved.
*/
static void
val_find_best_signer(struct ub_packed_rrset_key* rrset,
struct query_info* qinf, uint8_t** signer_name, size_t* signer_len,
int* matchcount)
{
struct packed_rrset_data* d = (struct packed_rrset_data*)
rrset->entry.data;
uint8_t* sign;
size_t i;
int m;
for(i=d->count; i<d->count+d->rrsig_count; i++) {
sign = d->rr_data[i]+2+18;
/* look at signatures that are valid (long enough),
* and have a signer name that is a superdomain of qname,
* and then check the number of labels in the shared topdomain
* improve the match if possible */
if(d->rr_len[i] > 2+19 && /* rdata, sig + root label*/
dname_subdomain_c(qinf->qname, sign)) {
(void)dname_lab_cmp(qinf->qname,
dname_count_labels(qinf->qname),
sign, dname_count_labels(sign), &m);
if(m > *matchcount) {
*matchcount = m;
*signer_name = sign;
(void)dname_count_size_labels(*signer_name,
signer_len);
}
}
}
}
void
val_find_signer(enum val_classification subtype, struct query_info* qinf,
struct reply_info* rep, size_t cname_skip, uint8_t** signer_name,
size_t* signer_len)
{
enum val_classification subtype = val_classify_response(qinf, rep);
size_t i;
if(subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_CNAME
|| subtype == VAL_CLASS_ANY) {
/* check for the answer rrset */
for(i=0; i<rep->an_numrrsets; i++) {
for(i=cname_skip; i<rep->an_numrrsets; i++) {
if(query_dname_compare(qinf->qname,
rep->rrsets[i]->rk.dname) == 0) {
val_find_rrset_signer(rep->rrsets[i],
@ -168,6 +213,21 @@ val_find_signer(struct query_info* qinf, struct reply_info* rep,
return;
}
}
} else if(subtype == VAL_CLASS_CNAMENOANSWER) {
/* find closest superdomain signer name in authority section
* NSEC and NSEC3s */
int matchcount = 0;
*signer_name = NULL;
*signer_len = 0;
for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->
ns_numrrsets; i++) {
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC
|| ntohs(rep->rrsets[i]->rk.type) ==
LDNS_RR_TYPE_NSEC3) {
val_find_best_signer(rep->rrsets[i], qinf,
signer_name, signer_len, &matchcount);
}
}
} else {
verbose(VERB_ALGO, "find_signer: could not find signer name"
" for unknown type response");
@ -408,3 +468,120 @@ val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc)
*wc = NULL;
return 1;
}
int
val_chase_cname(struct query_info* qchase, struct reply_info* rep,
size_t* cname_skip) {
size_t i;
/* skip any DNAMEs, go to the CNAME for next part */
for(i = *cname_skip; i < rep->an_numrrsets; i++) {
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME &&
query_dname_compare(qchase->qname, rep->rrsets[i]->
rk.dname) == 0) {
qchase->qname = NULL;
get_cname_target(rep->rrsets[i], &qchase->qname,
&qchase->qname_len);
if(!qchase->qname)
return 0; /* bad CNAME rdata */
(*cname_skip) = i;
return 1;
}
}
return 0; /* CNAME classified but no matching CNAME ?! */
}
/** see if rrset has signer name as one of the rrsig signers */
static int
rrset_has_signer(struct ub_packed_rrset_key* rrset, uint8_t* name, size_t len)
{
struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
entry.data;
size_t i;
for(i = d->count; i< d->count+d->rrsig_count; i++) {
if(d->rr_len[i] > 2+18+len) {
/* at least rdatalen + signature + signame (+1 sig)*/
if(query_dname_compare(name, d->rr_data[i]+2+18) == 0)
{
return 1;
}
}
}
return 0;
}
void
val_fill_reply(struct reply_info* chase, struct reply_info* orig,
size_t cname_skip, uint8_t* name, size_t len)
{
/* unsigned RRsets are never copied, but should not happen in
* secure answers anyway. Except for the synthesized CNAME after
* a DNAME. */
size_t i;
int seen_dname = 0;
chase->rrset_count = 0;
chase->an_numrrsets = 0;
chase->ns_numrrsets = 0;
chase->ar_numrrsets = 0;
/* ANSWER section */
for(i=cname_skip; i<orig->an_numrrsets; i++) {
if(seen_dname && ntohs(orig->rrsets[i]->rk.type) ==
LDNS_RR_TYPE_CNAME) {
chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i];
seen_dname = 0;
} else if(rrset_has_signer(orig->rrsets[i], name, len)) {
chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i];
if(ntohs(orig->rrsets[i]->rk.type) ==
LDNS_RR_TYPE_DNAME) {
seen_dname = 1;
}
}
}
/* AUTHORITY section */
for(i=orig->an_numrrsets; i<orig->an_numrrsets+orig->ns_numrrsets;
i++) {
if(rrset_has_signer(orig->rrsets[i], name, len)) {
chase->rrsets[chase->an_numrrsets+
chase->ns_numrrsets++] = orig->rrsets[i];
}
}
/* ADDITIONAL section */
for(i=orig->an_numrrsets+orig->ns_numrrsets; i<orig->rrset_count;
i++) {
if(rrset_has_signer(orig->rrsets[i], name, len)) {
chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+
chase->ar_numrrsets++] = orig->rrsets[i];
}
}
chase->rrset_count = chase->an_numrrsets + chase->ns_numrrsets +
chase->ar_numrrsets;
}
void
val_dump_nonsecure(struct reply_info* rep)
{
size_t i;
/* authority */
for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) {
if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data)
->security != sec_status_secure) {
/* remove this unsigned/bogus/unneeded rrset */
memmove(rep->rrsets+i, rep->rrsets+i+1,
sizeof(struct ub_packed_rrset_key*)*
(rep->rrset_count - i - 1));
rep->ns_numrrsets--;
rep->rrset_count--;
}
}
/* additional */
for(i=rep->an_numrrsets+rep->ns_numrrsets; i<rep->rrset_count; i++) {
if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data)
->security != sec_status_secure) {
/* remove this unsigned/bogus/unneeded rrset */
memmove(rep->rrsets+i, rep->rrsets+i+1,
sizeof(struct ub_packed_rrset_key*)*
(rep->rrset_count - i - 1));
rep->ar_numrrsets--;
rep->rrset_count--;
}
}
}

View file

@ -66,32 +66,42 @@ enum val_classification {
VAL_CLASS_NODATA,
/** A NXDOMAIN response. */
VAL_CLASS_NAMEERROR,
/** A CNAME/DNAME chain, and the offset is at the end of it,
* but there is no answer here, it can be NAMERROR or NODATA. */
VAL_CLASS_CNAMENOANSWER,
/** A response to a qtype=ANY query. */
VAL_CLASS_ANY
};
/**
* Given a response, classify ANSWER responses into a subtype.
* @param qinf: query info
* @param rep: response
* @return A subtype ranging from UNKNOWN to NAMEERROR.
* @param qinf: query info. The chased query name.
* @param rep: response. The original response.
* @param skip: offset into the original response answer section.
* @return A subtype, all values possible except UNTYPED .
* Once CNAME type is returned you can increase skip.
* Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible.
*/
enum val_classification val_classify_response(struct query_info* qinf,
struct reply_info* rep);
struct reply_info* rep, size_t skip);
/**
* Given a response, determine the name of the "signer". This is primarily
* to determine if the response is, in fact, signed at all, and, if so, what
* is the name of the most pertinent keyset.
*
* @param qinf: query
* @param rep: response to that
* @param subtype: the type from classify.
* @param qinf: query, the chased query name.
* @param rep: response to that, original response.
* @param cname_skip: how many answer rrsets have been skipped due to CNAME
* chains being chased around.
* @param signer_name: signer name, if the response is signed
* (even partially), or null if the response isn't signed.
* @param signer_len: length of signer_name of 0 if signer_name is NULL.
*/
void val_find_signer(struct query_info* qinf, struct reply_info* rep,
uint8_t** signer_name, size_t* signer_len);
void val_find_signer(enum val_classification subtype,
struct query_info* qinf, struct reply_info* rep,
size_t cname_skip, uint8_t** signer_name, size_t* signer_len);
/**
* Verify RRset with keys
@ -169,4 +179,39 @@ int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset);
*/
int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc);
/**
* Chase the cname to the next query name.
* @param qchase: the current query name, updated to next target.
* @param rep: original message reply to look at CNAMEs.
* @param cname_skip: the skip into the answer section. Updated to skip
* DNAME and CNAME to the next part of the answer.
* @return false on error (bad rdata).
*/
int val_chase_cname(struct query_info* qchase, struct reply_info* rep,
size_t* cname_skip);
/**
* Fill up the chased reply with the content from the original reply;
* as pointers to those rrsets. Select the part after the cname_skip into
* the answer section, NS and AR sections that are signed with same signer.
*
* @param chase: chased reply, filled up.
* @param orig: original reply.
* @param cname_skip: which part of the answer section to skip.
* The skipped part contains CNAME(and DNAME)s that have been chased.
* @param name: the signer name to look for.
* @param len: length of name.
*/
void val_fill_reply(struct reply_info* chase, struct reply_info* orig,
size_t cname_skip, uint8_t* name, size_t len);
/**
* Remove all unsigned or non-secure status rrsets from NS and AR sections.
* So that unsigned data does not get let through to clients, when we have
* found the data to be secure.
*
* @param rep: reply to dump all nonsecure stuff out of.
*/
void val_dump_nonsecure(struct reply_info* rep);
#endif /* VALIDATOR_VAL_UTILS_H */

View file

@ -143,7 +143,17 @@ val_new(struct module_qstate* qstate, int id)
vq->orig_msg = qstate->return_msg;
}
vq->qchase = qstate->qinfo;
vq->chase_reply = vq->orig_msg->rep;
/* chase reply will be an edited (sub)set of the orig msg rrset ptrs */
vq->chase_reply = region_alloc_init(qstate->region, vq->orig_msg->rep,
sizeof(struct reply_info) - sizeof(struct rrset_ref));
if(!vq->chase_reply)
return NULL;
vq->chase_reply->rrsets = region_alloc_init(qstate->region,
vq->orig_msg->rep->rrsets, sizeof(struct ub_packed_rrset_key*)
* vq->orig_msg->rep->rrset_count);
if(!vq->chase_reply->rrsets)
return NULL;
vq->cname_skip = 0;
return vq;
}
@ -170,12 +180,14 @@ val_error(struct module_qstate* qstate, int id)
* REFUSED, etc.)
*
* @param qstate: query state.
* @param vq: validator query state.
* @param ret_rc: rcode for this message (if noerror - examine ret_msg).
* @param ret_msg: return msg, can be NULL; look at rcode instead.
* @return true if the response could use validation (although this does not
* mean we can actually validate this response).
*/
static int
needs_validation(struct module_qstate* qstate, struct val_qstate* vq)
needs_validation(struct module_qstate* qstate, int ret_rc,
struct dns_msg* ret_msg)
{
int rcode;
@ -186,20 +198,23 @@ needs_validation(struct module_qstate* qstate, struct val_qstate* vq)
return 0;
}
/* validate unchecked, and re-validate bogus messages */
if (vq->orig_msg->rep->security > sec_status_bogus)
{
verbose(VERB_ALGO, "response has already been validated");
return 0;
}
if(ret_rc != LDNS_RCODE_NOERROR || !ret_msg)
rcode = ret_rc;
else rcode = (int)FLAGS_GET_RCODE(ret_msg->rep->flags);
rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags);
if(rcode != LDNS_RCODE_NOERROR && rcode != LDNS_RCODE_NXDOMAIN) {
verbose(VERB_ALGO, "cannot validate non-answer, rcode %s",
ldns_lookup_by_id(ldns_rcodes, rcode)?
ldns_lookup_by_id(ldns_rcodes, rcode)->name:"??");
return 0;
}
/* validate unchecked, and re-validate bogus messages */
if (ret_msg && ret_msg->rep->security > sec_status_bogus)
{
verbose(VERB_ALGO, "response has already been validated");
return 0;
}
return 1;
}
@ -422,8 +437,9 @@ validate_nodata_response(struct module_env* env, struct val_env* ve,
/* Since we are here, there must be nothing in the ANSWER section to
* validate. */
/* (Note: CNAME/DNAME responses will not directly get here --
* instead they are broken down into individual CNAME/DNAME/final answer
* responses. - TODO this will change though) */
* instead, they are chased down into indiviual CNAME validations,
* and at the end of the cname chain a POSITIVE, or CNAME_NOANSWER
* validation.) */
/* validate the AUTHORITY section */
int has_valid_nsec = 0; /* If true, then the NODATA has been proven.*/
@ -648,6 +664,265 @@ validate_any_response(struct module_env* env, struct val_env* ve,
chase_reply->security = sec_status_secure;
}
/**
* Validate CNAME response, or DNAME+CNAME.
* This is just like a positive proof, except that this is about a
* DNAME+CNAME. Possible wildcard proof.
* Also refuses wildcarded DNAMEs.
*
* Note that by the time this method is called, the process of finding the
* trusted DNSKEY rrset that signs this response must already have been
* completed.
*
* @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 key_entry: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
*/
static void
validate_cname_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
struct key_entry_key* key_entry)
{
uint8_t* wc = NULL;
int wc_NSEC_ok = 0;
int dname_seen = 0;
int nsec3s_seen = 0;
size_t i;
struct ub_packed_rrset_key* s;
enum sec_status sec;
/* validate the ANSWER section - this will be the CNAME (+DNAME) */
for(i=0; i<chase_reply->an_numrrsets; i++) {
s = chase_reply->rrsets[i];
/* Skip the CNAME following a (validated) DNAME.
* Because of the normalization routines in the iterator,
* there will always be an unsigned CNAME following a DNAME
* (unless qtype=DNAME). */
if(dname_seen && ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) {
dname_seen = 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 =
sec_status_secure;
((struct packed_rrset_data*)s->entry.data)->trust =
rrset_trust_validated;
continue;
}
/* Verify the answer rrset */
sec = val_verify_rrset_entry(env, ve, s, key_entry);
/* If the (answer) rrset failed to validate, then this
* message is BAD. */
if(sec != sec_status_secure) {
log_nametypeclass(VERB_ALGO, "Cname response has "
"failed ANSWER rrset: ", s->rk.dname,
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
chase_reply->security = sec_status_bogus;
return;
}
/* Check to see if the rrset is the result of a wildcard
* expansion. If so, an additional check will need to be
* made in the authority section. */
if(!val_rrset_wildcard(s, &wc)) {
log_nametypeclass(VERB_ALGO, "Cname response has "
"inconsistent wildcard sigs: ", s->rk.dname,
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
chase_reply->security = sec_status_bogus;
return;
}
/* Notice a DNAME that should be followed by an unsigned
* CNAME. */
if(qchase->qtype != LDNS_RR_TYPE_DNAME &&
ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) {
dname_seen = 1;
/* we will not follow a wildcarded DNAME because
* its synthesized expansion is underdefined */
if(dname_is_wild(s->rk.dname)) {
log_nametypeclass(VERB_ALGO, "cannot follow "
"wildcard DNAME: ", s->rk.dname,
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
chase_reply->security = sec_status_bogus;
return;
}
}
}
/* validate the AUTHORITY section as well - this will generally be
* the NS rrset (which could be missing, no problem) */
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
chase_reply->ns_numrrsets; i++) {
s = chase_reply->rrsets[i];
sec = val_verify_rrset_entry(env, ve, s, key_entry);
/* If anything in the authority section fails to be secure,
* we have a bad message. */
if(sec != sec_status_secure) {
log_nametypeclass(VERB_ALGO, "Cname response has "
"failed AUTHORITY rrset: ", s->rk.dname,
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
chase_reply->security = sec_status_bogus;
return;
}
/* If this is a positive wildcard response, and we have a
* (just verified) NSEC record, try to use it to 1) prove
* that qname doesn't exist and 2) that the correct wildcard
* was used. */
if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) {
if(val_nsec_proves_positive_wildcard(s, qchase, wc)) {
wc_NSEC_ok = 1;
}
/* if not, continue looking for proof */
}
/* Otherwise, if this is a positive wildcard response and
* we have NSEC3 records */
if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) {
nsec3s_seen = 1;
}
}
/* If this was a positive wildcard response that we haven't already
* 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; */
}
/* If after all this, we still haven't proven the positive wildcard
* response, fail. */
if(wc != NULL && !wc_NSEC_ok) {
verbose(VERB_ALGO, "CNAME response was wildcard "
"expansion and did not prove original data "
"did not exist");
chase_reply->security = sec_status_bogus;
return;
}
verbose(VERB_ALGO, "Successfully validated CNAME response");
chase_reply->security = sec_status_secure;
}
/**
* Validate CNAME NOANSWER response, no more data after a CNAME chain.
* This can be a NODATA or a NAME ERROR case, but not both at the same time.
* We don't know because the rcode has been set to NOERROR by the CNAME.
*
* Note that by the time this method is called, the process of finding the
* trusted DNSKEY rrset that signs this response must already have been
* completed.
*
* @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 key_entry: 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 module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
struct key_entry_key* key_entry)
{
/* Since we are here, there must be nothing in the ANSWER section to
* validate. */
/* validate the AUTHORITY section */
int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/
uint8_t* ce = NULL; /* for wildcard nodata responses. This is the
proven closest encloser. */
uint8_t* wc = NULL; /* for wildcard nodata responses. wildcard nsec */
int nxdomain_valid_nsec = 0; /* if true, namerror has been proven */
int nxdomain_valid_wnsec = 0;
int nsec3s_seen = 0; /* nsec3s seen */
struct ub_packed_rrset_key* s;
enum sec_status sec;
size_t i;
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
chase_reply->ns_numrrsets; i++) {
s = chase_reply->rrsets[i];
sec = val_verify_rrset_entry(env, ve, s, key_entry);
if(sec != sec_status_secure) {
log_nametypeclass(VERB_ALGO, "CNAMEnoanswer response has "
"failed AUTHORITY rrset: ", s->rk.dname,
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
chase_reply->security = sec_status_bogus;
return;
}
/* If we encounter an NSEC record, try to use it to prove
* NODATA.
* This needs to handle the ENT NODATA case. */
if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) {
if(nsec_proves_nodata(s, qchase)) {
nodata_valid_nsec = 1;
if(dname_is_wild(s->rk.dname))
wc = s->rk.dname;
}
if(val_nsec_proves_name_error(s, qchase->qname)) {
ce = nsec_closest_encloser(qchase->qname, s);
nxdomain_valid_nsec = 1;
}
if(val_nsec_proves_no_wc(s, qchase->qname,
qchase->qname_len))
nxdomain_valid_wnsec = 1;
} else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) {
nsec3s_seen = 1;
}
}
/* check to see if we have a wildcard NODATA proof. */
/* The wildcard NODATA is 1 NSEC proving that qname does not exists
* (and also proving what the closest encloser is), and 1 NSEC
* showing the matching wildcard, which must be *.closest_encloser. */
if(wc && !ce)
nodata_valid_nsec = 0;
else if(wc && ce) {
log_assert(dname_is_wild(wc));
/* first label wc is \001*, so remove and compare to ce */
if(query_dname_compare(wc+2, ce) != 0) {
nodata_valid_nsec = 0;
}
}
if(nxdomain_valid_nsec && !nxdomain_valid_wnsec) {
/* name error is missing wildcard denial proof */
nxdomain_valid_nsec = 0;
}
if(nodata_valid_nsec && nxdomain_valid_nsec) {
verbose(VERB_ALGO, "CNAMEnoanswer proves that name exists "
"and not exists, bogus");
chase_reply->security = sec_status_bogus;
return;
}
if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) {
/* TODO handle NSEC3 proof here */
/* and set nodata_valid_nsec=1; if so */
}
if(!nodata_valid_nsec && !nxdomain_valid_nsec) {
verbose(VERB_ALGO, "CNAMEnoanswer response failed to prove "
"status with NSEC/NSEC3");
if(verbosity >= VERB_ALGO)
log_dns_msg("Failed CNAMEnoanswer", qchase, chase_reply);
chase_reply->security = sec_status_bogus;
return;
}
if(nodata_valid_nsec)
verbose(VERB_ALGO, "successfully validated CNAME to a "
"NODATA response.");
else verbose(VERB_ALGO, "successfully validated CNAME to a "
"NAMEERROR response.");
chase_reply->security = sec_status_secure;
}
/**
* Process init state for validator.
* Process the INIT state. First tier responses start in the INIT state.
@ -672,12 +947,9 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
{
uint8_t* lookup_name;
size_t lookup_len;
if(!needs_validation(qstate, vq)) {
qstate->return_rcode = LDNS_RCODE_NOERROR;
qstate->return_msg = vq->orig_msg;
qstate->ext_state[id] = module_finished;
return 0;
}
enum val_classification subtype = val_classify_response(&vq->qchase,
vq->orig_msg->rep, vq->cname_skip);
vq->trust_anchor = anchors_lookup(ve->anchors, vq->qchase.qname,
vq->qchase.qname_len, vq->qchase.qclass);
if(vq->trust_anchor == NULL) {
@ -689,8 +961,8 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
}
/* Determine the signer/lookup name */
val_find_signer(&vq->qchase, vq->chase_reply,
&vq->signer_name, &vq->signer_len);
val_find_signer(subtype, &vq->qchase, vq->orig_msg->rep,
vq->cname_skip, &vq->signer_name, &vq->signer_len);
if(vq->signer_name == NULL) {
lookup_name = vq->qchase.qname;
lookup_len = vq->qchase.qname_len;
@ -699,6 +971,14 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
lookup_len = vq->signer_len;
}
if(vq->cname_skip > 0 || subtype == VAL_CLASS_CNAME) {
/* extract this part of orig_msg into chase_reply for
* the eventual VALIDATE stage */
val_fill_reply(vq->chase_reply, vq->orig_msg->rep,
vq->cname_skip, lookup_name, lookup_len);
log_dns_msg("chased extract", &vq->qchase, vq->chase_reply);
}
vq->key_entry = key_cache_obtain(ve->kcache, lookup_name, lookup_len,
vq->qchase.qclass, qstate->region);
@ -877,7 +1157,8 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
return 1;
}
subtype = val_classify_response(&vq->qchase, vq->chase_reply);
subtype = val_classify_response(&vq->qchase, vq->orig_msg->rep,
vq->cname_skip);
switch(subtype) {
case VAL_CLASS_POSITIVE:
verbose(VERB_ALGO, "Validating a positive response");
@ -899,13 +1180,31 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
case VAL_CLASS_CNAME:
verbose(VERB_ALGO, "Validating a cname response");
validate_cname_response(qstate->env, ve,
&vq->qchase, vq->chase_reply, vq->key_entry);
/*
* TODO special CNAME state or routines
* validate CNAME + wildcard NSEC or DNAME.
* reject wildcarded DNAMEs.
validate_cname_response(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(qstate->env, ve,
&vq->qchase, vq->chase_reply, vq->key_entry);
/*
* TODO special CNAME state or routines
* see if nodata or noerror can be proven.
* but not both.
validate_cnamenoanswer_response(vq->qchase,
vq->chase_reply, vq->key_entry);
*/
break;
case VAL_CLASS_ANY:
verbose(VERB_ALGO, "Validating a positive ANY "
"response");
@ -935,21 +1234,56 @@ static int
processFinished(struct module_qstate* qstate, struct val_qstate* vq,
struct val_env* ve, int id)
{
/* TODO CNAME query restarts */
enum val_classification subtype = val_classify_response(&vq->qchase,
vq->orig_msg->rep, vq->cname_skip);
/* store overall validation result in orig_msg */
if(vq->cname_skip == 0)
vq->orig_msg->rep->security = vq->chase_reply->security;
else {
/* use the lowest security status as end result. */
if(vq->chase_reply->security < vq->orig_msg->rep->security)
vq->orig_msg->rep->security =
vq->chase_reply->security;
}
if(vq->chase_reply->security != sec_status_bogus &&
subtype == VAL_CLASS_CNAME) {
/* chase the CNAME; process next part of the message */
if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep,
&vq->cname_skip)) {
verbose(VERB_ALGO, "validator: failed to chase CNAME");
vq->orig_msg->rep->security = sec_status_bogus;
} else {
/* restart process for new qchase at cname_skip */
log_query_info(VERB_DETAIL, "validator: chased to",
&vq->qchase);
vq->chase_reply->security = sec_status_unchecked;
vq->state = VAL_INIT_STATE;
return 1;
}
}
if(vq->orig_msg->rep->security == sec_status_secure) {
/* Do not store the validated status of the dropped RRsets.
* (only secure is reused). These rrsets are apparantly
* added on maliciously, or are unsigned additional data */
val_dump_nonsecure(vq->orig_msg->rep);
}
/* if the result is bogus - set message ttl to bogus ttl to avoid
* endless bogus revalidation */
if(vq->chase_reply->security == sec_status_bogus) {
vq->chase_reply->ttl = time(0) + ve->bogus_ttl;
if(vq->orig_msg->rep->security == sec_status_bogus) {
vq->orig_msg->rep->ttl = time(0) + ve->bogus_ttl;
}
/* store results in cache */
if(!dns_cache_store(qstate->env, &vq->qchase, vq->chase_reply, 0)) {
if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo,
vq->orig_msg->rep, 0)) {
log_err("out of memory caching validator results");
}
qstate->return_rcode = LDNS_RCODE_NOERROR;
qstate->return_msg = vq->orig_msg;
qstate->return_msg->rep = vq->chase_reply;
qstate->ext_state[id] = module_finished;
return 0;
}
@ -1019,16 +1353,9 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id,
if(event == module_event_moddone) {
/* check if validation is needed */
verbose(VERB_ALGO, "validator: nextmodule returned");
if(qstate->return_rcode != LDNS_RCODE_NOERROR &&
qstate->return_rcode != LDNS_RCODE_NXDOMAIN &&
qstate->return_rcode != LDNS_RCODE_YXDOMAIN) {
verbose(VERB_ALGO, "rcode for error not validated");
qstate->ext_state[id] = module_finished;
return;
}
if(qstate->query_flags & BIT_CD) {
if(!needs_validation(qstate, qstate->return_rcode,
qstate->return_msg)) {
/* no need to validate this */
verbose(VERB_ALGO, "CD bit set: query not validated");
qstate->ext_state[id] = module_finished;
return;
}
@ -1175,7 +1502,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
goto return_bogus;
}
subtype = val_classify_response(qinfo, msg->rep);
subtype = val_classify_response(qinfo, msg->rep, 0);
if(subtype == VAL_CLASS_POSITIVE) {
struct ub_packed_rrset_key* ds;
enum sec_status sec;

View file

@ -113,10 +113,21 @@ struct val_qstate {
* The chased reply, extract from original message. Can be:
* o CNAME
* o DNAME + CNAME
* o answer plus authority, additional (nsecs).
* o answer
* plus authority, additional (nsecs) that have same signature.
*/
struct reply_info* chase_reply;
/**
* The cname skip value; the number of rrsets that have been skipped
* due to chasing cnames. This is the offset into the
* orig_msg->rep->rrsets array, into the answer section.
* starts at 0 - for the full original message.
* if it is >0 - qchase followed the cname, chase_reply setup to be
* that message and relevant authority rrsets.
*/
size_t cname_skip;
/** the trust anchor rrset */
struct trust_anchor* trust_anchor;