- 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.
(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);
dname_test_isroot();
dname_test_removelabel();
dname_test_sigcount();
+ dname_test_iswild();
+ dname_test_canoncmp();
ldns_buffer_free(buff);
}
}
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);
+}
* @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);
*/
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 */
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;
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;
}
/**
* 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)
{
/* 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
* 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.");
/* 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;
}
* 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);
/** 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 */
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 "