mirror of
https://github.com/mollyim/unbound.git
synced 2025-05-20 09:07:48 +01:00
CNAME validation.
git-svn-id: file:///svn/unbound/trunk@542 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
parent
a7001366a1
commit
b54a0400ab
5 changed files with 621 additions and 52 deletions
|
@ -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().
|
||||
|
|
|
@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue