/** Get signer name from RRSIG */ static void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, size_t* slen) { /* RRSIG rdata is not allowed to be compressed, it is stored * uncompressed in memory as well, so return a ptr to the name */ if(len < 21) { /* too short RRSig: * short, byte, byte, long, long, long, short, "." is * 2 1 1 4 4 4 2 1 = 19 * and a skip of 18 bytes to the name. * +2 for the rdatalen is 21 bytes len for root label */ *sname = NULL; *slen = 0; return; } data += 20; /* skip the fixed size bits */ len -= 20; *slen = dname_valid(data, len); if(!*slen) { /* bad dname in this rrsig. */ *sname = NULL; return; } *sname = data; }
/** Test dname_valid */ static void dname_test_valid(void) { unit_show_func("util/data/dname.c", "dname_valid"); unit_assert( dname_valid( (uint8_t*)"\003www\007example\003com", 255) == 17); unit_assert( dname_valid((uint8_t*)"", 255) == 1); unit_assert( dname_valid( (uint8_t*) "\020a1cdef5555544444" "\020a2cdef5555544444" "\020a3cdef5555544444" "\020a4cdef5555544444" "\020a5cdef5555544444" "\020a6cdef5555544444" "\020a7cdef5555544444" "\020a8cdef5555544444" "\020a9cdef5555544444" "\020aAcdef5555544444" "\020aBcdef5555544444" "\020aCcdef5555544444" "\020aDcdef5555544444" "\020aEcdef5555544444" /* 238 up to here */ "\007aabbccd" /* 246 up to here */ "\007example\000" /* 255 to here */ , 255) == 255); unit_assert( dname_valid( (uint8_t*) "\020a1cdef5555544444" "\020a2cdef5555544444" "\020a3cdef5555544444" "\020a4cdef5555544444" "\020a5cdef5555544444" "\020a6cdef5555544444" "\020a7cdef5555544444" "\020a8cdef5555544444" "\020a9cdef5555544444" "\020aAcdef5555544444" "\020aBcdef5555544444" "\020aCcdef5555544444" "\020aDcdef5555544444" "\020aEcdef5555544444" /* 238 up to here */ "\007aabbccd" /* 246 up to here */ "\010exampleX\000" /* 256 to here */ , 4096) == 0); }
/** 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(!dname_valid(d->rr_data[i]+2+18, d->rr_len[i]-2-18)) continue; if(query_dname_compare(name, d->rr_data[i]+2+18) == 0) { return 1; } } } return 0; }
int delegpt_rrset_add_ns(struct delegpt* dp, struct regional* region, struct ub_packed_rrset_key* ns_rrset, int lame) { struct packed_rrset_data* nsdata = (struct packed_rrset_data*) ns_rrset->entry.data; size_t i; if(nsdata->security == sec_status_bogus) dp->bogus = 1; for(i=0; i<nsdata->count; i++) { if(nsdata->rr_len[i] < 2+1) continue; /* len + root label */ if(dname_valid(nsdata->rr_data[i]+2, nsdata->rr_len[i]-2) != (size_t)ldns_read_uint16(nsdata->rr_data[i])) continue; /* bad format */ /* add rdata of NS (= wirefmt dname), skip rdatalen bytes */ if(!delegpt_add_ns(dp, region, nsdata->rr_data[i]+2, lame)) return 0; } return 1; }
/** * Check if right hand name in NSEC is within zone * @param rrset: the NSEC rrset * @param zonename: the zone name. * @return true if BAD. */ static int sanitize_nsec_is_overreach(struct rrset_parse* rrset, uint8_t* zonename) { struct rr_parse* rr; uint8_t* rhs; size_t len; log_assert(rrset->type == LDNS_RR_TYPE_NSEC); for(rr = rrset->rr_first; rr; rr = rr->next) { rhs = rr->ttl_data+4+2; len = ldns_read_uint16(rr->ttl_data+4); if(!dname_valid(rhs, len)) { /* malformed domain name in rdata */ return 1; } if(!dname_subdomain_c(rhs, zonename)) { /* overreaching */ return 1; } } /* all NSEC RRs OK */ return 0; }
void get_cname_target(struct ub_packed_rrset_key* rrset, uint8_t** dname, size_t* dname_len) { struct packed_rrset_data* d; size_t len; if(ntohs(rrset->rk.type) != LDNS_RR_TYPE_CNAME && ntohs(rrset->rk.type) != LDNS_RR_TYPE_DNAME) return; d = (struct packed_rrset_data*)rrset->entry.data; if(d->count < 1) return; if(d->rr_len[0] < 3) /* at least rdatalen + 0byte root label */ return; len = sldns_read_uint16(d->rr_data[0]); if(len != d->rr_len[0] - sizeof(uint16_t)) return; if(dname_valid(d->rr_data[0]+sizeof(uint16_t), len) != len) return; *dname = d->rr_data[0]+sizeof(uint16_t); *dname_len = len; }
/** * Remove NSEC records between start and end points. * By walking the tree, the tree is sorted canonically. * @param neg: negative cache. * @param zone: the zone * @param el: element to start walking at. * @param nsec: the nsec record with the end point */ static void wipeout(struct val_neg_cache* neg, struct val_neg_zone* zone, struct val_neg_data* el, struct ub_packed_rrset_key* nsec) { struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> entry.data; uint8_t* end; size_t end_len; int end_labs, m; rbnode_t* walk, *next; struct val_neg_data* cur; uint8_t buf[257]; /* get endpoint */ if(!d || d->count == 0 || d->rr_len[0] < 2+1) return; if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC) { end = d->rr_data[0]+2; end_len = dname_valid(end, d->rr_len[0]-2); end_labs = dname_count_labels(end); } else { /* NSEC3 */ if(!nsec3_get_nextowner_b32(nsec, 0, buf, sizeof(buf))) return; end = buf; end_labs = dname_count_size_labels(end, &end_len); } /* sanity check, both owner and end must be below the zone apex */ if(!dname_subdomain_c(el->name, zone->name) || !dname_subdomain_c(end, zone->name)) return; /* detect end of zone NSEC ; wipe until the end of zone */ if(query_dname_compare(end, zone->name) == 0) { end = NULL; } walk = rbtree_next(&el->node); while(walk && walk != RBTREE_NULL) { cur = (struct val_neg_data*)walk; /* sanity check: must be larger than start */ if(dname_canon_lab_cmp(cur->name, cur->labs, el->name, el->labs, &m) <= 0) { /* r == 0 skip original record. */ /* r < 0 too small! */ walk = rbtree_next(walk); continue; } /* stop at endpoint, also data at empty nonterminals must be * removed (no NSECs there) so everything between * start and end */ if(end && dname_canon_lab_cmp(cur->name, cur->labs, end, end_labs, &m) >= 0) { break; } /* this element has to be deleted, but we cannot do it * now, because we are walking the tree still ... */ /* get the next element: */ next = rbtree_next(walk); /* now delete the original element, this may trigger * rbtree rebalances, but really, the next element is * the one we need. * But it may trigger delete of other data and the * entire zone. However, if that happens, this is done * by deleting the *parents* of the element for deletion, * and maybe also the entire zone if it is empty. * But parents are smaller in canonical compare, thus, * if a larger element exists, then it is not a parent, * it cannot get deleted, the zone cannot get empty. * If the next==NULL, then zone can be empty. */ if(cur->in_use) neg_delete_data(neg, cur); walk = next; } }
/** * Canonicalize Rdata in buffer. * @param buf: buffer at position just after the rdata. * @param rrset: rrset with type. * @param len: length of the rdata (including rdatalen uint16). */ static void canonicalize_rdata(sldns_buffer* buf, struct ub_packed_rrset_key* rrset, size_t len) { uint8_t* datstart = sldns_buffer_current(buf)-len+2; switch(ntohs(rrset->rk.type)) { case LDNS_RR_TYPE_NXT: case LDNS_RR_TYPE_NS: case LDNS_RR_TYPE_MD: case LDNS_RR_TYPE_MF: case LDNS_RR_TYPE_CNAME: case LDNS_RR_TYPE_MB: case LDNS_RR_TYPE_MG: case LDNS_RR_TYPE_MR: case LDNS_RR_TYPE_PTR: case LDNS_RR_TYPE_DNAME: /* type only has a single argument, the name */ query_dname_tolower(datstart); return; case LDNS_RR_TYPE_MINFO: case LDNS_RR_TYPE_RP: case LDNS_RR_TYPE_SOA: /* two names after another */ query_dname_tolower(datstart); query_dname_tolower(datstart + dname_valid(datstart, len-2)); return; case LDNS_RR_TYPE_RT: case LDNS_RR_TYPE_AFSDB: case LDNS_RR_TYPE_KX: case LDNS_RR_TYPE_MX: /* skip fixed part */ if(len < 2+2+1) /* rdlen, skiplen, 1byteroot */ return; datstart += 2; query_dname_tolower(datstart); return; case LDNS_RR_TYPE_SIG: /* downcase the RRSIG, compat with BIND (kept it from SIG) */ case LDNS_RR_TYPE_RRSIG: /* skip fixed part */ if(len < 2+18+1) return; datstart += 18; query_dname_tolower(datstart); return; case LDNS_RR_TYPE_PX: /* skip, then two names after another */ if(len < 2+2+1) return; datstart += 2; query_dname_tolower(datstart); query_dname_tolower(datstart + dname_valid(datstart, len-2-2)); return; case LDNS_RR_TYPE_NAPTR: if(len < 2+4) return; len -= 2+4; datstart += 4; if(len < (size_t)datstart[0]+1) /* skip text field */ return; len -= (size_t)datstart[0]+1; datstart += (size_t)datstart[0]+1; if(len < (size_t)datstart[0]+1) /* skip text field */ return; len -= (size_t)datstart[0]+1; datstart += (size_t)datstart[0]+1; if(len < (size_t)datstart[0]+1) /* skip text field */ return; len -= (size_t)datstart[0]+1; datstart += (size_t)datstart[0]+1; if(len < 1) /* check name is at least 1 byte*/ return; query_dname_tolower(datstart); return; case LDNS_RR_TYPE_SRV: /* skip fixed part */ if(len < 2+6+1) return; datstart += 6; query_dname_tolower(datstart); return; /* do not canonicalize NSEC rdata name, compat with * from bind 9.4 signer, where it does not do so */ case LDNS_RR_TYPE_NSEC: /* type starts with the name */ case LDNS_RR_TYPE_HINFO: /* not downcased */ /* A6 not supported */ default: /* nothing to do for unknown types */ return; } }
/** * Compare two RRs in the same RRset and determine their relative * canonical order. * @param rrset: the rrset in which to perform compares. * @param i: first RR to compare * @param j: first RR to compare * @return 0 if RR i== RR j, -1 if <, +1 if >. */ static int canonical_compare(struct ub_packed_rrset_key* rrset, size_t i, size_t j) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; const sldns_rr_descriptor* desc; uint16_t type = ntohs(rrset->rk.type); size_t minlen; int c; if(i==j) return 0; switch(type) { /* These RR types have only a name as RDATA. * This name has to be canonicalized.*/ case LDNS_RR_TYPE_NS: case LDNS_RR_TYPE_MD: case LDNS_RR_TYPE_MF: case LDNS_RR_TYPE_CNAME: case LDNS_RR_TYPE_MB: case LDNS_RR_TYPE_MG: case LDNS_RR_TYPE_MR: case LDNS_RR_TYPE_PTR: case LDNS_RR_TYPE_DNAME: /* the wireread function has already checked these * dname's for correctness, and this double checks */ if(!dname_valid(d->rr_data[i]+2, d->rr_len[i]-2) || !dname_valid(d->rr_data[j]+2, d->rr_len[j]-2)) return 0; return query_dname_compare(d->rr_data[i]+2, d->rr_data[j]+2); /* These RR types have STR and fixed size rdata fields * before one or more name fields that need canonicalizing, * and after that a byte-for byte remainder can be compared. */ /* type starts with the name; remainder is binary compared */ case LDNS_RR_TYPE_NXT: /* use rdata field formats */ case LDNS_RR_TYPE_MINFO: case LDNS_RR_TYPE_RP: case LDNS_RR_TYPE_SOA: case LDNS_RR_TYPE_RT: case LDNS_RR_TYPE_AFSDB: case LDNS_RR_TYPE_KX: case LDNS_RR_TYPE_MX: case LDNS_RR_TYPE_SIG: /* RRSIG signer name has to be downcased */ case LDNS_RR_TYPE_RRSIG: case LDNS_RR_TYPE_PX: case LDNS_RR_TYPE_NAPTR: case LDNS_RR_TYPE_SRV: desc = sldns_rr_descript(type); log_assert(desc); /* this holds for the types that need canonicalizing */ log_assert(desc->_minimum == desc->_maximum); return canonical_compare_byfield(d, desc, i, j); case LDNS_RR_TYPE_HINFO: /* no longer downcased */ case LDNS_RR_TYPE_NSEC: default: /* For unknown RR types, or types not listed above, * no canonicalization is needed, do binary compare */ /* byte for byte compare, equal means shortest first*/ minlen = d->rr_len[i]-2; if(minlen > d->rr_len[j]-2) minlen = d->rr_len[j]-2; c = memcmp(d->rr_data[i]+2, d->rr_data[j]+2, minlen); if(c!=0) return c; /* rdata equal, shortest is first */ if(d->rr_len[i] < d->rr_len[j]) return -1; if(d->rr_len[i] > d->rr_len[j]) return 1; /* rdata equal, length equal */ break; } return 0; }
enum sec_status dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf, struct val_env* ve, time_t now, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, size_t sig_idx, struct rbtree_t** sortree, int* buf_canon, char** reason) { enum sec_status sec; uint8_t* sig; /* RRSIG rdata */ size_t siglen; size_t rrnum = rrset_get_count(rrset); uint8_t* signer; /* rrsig signer name */ size_t signer_len; unsigned char* sigblock; /* signature rdata field */ unsigned int sigblock_len; uint16_t ktag; /* DNSKEY key tag */ unsigned char* key; /* public key rdata field */ unsigned int keylen; rrset_get_rdata(rrset, rrnum + sig_idx, &sig, &siglen); /* min length of rdatalen, fixed rrsig, root signer, 1 byte sig */ if(siglen < 2+20) { verbose(VERB_QUERY, "verify: signature too short"); *reason = "signature too short"; return sec_status_bogus; } if(!(dnskey_get_flags(dnskey, dnskey_idx) & DNSKEY_BIT_ZSK)) { verbose(VERB_QUERY, "verify: dnskey without ZSK flag"); *reason = "dnskey without ZSK flag"; return sec_status_bogus; } if(dnskey_get_protocol(dnskey, dnskey_idx) != LDNS_DNSSEC_KEYPROTO) { /* RFC 4034 says DNSKEY PROTOCOL MUST be 3 */ verbose(VERB_QUERY, "verify: dnskey has wrong key protocol"); *reason = "dnskey has wrong protocolnumber"; return sec_status_bogus; } /* verify as many fields in rrsig as possible */ signer = sig+2+18; signer_len = dname_valid(signer, siglen-2-18); if(!signer_len) { verbose(VERB_QUERY, "verify: malformed signer name"); *reason = "signer name malformed"; return sec_status_bogus; /* signer name invalid */ } if(!dname_subdomain_c(rrset->rk.dname, signer)) { verbose(VERB_QUERY, "verify: signer name is off-tree"); *reason = "signer name off-tree"; return sec_status_bogus; /* signer name offtree */ } sigblock = (unsigned char*)signer+signer_len; if(siglen < 2+18+signer_len+1) { verbose(VERB_QUERY, "verify: too short, no signature data"); *reason = "signature too short, no signature data"; return sec_status_bogus; /* sig rdf is < 1 byte */ } sigblock_len = (unsigned int)(siglen - 2 - 18 - signer_len); /* verify key dname == sig signer name */ if(query_dname_compare(signer, dnskey->rk.dname) != 0) { verbose(VERB_QUERY, "verify: wrong key for rrsig"); log_nametypeclass(VERB_QUERY, "RRSIG signername is", signer, 0, 0); log_nametypeclass(VERB_QUERY, "the key name is", dnskey->rk.dname, 0, 0); *reason = "signer name mismatches key name"; return sec_status_bogus; } /* verify covered type */ /* memcmp works because type is in network format for rrset */ if(memcmp(sig+2, &rrset->rk.type, 2) != 0) { verbose(VERB_QUERY, "verify: wrong type covered"); *reason = "signature covers wrong type"; return sec_status_bogus; } /* verify keytag and sig algo (possibly again) */ if((int)sig[2+2] != dnskey_get_algo(dnskey, dnskey_idx)) { verbose(VERB_QUERY, "verify: wrong algorithm"); *reason = "signature has wrong algorithm"; return sec_status_bogus; } ktag = htons(dnskey_calc_keytag(dnskey, dnskey_idx)); if(memcmp(sig+2+16, &ktag, 2) != 0) { verbose(VERB_QUERY, "verify: wrong keytag"); *reason = "signature has wrong keytag"; return sec_status_bogus; } /* verify labels is in a valid range */ if((int)sig[2+3] > dname_signame_label_count(rrset->rk.dname)) { verbose(VERB_QUERY, "verify: labelcount out of range"); *reason = "signature labelcount out of range"; return sec_status_bogus; } /* original ttl, always ok */ if(!*buf_canon) { /* create rrset canonical format in buffer, ready for * signature */ if(!rrset_canonical(region, buf, rrset, sig+2, 18 + signer_len, sortree)) { log_err("verify: failed due to alloc error"); return sec_status_unchecked; } *buf_canon = 1; } /* check that dnskey is available */ dnskey_get_pubkey(dnskey, dnskey_idx, &key, &keylen); if(!key) { verbose(VERB_QUERY, "verify: short DNSKEY RR"); return sec_status_unchecked; } /* verify */ sec = verify_canonrrset(buf, (int)sig[2+2], sigblock, sigblock_len, key, keylen, reason); if(sec == sec_status_secure) { /* check if TTL is too high - reduce if so */ adjust_ttl(ve, now, rrset, sig+2+4, sig+2+8, sig+2+12); /* verify inception, expiration dates * Do this last so that if you ignore expired-sigs the * rest is sure to be OK. */ if(!check_dates(ve, now, sig+2+8, sig+2+12, reason)) { return sec_status_bogus; } } return sec; }