nsec work, canonical compare routine and tests.

git-svn-id: file:///svn/unbound/trunk@530 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2007-08-17 14:25:42 +00:00
parent cedeaa8316
commit 453df0c66c
7 changed files with 525 additions and 15 deletions

View File

@ -2,6 +2,7 @@
- work on DS2KE routine.
- val_nsec.c for validator NSEC proofs.
- unit test for NSEC bitmap reading.
- dname iswild and canonical_compare with unit tests.
16 August 2007: Wouter
- DS sig unit test.

View File

@ -482,6 +482,235 @@ dname_test_sigcount()
(uint8_t*)"\001*\003www\007example\003xom\000") == 3);
}
/** test dname_is_wild routine */
static void
dname_test_iswild()
{
unit_assert( !dname_is_wild((uint8_t*)"\000") );
unit_assert( dname_is_wild((uint8_t*)"\001*\000") );
unit_assert( !dname_is_wild((uint8_t*)"\003net\000") );
unit_assert( dname_is_wild((uint8_t*)"\001*\003net\000") );
}
/** test dname_canonical_compare */
static void
dname_test_canoncmp()
{
/* equality */
unit_assert( dname_canonical_compare(
(uint8_t*)"\000",
(uint8_t*)"\000"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\003net\000",
(uint8_t*)"\003net\000"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\007example\003net\000",
(uint8_t*)"\007example\003net\000"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\004test\007example\003net\000",
(uint8_t*)"\004test\007example\003net\000"
) == 0);
/* subdomains */
unit_assert( dname_canonical_compare(
(uint8_t*)"\003com",
(uint8_t*)"\000"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\000",
(uint8_t*)"\003com"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\007example\003com",
(uint8_t*)"\003com"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\003com",
(uint8_t*)"\007example\003com"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\007example\003com",
(uint8_t*)"\000"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\000",
(uint8_t*)"\007example\003com"
) == -1);
/* compare rightmost label */
unit_assert( dname_canonical_compare(
(uint8_t*)"\003com",
(uint8_t*)"\003net"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\003net",
(uint8_t*)"\003com"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\003net",
(uint8_t*)"\003org"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\007example\003net",
(uint8_t*)"\003org"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\003org",
(uint8_t*)"\007example\003net"
) == 1);
/* label length makes a difference; but only if rest is equal */
unit_assert( dname_canonical_compare(
(uint8_t*)"\004neta",
(uint8_t*)"\003net"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\002ne",
(uint8_t*)"\004neta"
) == -1);
/* label content */
unit_assert( dname_canonical_compare(
(uint8_t*)"\003aag\007example\003net",
(uint8_t*)"\003bla\007example\003net"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\003bla\007example\003net",
(uint8_t*)"\003aag\007example\003net"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\003bla\003aag\007example\003net",
(uint8_t*)"\003aag\003bla\007example\003net"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\02sn\003opt\003aag\007example\003net",
(uint8_t*)"\02sn\003opt\003bla\007example\003net"
) == -1);
/* lowercase during compare */
unit_assert( dname_canonical_compare(
(uint8_t*)"\003bLa\007examPLe\003net",
(uint8_t*)"\003bla\007eXAmple\003nET"
) == 0);
/* example from 4034 */
/* example a.example yljkjljk.a.example Z.a.example zABC.a.EXAMPLE
z.example \001.z.example *.z.example \200.z.example */
unit_assert( dname_canonical_compare(
(uint8_t*)"",
(uint8_t*)"\007example"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\007example",
(uint8_t*)"\001a\007example"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001a\007example",
(uint8_t*)"\010yljkjljk\001a\007example"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\010yljkjljk\001a\007example",
(uint8_t*)"\001Z\001a\007example"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001Z\001a\007example",
(uint8_t*)"\004zABC\001a\007EXAMPLE"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\004zABC\001a\007EXAMPLE",
(uint8_t*)"\001z\007example"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001z\007example",
(uint8_t*)"\001\001\001z\007example"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001\001\001z\007example",
(uint8_t*)"\001*\001z\007example"
) == -1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001*\001z\007example",
(uint8_t*)"\001\200\001z\007example"
) == -1);
/* same example in reverse */
unit_assert( dname_canonical_compare(
(uint8_t*)"\007example",
(uint8_t*)""
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001a\007example",
(uint8_t*)"\007example"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\010yljkjljk\001a\007example",
(uint8_t*)"\001a\007example"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001Z\001a\007example",
(uint8_t*)"\010yljkjljk\001a\007example"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\004zABC\001a\007EXAMPLE",
(uint8_t*)"\001Z\001a\007example"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001z\007example",
(uint8_t*)"\004zABC\001a\007EXAMPLE"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001\001\001z\007example",
(uint8_t*)"\001z\007example"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001*\001z\007example",
(uint8_t*)"\001\001\001z\007example"
) == 1);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001\200\001z\007example",
(uint8_t*)"\001*\001z\007example"
) == 1);
/* same example for equality */
unit_assert( dname_canonical_compare(
(uint8_t*)"\007example",
(uint8_t*)"\007example"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001a\007example",
(uint8_t*)"\001a\007example"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\010yljkjljk\001a\007example",
(uint8_t*)"\010yljkjljk\001a\007example"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001Z\001a\007example",
(uint8_t*)"\001Z\001a\007example"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\004zABC\001a\007EXAMPLE",
(uint8_t*)"\004zABC\001a\007EXAMPLE"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001z\007example",
(uint8_t*)"\001z\007example"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001\001\001z\007example",
(uint8_t*)"\001\001\001z\007example"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001*\001z\007example",
(uint8_t*)"\001*\001z\007example"
) == 0);
unit_assert( dname_canonical_compare(
(uint8_t*)"\001\200\001z\007example",
(uint8_t*)"\001\200\001z\007example"
) == 0);
}
void dname_test()
{
ldns_buffer* buff = ldns_buffer_new(65800);
@ -498,5 +727,7 @@ void dname_test()
dname_test_isroot();
dname_test_removelabel();
dname_test_sigcount();
dname_test_iswild();
dname_test_canoncmp();
ldns_buffer_free(buff);
}

View File

@ -621,3 +621,107 @@ dname_signame_label_count(uint8_t* dname)
}
return count;
}
int
dname_is_wild(uint8_t* dname)
{
return (dname[0] == 1 && dname[1] == '*');
}
/**
* Compare labels in memory, lowercase while comparing.
* Returns canonical order for labels. If all is equal, the
* shortest is first.
*
* @param p1: label 1
* @param len1: length of label 1.
* @param p2: label 2
* @param len2: length of label 2.
* @return: 0, -1, +1 comparison result.
*/
static int
memcanoncmp(uint8_t* p1, uint8_t len1, uint8_t* p2, uint8_t len2)
{
uint8_t min = (len1<len2)?len1:len2;
int c = memlowercmp(p1, p2, min);
if(c != 0)
return c;
/* equal, see who is shortest */
if(len1 < len2)
return -1;
if(len1 > len2)
return 1;
return 0;
}
int
dname_canon_lab_cmp(uint8_t* d1, int labs1, uint8_t* d2, int labs2, int* mlabs)
{
/* like dname_lab_cmp, but with different label comparison,
* empty character sorts before \000.
* So ylyly is before z. */
uint8_t len1, len2;
int atlabel = labs1;
int lastmlabs;
int lastdiff = 0;
int c;
/* first skip so that we compare same label. */
if(labs1 > labs2) {
while(atlabel > labs2) {
len1 = *d1++;
d1 += len1;
atlabel--;
}
log_assert(atlabel == labs2);
} else if(labs1 < labs2) {
atlabel = labs2;
while(atlabel > labs1) {
len2 = *d2++;
d2 += len2;
atlabel--;
}
log_assert(atlabel == labs1);
}
lastmlabs = atlabel+1;
/* now at same label in d1 and d2, atlabel */
/* www.example.com. */
/* 4 3 2 1 atlabel number */
/* repeat until at root label (which is always the same) */
while(atlabel > 1) {
len1 = *d1++;
len2 = *d2++;
if((c=memcanoncmp(d1, len1, d2, len2)) != 0) {
if(c<0)
lastdiff = -1;
else lastdiff = 1;
lastmlabs = atlabel;
}
d1 += len1;
d2 += len2;
atlabel--;
}
/* last difference atlabel number, so number of labels matching,
* at the right side, is one less. */
*mlabs = lastmlabs-1;
if(lastdiff == 0) {
/* all labels compared were equal, check if one has more
* labels, so that example.com. > com. */
if(labs1 > labs2)
return 1;
else if(labs1 < labs2)
return -1;
}
return lastdiff;
}
int
dname_canonical_compare(uint8_t* d1, uint8_t* d2)
{
int labs1, labs2, m;
labs1 = dname_count_labels(d1);
labs2 = dname_count_labels(d2);
return dname_canon_lab_cmp(d1, labs1, d2, labs2, &m);
}

View File

@ -167,7 +167,7 @@ int dname_count_size_labels(uint8_t* dname, size_t* size);
* @param labs1: number of labels in first dname.
* @param d2: second dname. pointer to uncompressed wireformat.
* @param labs2: number of labels in second dname.
* @param mlabs: number of labels that matched exactly.
* @param mlabs: number of labels that matched exactly (the shared topdomain).
* @return: 0 for equal, -1 smaller, or +1 d1 larger than d2.
*/
int dname_lab_cmp(uint8_t* d1, int labs1, uint8_t* d2, int labs2, int* mlabs);
@ -249,4 +249,35 @@ void dname_remove_labels(uint8_t** dname, size_t* len, int n);
*/
int dname_signame_label_count(uint8_t* dname);
/**
* Return true if the label is a wildcard, *.example.com.
* @param dname: valid uncompressed wireformat.
* @return true if wildcard, or false.
*/
int dname_is_wild(uint8_t* dname);
/**
* Compare dnames, Canonical in rfc4034 sense, but by label.
* Such that zone contents follows zone apex.
*
* @param d1: first dname. pointer to uncompressed wireformat.
* @param labs1: number of labels in first dname.
* @param d2: second dname. pointer to uncompressed wireformat.
* @param labs2: number of labels in second dname.
* @param mlabs: number of labels that matched exactly (the shared topdomain).
* @return: 0 for equal, -1 smaller, or +1 d1 larger than d2.
*/
int dname_canon_lab_cmp(uint8_t* d1, int labs1, uint8_t* d2, int labs2,
int* mlabs);
/**
* Canonical dname compare. Takes care of counting labels.
* Per rfc 4034 canonical order.
*
* @param d1: first dname. pointer to uncompressed wireformat.
* @param d2: second dname. pointer to uncompressed wireformat.
* @return: 0 for equal, -1 smaller, or +1 d1 larger than d2.
*/
int dname_canonical_compare(uint8_t* d1, uint8_t* d2);
#endif /* UTIL_DATA_DNAME_H */

View File

@ -71,7 +71,7 @@ nsec_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type)
size_t mybyte = type_low>>3;
if(winlen <= mybyte)
return 0; /* window too short */
return bitmap[mybyte] & masks[type_low&0x7];
return (int)(bitmap[mybyte] & masks[type_low&0x7]);
} else {
/* not the window we are looking for */
bitmap += winlen;
@ -107,7 +107,36 @@ nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type)
len = dname_valid(d->rr_data[0]+2, d->rr_len[0]-2);
if(!len)
return 0;
nsec_has_type_rdata(d->rr_data[0]+2+len, d->rr_len[0]-2-len, type);
return nsec_has_type_rdata(d->rr_data[0]+2+len,
d->rr_len[0]-2-len, type);
}
/**
* Get next owner name from nsec record
* @param nsec: the nsec RRset.
* If there are multiple RRs, then this will only return one of them.
* @param nm: the next name is returned.
* @param ln: length of nm is returned.
* @return false on a bad NSEC RR (too short, malformed dname).
*/
static int
nsec_get_next(struct ub_packed_rrset_key* nsec, uint8_t** nm, size_t* ln)
{
struct packed_rrset_data* d = (struct packed_rrset_data*)nsec->
entry.data;
if(!d || d->count == 0 || d->rr_len[0] < 2+1) {
*nm = 0;
*ln = 0;
return 0;
}
*nm = d->rr_data[0]+2;
*ln = dname_valid(*nm, d->rr_len[0]-2);
if(!*ln) {
*nm = 0;
*ln = 0;
return 0;
}
return 1;
}
/**
@ -119,7 +148,7 @@ nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type)
* insecure if it proves it is not a delegation point.
* or bogus if something was wrong.
*/
enum sec_status
static enum sec_status
val_nsec_proves_no_ds(struct ub_packed_rrset_key* nsec,
struct query_info* qinfo)
{
@ -128,17 +157,34 @@ val_nsec_proves_no_ds(struct ub_packed_rrset_key* nsec,
/* this proof may also work if qname is a subdomain */
log_assert(query_dname_compare(nsec->rk.dname, qinfo->qname) == 0);
return sec_status_bogus;
if(nsec_has_type(nsec, LDNS_RR_TYPE_SOA) ||
nsec_has_type(nsec, LDNS_RR_TYPE_DS)) {
/* SOA present means that this is the NSEC from the child,
* not the parent (so it is the wrong one).
* DS present means that there should have been a positive
* response to the DS query, so there is something wrong. */
return sec_status_bogus;
}
if(!nsec_has_type(nsec, LDNS_RR_TYPE_NS)) {
/* If there is no NS at this point at all, then this
* doesn't prove anything one way or the other. */
return sec_status_insecure;
}
/* Otherwise, this proves no DS. */
return sec_status_secure;
}
enum sec_status
val_nsec_prove_nodata_ds(struct module_env* env, struct val_env* ve,
val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve,
struct query_info* qinfo, struct reply_info* rep,
struct key_entry_key* kkey, uint32_t* proof_ttl)
{
struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns(
rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC,
qinfo->qclass);
enum sec_status sec;
size_t i;
/* If we have a NSEC at the same name, it must prove one
* of two things
@ -146,8 +192,7 @@ val_nsec_prove_nodata_ds(struct module_env* env, struct val_env* ve,
* 1) this is a delegation point and there is no DS
* 2) this is not a delegation point */
if(nsec) {
enum sec_status sec = val_verify_rrset_entry(env, ve, nsec,
kkey);
sec = val_verify_rrset_entry(env, ve, nsec, kkey);
if(sec != sec_status_secure) {
verbose(VERB_ALGO, "NSEC RRset for the "
"referral did not verify.");
@ -171,8 +216,91 @@ val_nsec_prove_nodata_ds(struct module_env* env, struct val_env* ve,
/* Otherwise, there is no NSEC at qname. This could be an ENT.
* (ENT=empty non terminal). If not, this is broken. */
/* verify NSEC rrsets in auth section, call */
/* ValUtils.nsecProvesNodata, if so: NULL entry */
/* verify NSEC rrsets in auth section */
for(i=rep->an_numrrsets; i < rep->an_numrrsets+rep->ns_numrrsets;
i++) {
if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC))
continue;
sec = val_verify_rrset_entry(env, ve, rep->rrsets[i], kkey);
if(sec != sec_status_secure) {
verbose(VERB_ALGO, "NSEC for empty non-terminal "
"did not verify.");
return sec_status_bogus;
}
if(nsec_proves_nodata(rep->rrsets[i], qinfo)) {
verbose(VERB_ALGO, "NSEC for empty non-terminal "
"proved no DS.");
return sec_status_secure;
}
}
return sec_status_bogus;
/* NSEC proof did not conlusively point to DS or no DS */
return sec_status_unchecked;
}
int nsec_proves_nodata(struct ub_packed_rrset_key* nsec,
struct query_info* qinfo)
{
if(query_dname_compare(nsec->rk.dname, qinfo->qname) != 0) {
uint8_t* nm;
size_t ln;
/* wildcard checking. */
/* If this is a wildcard NSEC, make sure that a) it was
* possible to have generated qname from the wildcard and
* b) the type map does not contain qtype. Note that this
* does NOT prove that this wildcard was the applicable
* wildcard. */
if(dname_is_wild(nsec->rk.dname)) {
/* the purported closest encloser. */
uint8_t* ce = nsec->rk.dname;
size_t ce_len = nsec->rk.dname_len;
dname_remove_label(&ce, &ce_len);
/* The qname must be a strict subdomain of the
* closest encloser, and the qtype must be absent
* from the type map. */
if(!dname_strict_subdomain_c(qinfo->qname, ce) ||
nsec_has_type(nsec, qinfo->qtype)) {
return 0;
}
return 1;
}
/* empty-non-terminal checking. */
/* If the nsec is proving that qname is an ENT, the nsec owner
* will be less than qname, and the next name will be a child
* domain of the qname. */
if(!nsec_get_next(nsec, &nm, &ln))
return 0; /* bad nsec */
if(dname_strict_subdomain_c(nm, qinfo->qname) &&
dname_canonical_compare(nsec->rk.dname,
qinfo->qname) < 0) {
return 1; /* proves ENT */
}
/* Otherwise, this NSEC does not prove ENT, so it does not
* prove NODATA. */
return 0;
}
/* If the qtype exists, then we should have gotten it. */
if(nsec_has_type(nsec, qinfo->qtype)) {
return 0;
}
/* if the name is a CNAME node, then we should have gotten the CNAME*/
if(nsec_has_type(nsec, LDNS_RR_TYPE_CNAME)) {
return 0;
}
/* If an NS set exists at this name, and NOT a SOA (so this is a
* zone cut, not a zone apex), then we should have gotten a
* referral (or we just got the wrong NSEC). */
if(nsec_has_type(nsec, LDNS_RR_TYPE_NS) &&
!nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) {
return 0;
}
return 1;
}

View File

@ -67,9 +67,9 @@ struct key_entry_key;
* SECURE: proved absence of DS.
* INSECURE: proved that this was not a delegation point.
* BOGUS: crypto bad, or no absence of DS proven.
* UNCHECKED: there was no way to prove anything (no nsecs, unknown algo).
* UNCHECKED: there was no way to prove anything (no NSECs, unknown algo).
*/
enum sec_status val_nsec_prove_nodata_ds(struct module_env* env,
enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env,
struct val_env* ve, struct query_info* qinfo,
struct reply_info* rep, struct key_entry_key* kkey,
uint32_t* proof_ttl);
@ -77,4 +77,18 @@ enum sec_status val_nsec_prove_nodata_ds(struct module_env* env,
/** Unit test call to test function for nsec typemap check */
int unitest_nsec_has_type_rdata(char* bitmap, size_t len, uint16_t type);
/**
* Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
* handle the empty non-terminal (ENT) case and partially handle the
* wildcard case. If the ownername of 'nsec' is a wildcard, the validator
* must still be provided proof that qname did not directly exist and that
* the wildcard is, in fact, *.closest_encloser.
*
* @param nsec: the nsec record to check against.
* @param qinfo: the query info.
* @return true if NSEC proves this.
*/
int nsec_proves_nodata(struct ub_packed_rrset_key* nsec,
struct query_info* qinfo);
#endif /* VALIDATOR_VAL_NSEC_H */

View File

@ -690,8 +690,9 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
uint32_t proof_ttl = 0;
/* Try to prove absence of the DS with NSEC */
enum sec_status sec = val_nsec_prove_nodata_ds(qstate->env, ve,
qinfo, msg->rep, vq->key_entry, &proof_ttl);
enum sec_status sec = val_nsec_prove_nodata_dsreply(
qstate->env, ve, qinfo, msg->rep, vq->key_entry,
&proof_ttl);
switch(sec) {
case sec_status_secure:
verbose(VERB_ALGO, "NSEC RRset for the "