/** * Create a DS digest for a DNSKEY entry. * * @param env: module environment. Uses scratch space. * @param dnskey_rrset: DNSKEY rrset. * @param dnskey_idx: index of RR in rrset. * @param ds_rrset: DS rrset * @param ds_idx: index of RR in DS rrset. * @param digest: digest is returned in here (must be correctly sized). * @return false on error. */ static int ds_create_dnskey_digest(struct module_env* env, struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, uint8_t* digest) { sldns_buffer* b = env->scratch_buffer; uint8_t* dnskey_rdata; size_t dnskey_len; rrset_get_rdata(dnskey_rrset, dnskey_idx, &dnskey_rdata, &dnskey_len); /* create digest source material in buffer * digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); * DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. */ sldns_buffer_clear(b); sldns_buffer_write(b, dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len); query_dname_tolower(sldns_buffer_begin(b)); sldns_buffer_write(b, dnskey_rdata+2, dnskey_len-2); /* skip rdatalen*/ sldns_buffer_flip(b); return secalgo_ds_digest(ds_get_digest_algo(ds_rrset, ds_idx), (unsigned char*)sldns_buffer_begin(b), sldns_buffer_limit(b), (unsigned char*)digest); }
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; }
int ds_digest_match_dnskey(struct module_env* env, struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx) { uint8_t* ds; /* DS digest */ size_t dslen; uint8_t* digest; /* generated digest */ size_t digestlen = ds_digest_size_algo(ds_rrset, ds_idx); if(digestlen == 0) { verbose(VERB_QUERY, "DS fail: not supported, or DS RR " "format error"); return 0; /* not supported, or DS RR format error */ } #ifndef USE_SHA1 if(fake_sha1 && ds_get_digest_algo(ds_rrset, ds_idx)==LDNS_SHA1) return 1; #endif /* check digest length in DS with length from hash function */ ds_get_sigdata(ds_rrset, ds_idx, &ds, &dslen); if(!ds || dslen != digestlen) { verbose(VERB_QUERY, "DS fail: DS RR algo and digest do not " "match each other"); return 0; /* DS algorithm and digest do not match */ } digest = regional_alloc(env->scratch, digestlen); if(!digest) { verbose(VERB_QUERY, "DS fail: out of memory"); return 0; /* mem error */ } if(!ds_create_dnskey_digest(env, dnskey_rrset, dnskey_idx, ds_rrset, ds_idx, digest)) { verbose(VERB_QUERY, "DS fail: could not calc key digest"); return 0; /* digest algo failed */ } if(memcmp(digest, ds, dslen) != 0) { verbose(VERB_QUERY, "DS fail: digest is different"); return 0; /* digest different */ } return 1; }
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; }
/** * Return size of DS digest according to its hash algorithm. * @param k: DS rrset. * @param idx: which DS. * @return size in bytes of digest, or 0 if not supported. */ static size_t ds_digest_size_algo(struct ub_packed_rrset_key* k, size_t idx) { return ds_digest_size_supported(ds_get_digest_algo(k, idx)); }