/** see if at least one RR is known (flags, algo) */ static int nsec3_rrset_has_known(struct ub_packed_rrset_key* s) { int r; for(r=0; r < (int)rrset_get_count(s); r++) { if(!nsec3_unknown_flags(s, r) && nsec3_known_algo(s, r)) return 1; } return 0; }
int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset) { size_t i; for(i=0; i<rrset_get_count(ds_rrset); i++) { if(ds_digest_algo_is_supported(ds_rrset, i) && ds_key_algo_is_supported(ds_rrset, i)) return 1; } return 0; }
/** verify that a DS RR hashes to a key and that key signs the set */ static enum sec_status verify_dnskeys_with_ds_rr(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, char** reason) { enum sec_status sec = sec_status_bogus; size_t i, num, numchecked = 0, numhashok = 0; num = rrset_get_count(dnskey_rrset); for(i=0; i<num; i++) { /* Skip DNSKEYs that don't match the basic criteria. */ if(ds_get_key_algo(ds_rrset, ds_idx) != dnskey_get_algo(dnskey_rrset, i) || dnskey_calc_keytag(dnskey_rrset, i) != ds_get_keytag(ds_rrset, ds_idx)) { continue; } numchecked++; verbose(VERB_ALGO, "attempt DS match algo %d keytag %d", ds_get_key_algo(ds_rrset, ds_idx), ds_get_keytag(ds_rrset, ds_idx)); /* Convert the candidate DNSKEY into a hash using the * same DS hash algorithm. */ if(!ds_digest_match_dnskey(env, dnskey_rrset, i, ds_rrset, ds_idx)) { verbose(VERB_ALGO, "DS match attempt failed"); continue; } numhashok++; verbose(VERB_ALGO, "DS match digest ok, trying signature"); /* Otherwise, we have a match! Make sure that the DNSKEY * verifies *with this key* */ sec = dnskey_verify_rrset(env, ve, dnskey_rrset, dnskey_rrset, i, reason); if(sec == sec_status_secure) { return sec; } /* If it didn't validate with the DNSKEY, try the next one! */ } if(numchecked == 0) algo_needs_reason(env, ds_get_key_algo(ds_rrset, ds_idx), reason, "no keys have a DS"); else if(numhashok == 0) *reason = "DS hash mismatches key"; else if(!*reason) *reason = "keyset not secured by DNSKEY that matches DS"; return sec_status_bogus; }
/** setup sig alg list from dnskey */ static void setup_sigalg(struct ub_packed_rrset_key* dnskey, uint8_t* sigalg) { uint8_t a[ALGO_NEEDS_MAX]; size_t i, n = 0; memset(a, 0, sizeof(a)); for(i=0; i<rrset_get_count(dnskey); i++) { uint8_t algo = (uint8_t)dnskey_get_algo(dnskey, i); if(a[algo] == 0) { a[algo] = 1; sigalg[n++] = algo; } } sigalg[n] = 0; }
int val_favorite_ds_algo(struct ub_packed_rrset_key* ds_rrset) { size_t i, num = rrset_get_count(ds_rrset); int d, digest_algo = 0; /* DS digest algo 0 is not used. */ /* find favorite algo, for now, highest number supported */ for(i=0; i<num; i++) { if(!ds_digest_algo_is_supported(ds_rrset, i) || !ds_key_algo_is_supported(ds_rrset, i)) { continue; } d = ds_get_digest_algo(ds_rrset, i); if(d > digest_algo) digest_algo = d; } return digest_algo; }
void algo_needs_init_dnskey_add(struct algo_needs* n, struct ub_packed_rrset_key* dnskey, uint8_t* sigalg) { uint8_t algo; size_t i, total = n->num; size_t num = rrset_get_count(dnskey); for(i=0; i<num; i++) { algo = (uint8_t)dnskey_get_algo(dnskey, i); if(!dnskey_algo_id_is_supported((int)algo)) continue; if(n->needs[algo] == 0) { n->needs[algo] = 1; sigalg[total] = algo; total++; } } sigalg[total] = 0; n->num = total; }
enum sec_status dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, time_t now, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t sig_idx, struct rbtree_t** sortree, char** reason) { /* find matching keys and check them */ enum sec_status sec = sec_status_bogus; uint16_t tag = rrset_get_sig_keytag(rrset, sig_idx); int algo = rrset_get_sig_algo(rrset, sig_idx); size_t i, num = rrset_get_count(dnskey); size_t numchecked = 0; int buf_canon = 0; verbose(VERB_ALGO, "verify sig %d %d", (int)tag, algo); if(!dnskey_algo_id_is_supported(algo)) { verbose(VERB_QUERY, "verify sig: unknown algorithm"); return sec_status_insecure; } for(i=0; i<num; i++) { /* see if key matches keytag and algo */ if(algo != dnskey_get_algo(dnskey, i) || tag != dnskey_calc_keytag(dnskey, i)) continue; numchecked ++; /* see if key verifies */ sec = dnskey_verify_rrset_sig(env->scratch, env->scratch_buffer, ve, now, rrset, dnskey, i, sig_idx, sortree, &buf_canon, reason); if(sec == sec_status_secure) return sec; } if(numchecked == 0) { *reason = "signatures from unknown keys"; verbose(VERB_QUERY, "verify: could not find appropriate key"); return sec_status_bogus; } return sec_status_bogus; }
/** * Iterate through NSEC3 list, per RR * This routine gives the next RR in the list (or sets rrset null). * Usage: * * size_t rrsetnum; * int rrnum; * struct ub_packed_rrset_key* rrset; * for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset; * rrset=filter_next(filter, &rrsetnum, &rrnum)) * do_stuff; * * Also filters out * o unknown flag NSEC3s * o unknown algorithm NSEC3s. * @param filter: nsec3 filter structure. * @param rrsetnum: in/out rrset number to look at. * @param rrnum: in/out rr number in rrset to look at. * @returns ptr to the next rrset (or NULL at end). */ static struct ub_packed_rrset_key* filter_next(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum) { size_t i; int r; uint8_t* nm; size_t nmlen; if(!filter->zone) /* empty list */ return NULL; for(i=*rrsetnum; i<filter->num; i++) { /* see if RRset qualifies */ if(ntohs(filter->list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 || ntohs(filter->list[i]->rk.rrset_class) != filter->fclass) continue; /* check RRset zone */ nm = filter->list[i]->rk.dname; nmlen = filter->list[i]->rk.dname_len; dname_remove_label(&nm, &nmlen); if(query_dname_compare(nm, filter->zone) != 0) continue; if(i == *rrsetnum) r = (*rrnum) + 1; /* continue at next RR */ else r = 0; /* new RRset start at first RR */ for(; r < (int)rrset_get_count(filter->list[i]); r++) { /* skip unknown flags, algo */ if(nsec3_unknown_flags(filter->list[i], r) || !nsec3_known_algo(filter->list[i], r)) continue; /* this one is a good target */ *rrsetnum = i; *rrnum = r; return filter->list[i]; } } return NULL; }
void algo_needs_init_ds(struct algo_needs* n, struct ub_packed_rrset_key* ds, int fav_ds_algo, uint8_t* sigalg) { uint8_t algo; size_t i, total = 0; size_t num = rrset_get_count(ds); memset(n->needs, 0, sizeof(uint8_t)*ALGO_NEEDS_MAX); for(i=0; i<num; i++) { if(ds_get_digest_algo(ds, i) != fav_ds_algo) continue; algo = (uint8_t)ds_get_key_algo(ds, i); if(!dnskey_algo_id_is_supported((int)algo)) continue; log_assert(algo != 0); /* we do not support 0 and is EOS */ if(n->needs[algo] == 0) { n->needs[algo] = 1; sigalg[total] = algo; total++; } } sigalg[total] = 0; n->num = total; }
enum sec_status val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, char** reason) { /* as long as this is false, we can consider this DS rrset to be * equivalent to no DS rrset. */ int has_useful_ds = 0, digest_algo, alg; struct algo_needs needs; size_t i, num; enum sec_status sec; if(dnskey_rrset->rk.dname_len != ds_rrset->rk.dname_len || query_dname_compare(dnskey_rrset->rk.dname, ds_rrset->rk.dname) != 0) { verbose(VERB_QUERY, "DNSKEY RRset did not match DS RRset " "by name"); *reason = "DNSKEY RRset did not match DS RRset by name"; return sec_status_bogus; } digest_algo = val_favorite_ds_algo(ds_rrset); algo_needs_init_ds(&needs, ds_rrset, digest_algo); num = rrset_get_count(ds_rrset); for(i=0; i<num; i++) { /* Check to see if we can understand this DS. * And check it is the strongest digest */ if(!ds_digest_algo_is_supported(ds_rrset, i) || !ds_key_algo_is_supported(ds_rrset, i) || ds_get_digest_algo(ds_rrset, i) != digest_algo) { continue; } /* Once we see a single DS with a known digestID and * algorithm, we cannot return INSECURE (with a * "null" KeyEntry). */ has_useful_ds = true; sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, ds_rrset, i, reason); if(sec == sec_status_secure) { if(algo_needs_set_secure(&needs, (uint8_t)ds_get_key_algo(ds_rrset, i))) { verbose(VERB_ALGO, "DS matched DNSKEY."); return sec_status_secure; } } else if(sec == sec_status_bogus) { algo_needs_set_bogus(&needs, (uint8_t)ds_get_key_algo(ds_rrset, i)); } } /* None of the DS's worked out. */ /* If no DSs were understandable, then this is OK. */ if(!has_useful_ds) { verbose(VERB_ALGO, "No usable DS records were found -- " "treating as insecure."); return sec_status_insecure; } /* If any were understandable, then it is bad. */ verbose(VERB_QUERY, "Failed to match any usable DS to a DNSKEY."); if((alg=algo_needs_missing(&needs)) != 0) { algo_needs_reason(env, alg, reason, "missing verification of " "DNSKEY signature"); } return sec_status_bogus; }
enum sec_status val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ta_ds, struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason) { /* as long as this is false, we can consider this anchor to be * equivalent to no anchor. */ int has_useful_ta = 0, digest_algo = 0, alg; struct algo_needs needs; size_t i, num; enum sec_status sec; if(ta_ds && (dnskey_rrset->rk.dname_len != ta_ds->rk.dname_len || query_dname_compare(dnskey_rrset->rk.dname, ta_ds->rk.dname) != 0)) { verbose(VERB_QUERY, "DNSKEY RRset did not match DS RRset " "by name"); *reason = "DNSKEY RRset did not match DS RRset by name"; return sec_status_bogus; } if(ta_dnskey && (dnskey_rrset->rk.dname_len != ta_dnskey->rk.dname_len || query_dname_compare(dnskey_rrset->rk.dname, ta_dnskey->rk.dname) != 0)) { verbose(VERB_QUERY, "DNSKEY RRset did not match anchor RRset " "by name"); *reason = "DNSKEY RRset did not match anchor RRset by name"; return sec_status_bogus; } if(ta_ds) digest_algo = val_favorite_ds_algo(ta_ds); if(sigalg) { if(ta_ds) algo_needs_init_ds(&needs, ta_ds, digest_algo, sigalg); else memset(&needs, 0, sizeof(needs)); if(ta_dnskey) algo_needs_init_dnskey_add(&needs, ta_dnskey, sigalg); } if(ta_ds) { num = rrset_get_count(ta_ds); for(i=0; i<num; i++) { /* Check to see if we can understand this DS. * And check it is the strongest digest */ if(!ds_digest_algo_is_supported(ta_ds, i) || !ds_key_algo_is_supported(ta_ds, i) || ds_get_digest_algo(ta_ds, i) != digest_algo) continue; /* Once we see a single DS with a known digestID and * algorithm, we cannot return INSECURE (with a * "null" KeyEntry). */ has_useful_ta = 1; sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, ta_ds, i, reason); if(sec == sec_status_secure) { if(!sigalg || algo_needs_set_secure(&needs, (uint8_t)ds_get_key_algo(ta_ds, i))) { verbose(VERB_ALGO, "DS matched DNSKEY."); return sec_status_secure; } } else if(sigalg && sec == sec_status_bogus) { algo_needs_set_bogus(&needs, (uint8_t)ds_get_key_algo(ta_ds, i)); } } } /* None of the DS's worked out: check the DNSKEYs. */ if(ta_dnskey) { num = rrset_get_count(ta_dnskey); for(i=0; i<num; i++) { /* Check to see if we can understand this DNSKEY */ if(!dnskey_algo_is_supported(ta_dnskey, i)) continue; /* we saw a useful TA */ has_useful_ta = 1; sec = dnskey_verify_rrset(env, ve, dnskey_rrset, ta_dnskey, i, reason); if(sec == sec_status_secure) { if(!sigalg || algo_needs_set_secure(&needs, (uint8_t)dnskey_get_algo(ta_dnskey, i))) { verbose(VERB_ALGO, "anchor matched DNSKEY."); return sec_status_secure; } } else if(sigalg && sec == sec_status_bogus) { algo_needs_set_bogus(&needs, (uint8_t)dnskey_get_algo(ta_dnskey, i)); } } } /* If no DSs were understandable, then this is OK. */ if(!has_useful_ta) { verbose(VERB_ALGO, "No usable trust anchors were found -- " "treating as insecure."); return sec_status_insecure; } /* If any were understandable, then it is bad. */ verbose(VERB_QUERY, "Failed to match any usable anchor to a DNSKEY."); if(sigalg && (alg=algo_needs_missing(&needs)) != 0) { algo_needs_reason(env, alg, reason, "missing verification of " "DNSKEY signature"); } return sec_status_bogus; }
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; }