From: Wouter Wijngaards Date: Fri, 17 Aug 2007 14:25:42 +0000 (+0000) Subject: nsec work, canonical compare routine and tests. X-Git-Tag: release-0.5~111 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=453df0c66c156c0c780608f7e81a2e70b7ee8016;p=thirdparty%2Funbound.git nsec work, canonical compare routine and tests. git-svn-id: file:///svn/unbound/trunk@530 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 97a37ffaf..909e04c55 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -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. diff --git a/testcode/unitdname.c b/testcode/unitdname.c index 25e33e707..f8662f7e6 100644 --- a/testcode/unitdname.c +++ b/testcode/unitdname.c @@ -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); } diff --git a/util/data/dname.c b/util/data/dname.c index 3b42ee405..2d3289831 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -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) + 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); +} diff --git a/util/data/dname.h b/util/data/dname.h index 26d54bc33..f2b299877 100644 --- a/util/data/dname.h +++ b/util/data/dname.h @@ -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 */ diff --git a/validator/val_nsec.c b/validator/val_nsec.c index 6bf997f46..9eac00bed 100644 --- a/validator/val_nsec.c +++ b/validator/val_nsec.c @@ -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; + } + } + + /* 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 sec_status_bogus; + return 1; } diff --git a/validator/val_nsec.h b/validator/val_nsec.h index 7e9a14473..aee1483be 100644 --- a/validator/val_nsec.h +++ b/validator/val_nsec.h @@ -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 */ diff --git a/validator/validator.c b/validator/validator.c index 7f12cbe79..ae4f2ed14 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -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 "