diff options
Diffstat (limited to 'services/authzone.c')
| -rw-r--r-- | services/authzone.c | 1405 |
1 files changed, 1387 insertions, 18 deletions
diff --git a/services/authzone.c b/services/authzone.c index 2ef782c1f45c..e6e3a8cff9b4 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -67,7 +67,11 @@ #include "sldns/parseutil.h" #include "sldns/keyraw.h" #include "validator/val_nsec3.h" +#include "validator/val_nsec.h" #include "validator/val_secalgo.h" +#include "validator/val_sigcrypt.h" +#include "validator/val_anchor.h" +#include "validator/val_utils.h" #include <ctype.h> /** bytes to use for NSEC3 hash buffer. 20 for sha1 */ @@ -1741,9 +1745,45 @@ int auth_zone_write_file(struct auth_zone* z, const char* fname) return 1; } +/** offline verify for zonemd, while reading a zone file to immediately + * spot bad hashes in zonefile as they are read. + * Creates temp buffers, but uses anchors and validation environment + * from the module_env. */ +static void +zonemd_offline_verify(struct auth_zone* z, struct module_env* env_for_val, + struct module_stack* mods) +{ + struct module_env env; + time_t now = 0; + if(!z->zonemd_check) + return; + env = *env_for_val; + env.scratch_buffer = sldns_buffer_new(env.cfg->msg_buffer_size); + if(!env.scratch_buffer) { + log_err("out of memory"); + goto clean_exit; + } + env.scratch = regional_create(); + if(!env.now) { + env.now = &now; + now = time(NULL); + } + if(!env.scratch) { + log_err("out of memory"); + goto clean_exit; + } + auth_zone_verify_zonemd(z, &env, mods, NULL, 1, 0); + +clean_exit: + /* clean up and exit */ + sldns_buffer_free(env.scratch_buffer); + regional_destroy(env.scratch); +} + /** read all auth zones from file (if they have) */ static int -auth_zones_read_zones(struct auth_zones* az, struct config_file* cfg) +auth_zones_read_zones(struct auth_zones* az, struct config_file* cfg, + struct module_env* env, struct module_stack* mods) { struct auth_zone* z; lock_rw_wrlock(&az->lock); @@ -1754,12 +1794,162 @@ auth_zones_read_zones(struct auth_zones* az, struct config_file* cfg) lock_rw_unlock(&az->lock); return 0; } + if(z->zonefile && z->zonefile[0]!=0 && env) + zonemd_offline_verify(z, env, mods); lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); return 1; } +/** fetch the content of a ZONEMD RR from the rdata */ +static int zonemd_fetch_parameters(struct auth_rrset* zonemd_rrset, size_t i, + uint32_t* serial, int* scheme, int* hashalgo, uint8_t** hash, + size_t* hashlen) +{ + size_t rr_len; + uint8_t* rdata; + if(i >= zonemd_rrset->data->count) + return 0; + rr_len = zonemd_rrset->data->rr_len[i]; + if(rr_len < 2+4+1+1) + return 0; /* too short, for rdlen+serial+scheme+algo */ + rdata = zonemd_rrset->data->rr_data[i]; + *serial = sldns_read_uint32(rdata+2); + *scheme = rdata[6]; + *hashalgo = rdata[7]; + *hashlen = rr_len - 8; + if(*hashlen == 0) + *hash = NULL; + else *hash = rdata+8; + return 1; +} + +/** + * See if the ZONEMD scheme, hash occurs more than once. + * @param zonemd_rrset: the zonemd rrset to check with the RRs in it. + * @param index: index of the original, this is allowed to have that + * scheme and hashalgo, but other RRs should not have it. + * @param scheme: the scheme to check for. + * @param hashalgo: the hash algorithm to check for. + * @return true if it occurs more than once. + */ +static int zonemd_is_duplicate_scheme_hash(struct auth_rrset* zonemd_rrset, + size_t index, int scheme, int hashalgo) +{ + size_t j; + for(j=0; j<zonemd_rrset->data->count; j++) { + uint32_t serial2 = 0; + int scheme2 = 0, hashalgo2 = 0; + uint8_t* hash2 = NULL; + size_t hashlen2 = 0; + if(index == j) { + /* this is the original */ + continue; + } + if(!zonemd_fetch_parameters(zonemd_rrset, j, &serial2, + &scheme2, &hashalgo2, &hash2, &hashlen2)) { + /* malformed, skip it */ + continue; + } + if(scheme == scheme2 && hashalgo == hashalgo2) { + /* duplicate scheme, hash */ + verbose(VERB_ALGO, "zonemd duplicate for scheme %d " + "and hash %d", scheme, hashalgo); + return 1; + } + } + return 0; +} + +/** + * Check ZONEMDs if present for the auth zone. Depending on config + * it can warn or fail on that. Checks the hash of the ZONEMD. + * @param z: auth zone to check for. + * caller must hold lock on zone. + * @param env: module env for temp buffers. + * @param reason: returned on failure. + * @return false on failure, true if hash checks out. + */ +static int auth_zone_zonemd_check_hash(struct auth_zone* z, + struct module_env* env, char** reason) +{ + /* loop over ZONEMDs and see which one is valid. if not print + * failure (depending on config) */ + struct auth_data* apex; + struct auth_rrset* zonemd_rrset; + size_t i; + struct regional* region = NULL; + struct sldns_buffer* buf = NULL; + uint32_t soa_serial = 0; + region = env->scratch; + regional_free_all(region); + buf = env->scratch_buffer; + if(!auth_zone_get_serial(z, &soa_serial)) { + *reason = "zone has no SOA serial"; + return 0; + } + + apex = az_find_name(z, z->name, z->namelen); + if(!apex) { + *reason = "zone has no apex"; + return 0; + } + zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); + if(!zonemd_rrset || zonemd_rrset->data->count==0) { + *reason = "zone has no ZONEMD"; + return 0; /* no RRset or no RRs in rrset */ + } + + /* we have a ZONEMD, check if it is correct */ + for(i=0; i<zonemd_rrset->data->count; i++) { + uint32_t serial = 0; + int scheme = 0, hashalgo = 0; + uint8_t* hash = NULL; + size_t hashlen = 0; + if(!zonemd_fetch_parameters(zonemd_rrset, i, &serial, &scheme, + &hashalgo, &hash, &hashlen)) { + /* malformed RR */ + *reason = "ZONEMD rdata malformed"; + continue; + } + /* check for duplicates */ + if(zonemd_is_duplicate_scheme_hash(zonemd_rrset, i, scheme, + hashalgo)) { + /* duplicate hash of the same scheme,hash + * is not allowed. */ + *reason = "ZONEMD RRSet contains more than one RR " + "with the same scheme and hash algorithm"; + continue; + } + regional_free_all(region); + if(serial != soa_serial) { + *reason = "ZONEMD serial is wrong"; + continue; + } + if(auth_zone_generate_zonemd_check(z, scheme, hashalgo, + hash, hashlen, region, buf, reason)) { + /* success */ + if(verbosity >= VERB_ALGO) { + char zstr[255+1]; + dname_str(z->name, zstr); + verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); + } + return 1; + } + /* try next one */ + } + /* fail, we may have reason */ + if(!*reason) + *reason = "no ZONEMD records found"; + if(verbosity >= VERB_ALGO) { + char zstr[255+1]; + dname_str(z->name, zstr); + verbose(VERB_ALGO, "auth-zone %s ZONEMD failed: %s", zstr, *reason); + } + return 0; +} + /** find serial number of zone or false if none */ int auth_zone_get_serial(struct auth_zone* z, uint32_t* serial) @@ -1779,7 +1969,7 @@ auth_zone_get_serial(struct auth_zone* z, uint32_t* serial) } /** Find auth_zone SOA and populate the values in xfr(soa values). */ -static int +int xfr_find_soa(struct auth_zone* z, struct auth_xfer* xfr) { struct auth_data* apex; @@ -1908,6 +2098,8 @@ auth_zones_cfg(struct auth_zones* az, struct config_auth* c) z->for_downstream = c->for_downstream; z->for_upstream = c->for_upstream; z->fallback_enabled = c->fallback_enabled; + z->zonemd_check = c->zonemd_check; + z->zonemd_reject_absence = c->zonemd_reject_absence; if(c->isrpz && !z->rpz){ if(!(z->rpz = rpz_create(c))){ fatal_exit("Could not setup RPZ zones"); @@ -2000,7 +2192,8 @@ az_delete_deleted_zones(struct auth_zones* az) } int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, - int setup, int* is_rpz) + int setup, int* is_rpz, struct module_env* env, + struct module_stack* mods) { struct config_auth* p; az_setall_deleted(az); @@ -2016,7 +2209,7 @@ int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, } } az_delete_deleted_zones(az); - if(!auth_zones_read_zones(az, cfg)) + if(!auth_zones_read_zones(az, cfg, env, mods)) return 0; if(setup) { if(!auth_zones_setup_zones(az)) @@ -4959,6 +5152,9 @@ xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) lock_rw_unlock(&z->lock); return; } +#ifdef UB_ON_WINDOWS + (void)unlink(zfilename); /* windows does not replace file with rename() */ +#endif if(rename(tmpfile, zfilename) < 0) { log_err("could not rename(%s, %s): %s", tmpfile, zfilename, strerror(errno)); @@ -4969,6 +5165,28 @@ xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) lock_rw_unlock(&z->lock); } +/** reacquire locks and structures. Starts with no locks, ends + * with xfr and z locks, if fail, no z lock */ +static int xfr_process_reacquire_locks(struct auth_xfer* xfr, + struct module_env* env, struct auth_zone** z) +{ + /* release xfr lock, then, while holding az->lock grab both + * z->lock and xfr->lock */ + lock_rw_rdlock(&env->auth_zones->lock); + *z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, + xfr->dclass); + if(!*z) { + lock_rw_unlock(&env->auth_zones->lock); + lock_basic_lock(&xfr->lock); + *z = NULL; + return 0; + } + lock_rw_wrlock(&(*z)->lock); + lock_basic_lock(&xfr->lock); + lock_rw_unlock(&env->auth_zones->lock); + return 1; +} + /** process chunk list and update zone in memory, * return false if it did not work */ static int @@ -4978,21 +5196,12 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, struct auth_zone* z; /* obtain locks and structures */ - /* release xfr lock, then, while holding az->lock grab both - * z->lock and xfr->lock */ lock_basic_unlock(&xfr->lock); - lock_rw_rdlock(&env->auth_zones->lock); - z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, - xfr->dclass); - if(!z) { - lock_rw_unlock(&env->auth_zones->lock); + if(!xfr_process_reacquire_locks(xfr, env, &z)) { /* the zone is gone, ignore xfr results */ - lock_basic_lock(&xfr->lock); return 0; } - lock_rw_wrlock(&z->lock); - lock_basic_lock(&xfr->lock); - lock_rw_unlock(&env->auth_zones->lock); + /* holding xfr and z locks */ /* apply data */ if(xfr->task_transfer->master->http) { @@ -5027,6 +5236,35 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, " (or malformed RR)", xfr->task_transfer->master->host); return 0; } + + /* release xfr lock while verifying zonemd because it may have + * to spawn lookups in the state machines */ + lock_basic_unlock(&xfr->lock); + /* holding z lock */ + auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 0); + if(z->zone_expired) { + char zname[256]; + /* ZONEMD must have failed */ + /* reacquire locks, so we hold xfr lock on exit of routine, + * and both xfr and z again after releasing xfr for potential + * state machine mesh callbacks */ + lock_rw_unlock(&z->lock); + if(!xfr_process_reacquire_locks(xfr, env, &z)) + return 0; + dname_str(xfr->name, zname); + verbose(VERB_ALGO, "xfr from %s: ZONEMD failed for %s, transfer is failed", xfr->task_transfer->master->host, zname); + xfr->zone_expired = 1; + lock_rw_unlock(&z->lock); + return 0; + } + /* reacquire locks, so we hold xfr lock on exit of routine, + * and both xfr and z again after releasing xfr for potential + * state machine mesh callbacks */ + lock_rw_unlock(&z->lock); + if(!xfr_process_reacquire_locks(xfr, env, &z)) + return 0; + /* holding xfr and z locks */ + if(xfr->have_zone) xfr->lease_time = *env->now; @@ -5188,7 +5426,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) xfr->task_transfer->cp = outnet_comm_point_for_http( env->outnet, auth_xfer_transfer_http_callback, xfr, &addr, addrlen, -1, master->ssl, master->host, - master->file); + master->file, env->cfg); if(!xfr->task_transfer->cp) { char zname[255+1], as[256]; dname_str(xfr->name, zname); @@ -5210,7 +5448,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) /* perform AXFR/IXFR */ /* set the packet to be written */ /* create new ID */ - xfr->task_transfer->id = (uint16_t)(ub_random(env->rnd)&0xffff); + xfr->task_transfer->id = GET_RANDOM_ID(env->rnd); xfr_create_ixfr_packet(xfr, env->scratch_buffer, xfr->task_transfer->id, master); @@ -6060,7 +6298,7 @@ xfr_probe_send_probe(struct auth_xfer* xfr, struct module_env* env, /* create new ID for new probes, but not on timeout retries, * this means we'll accept replies to previous retries to same ip */ if(timeout == AUTH_PROBE_TIMEOUT) - xfr->task_probe->id = (uint16_t)(ub_random(env->rnd)&0xffff); + xfr->task_probe->id = GET_RANDOM_ID(env->rnd); xfr_create_soa_probe_packet(xfr, env->scratch_buffer, xfr->task_probe->id); /* we need to remove the cp if we have a different ip4/ip6 type now */ @@ -6933,12 +7171,14 @@ xfer_set_masters(struct auth_master** list, struct config_auth* c, if(with_http) for(p = c->urls; p; p = p->next) { m = auth_master_new(&list); + if(!m) return 0; m->http = 1; if(!parse_url(p->str, &m->host, &m->file, &m->port, &m->ssl)) return 0; } for(p = c->masters; p; p = p->next) { m = auth_master_new(&list); + if(!m) return 0; m->ixfr = 1; /* this flag is not configurable */ m->host = strdup(p->str); if(!m->host) { @@ -6948,6 +7188,7 @@ xfer_set_masters(struct auth_master** list, struct config_auth* c, } for(p = c->allow_notify; p; p = p->next) { m = auth_master_new(&list); + if(!m) return 0; m->allow_notify = 1; m->host = strdup(p->str); if(!m->host) { @@ -6972,3 +7213,1131 @@ compare_serial(uint32_t a, uint32_t b) return 1; } } + +int zonemd_hashalgo_supported(int hashalgo) +{ + if(hashalgo == ZONEMD_ALGO_SHA384) return 1; + if(hashalgo == ZONEMD_ALGO_SHA512) return 1; + return 0; +} + +int zonemd_scheme_supported(int scheme) +{ + if(scheme == ZONEMD_SCHEME_SIMPLE) return 1; + return 0; +} + +/** initialize hash for hashing with zonemd hash algo */ +static struct secalgo_hash* zonemd_digest_init(int hashalgo, char** reason) +{ + struct secalgo_hash *h; + if(hashalgo == ZONEMD_ALGO_SHA384) { + /* sha384 */ + h = secalgo_hash_create_sha384(); + if(!h) + *reason = "digest sha384 could not be created"; + return h; + } else if(hashalgo == ZONEMD_ALGO_SHA512) { + /* sha512 */ + h = secalgo_hash_create_sha512(); + if(!h) + *reason = "digest sha512 could not be created"; + return h; + } + /* unknown hash algo */ + *reason = "unsupported algorithm"; + return NULL; +} + +/** update the hash for zonemd */ +static int zonemd_digest_update(int hashalgo, struct secalgo_hash* h, + uint8_t* data, size_t len, char** reason) +{ + if(hashalgo == ZONEMD_ALGO_SHA384) { + if(!secalgo_hash_update(h, data, len)) { + *reason = "digest sha384 failed"; + return 0; + } + return 1; + } else if(hashalgo == ZONEMD_ALGO_SHA512) { + if(!secalgo_hash_update(h, data, len)) { + *reason = "digest sha512 failed"; + return 0; + } + return 1; + } + /* unknown hash algo */ + *reason = "unsupported algorithm"; + return 0; +} + +/** finish the hash for zonemd */ +static int zonemd_digest_finish(int hashalgo, struct secalgo_hash* h, + uint8_t* result, size_t hashlen, size_t* resultlen, char** reason) +{ + if(hashalgo == ZONEMD_ALGO_SHA384) { + if(hashlen < 384/8) { + *reason = "digest buffer too small for sha384"; + return 0; + } + if(!secalgo_hash_final(h, result, hashlen, resultlen)) { + *reason = "digest sha384 finish failed"; + return 0; + } + return 1; + } else if(hashalgo == ZONEMD_ALGO_SHA512) { + if(hashlen < 512/8) { + *reason = "digest buffer too small for sha512"; + return 0; + } + if(!secalgo_hash_final(h, result, hashlen, resultlen)) { + *reason = "digest sha512 finish failed"; + return 0; + } + return 1; + } + /* unknown algo */ + *reason = "unsupported algorithm"; + return 0; +} + +/** add rrsets from node to the list */ +static size_t authdata_rrsets_to_list(struct auth_rrset** array, + size_t arraysize, struct auth_rrset* first) +{ + struct auth_rrset* rrset = first; + size_t num = 0; + while(rrset) { + if(num >= arraysize) + return num; + array[num] = rrset; + num++; + rrset = rrset->next; + } + return num; +} + +/** compare rr list entries */ +static int rrlist_compare(const void* arg1, const void* arg2) +{ + struct auth_rrset* r1 = *(struct auth_rrset**)arg1; + struct auth_rrset* r2 = *(struct auth_rrset**)arg2; + uint16_t t1, t2; + if(r1 == NULL) t1 = LDNS_RR_TYPE_RRSIG; + else t1 = r1->type; + if(r2 == NULL) t2 = LDNS_RR_TYPE_RRSIG; + else t2 = r2->type; + if(t1 < t2) + return -1; + if(t1 > t2) + return 1; + return 0; +} + +/** add type RRSIG to rr list if not one there already, + * this is to perform RRSIG collate processing at that point. */ +static void addrrsigtype_if_needed(struct auth_rrset** array, + size_t arraysize, size_t* rrnum, struct auth_data* node) +{ + if(az_domain_rrset(node, LDNS_RR_TYPE_RRSIG)) + return; /* already one there */ + if((*rrnum) >= arraysize) + return; /* array too small? */ + array[*rrnum] = NULL; /* nothing there, but need entry in list */ + (*rrnum)++; +} + +/** collate the RRs in an RRset using the simple scheme */ +static int zonemd_simple_rrset(struct auth_zone* z, int hashalgo, + struct secalgo_hash* h, struct auth_data* node, + struct auth_rrset* rrset, struct regional* region, + struct sldns_buffer* buf, char** reason) +{ + /* canonicalize */ + struct ub_packed_rrset_key key; + memset(&key, 0, sizeof(key)); + key.entry.key = &key; + key.entry.data = rrset->data; + key.rk.dname = node->name; + key.rk.dname_len = node->namelen; + key.rk.type = htons(rrset->type); + key.rk.rrset_class = htons(z->dclass); + if(!rrset_canonicalize_to_buffer(region, buf, &key)) { + *reason = "out of memory"; + return 0; + } + regional_free_all(region); + + /* hash */ + if(!zonemd_digest_update(hashalgo, h, sldns_buffer_begin(buf), + sldns_buffer_limit(buf), reason)) { + return 0; + } + return 1; +} + +/** count number of RRSIGs in a domain name rrset list */ +static size_t zonemd_simple_count_rrsig(struct auth_rrset* rrset, + struct auth_rrset** rrlist, size_t rrnum, + struct auth_zone* z, struct auth_data* node) +{ + size_t i, count = 0; + if(rrset) { + size_t j; + for(j = 0; j<rrset->data->count; j++) { + if(rrsig_rdata_get_type_covered(rrset->data-> + rr_data[j], rrset->data->rr_len[j]) == + LDNS_RR_TYPE_ZONEMD && + query_dname_compare(z->name, node->name)==0) { + /* omit RRSIGs over type ZONEMD at apex */ + continue; + } + count++; + } + } + for(i=0; i<rrnum; i++) { + if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD && + query_dname_compare(z->name, node->name)==0) { + /* omit RRSIGs over type ZONEMD at apex */ + continue; + } + count += (rrlist[i]?rrlist[i]->data->rrsig_count:0); + } + return count; +} + +/** allocate sparse rrset data for the number of entries in tepm region */ +static int zonemd_simple_rrsig_allocs(struct regional* region, + struct packed_rrset_data* data, size_t count) +{ + data->rr_len = regional_alloc(region, sizeof(*data->rr_len) * count); + if(!data->rr_len) { + return 0; + } + data->rr_ttl = regional_alloc(region, sizeof(*data->rr_ttl) * count); + if(!data->rr_ttl) { + return 0; + } + data->rr_data = regional_alloc(region, sizeof(*data->rr_data) * count); + if(!data->rr_data) { + return 0; + } + return 1; +} + +/** add the RRSIGs from the rrs in the domain into the data */ +static void add_rrlist_rrsigs_into_data(struct packed_rrset_data* data, + size_t* done, struct auth_rrset** rrlist, size_t rrnum, + struct auth_zone* z, struct auth_data* node) +{ + size_t i; + for(i=0; i<rrnum; i++) { + size_t j; + if(!rrlist[i]) + continue; + if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD && + query_dname_compare(z->name, node->name)==0) { + /* omit RRSIGs over type ZONEMD at apex */ + continue; + } + for(j = 0; j<rrlist[i]->data->rrsig_count; j++) { + data->rr_len[*done] = rrlist[i]->data->rr_len[rrlist[i]->data->count + j]; + data->rr_ttl[*done] = rrlist[i]->data->rr_ttl[rrlist[i]->data->count + j]; + /* reference the rdata in the rrset, no need to + * copy it, it is no longer needed at the end of + * the routine */ + data->rr_data[*done] = rrlist[i]->data->rr_data[rrlist[i]->data->count + j]; + (*done)++; + } + } +} + +static void add_rrset_into_data(struct packed_rrset_data* data, + size_t* done, struct auth_rrset* rrset, + struct auth_zone* z, struct auth_data* node) +{ + if(rrset) { + size_t j; + for(j = 0; j<rrset->data->count; j++) { + if(rrsig_rdata_get_type_covered(rrset->data-> + rr_data[j], rrset->data->rr_len[j]) == + LDNS_RR_TYPE_ZONEMD && + query_dname_compare(z->name, node->name)==0) { + /* omit RRSIGs over type ZONEMD at apex */ + continue; + } + data->rr_len[*done] = rrset->data->rr_len[j]; + data->rr_ttl[*done] = rrset->data->rr_ttl[j]; + /* reference the rdata in the rrset, no need to + * copy it, it is no longer need at the end of + * the routine */ + data->rr_data[*done] = rrset->data->rr_data[j]; + (*done)++; + } + } +} + +/** collate the RRSIGs using the simple scheme */ +static int zonemd_simple_rrsig(struct auth_zone* z, int hashalgo, + struct secalgo_hash* h, struct auth_data* node, + struct auth_rrset* rrset, struct auth_rrset** rrlist, size_t rrnum, + struct regional* region, struct sldns_buffer* buf, char** reason) +{ + /* the rrset pointer can be NULL, this means it is type RRSIG and + * there is no ordinary type RRSIG there. The RRSIGs are stored + * with the RRsets in their data. + * + * The RRset pointer can be nonNULL. This happens if there is + * no RR that is covered by the RRSIG for the domain. Then this + * RRSIG RR is stored in an rrset of type RRSIG. The other RRSIGs + * are stored in the rrset entries for the RRs in the rr list for + * the domain node. We need to collate the rrset's data, if any, and + * the rrlist's rrsigs */ + /* if this is the apex, omit RRSIGs that cover type ZONEMD */ + /* build rrsig rrset */ + size_t done = 0; + struct ub_packed_rrset_key key; + struct packed_rrset_data data; + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + key.entry.key = &key; + key.entry.data = &data; + key.rk.dname = node->name; + key.rk.dname_len = node->namelen; + key.rk.type = htons(LDNS_RR_TYPE_RRSIG); + key.rk.rrset_class = htons(z->dclass); + data.count = zonemd_simple_count_rrsig(rrset, rrlist, rrnum, z, node); + if(!zonemd_simple_rrsig_allocs(region, &data, data.count)) { + *reason = "out of memory"; + regional_free_all(region); + return 0; + } + /* all the RRSIGs stored in the other rrsets for this domain node */ + add_rrlist_rrsigs_into_data(&data, &done, rrlist, rrnum, z, node); + /* plus the RRSIGs stored in an rrset of type RRSIG for this node */ + add_rrset_into_data(&data, &done, rrset, z, node); + + /* canonicalize */ + if(!rrset_canonicalize_to_buffer(region, buf, &key)) { + *reason = "out of memory"; + regional_free_all(region); + return 0; + } + regional_free_all(region); + + /* hash */ + if(!zonemd_digest_update(hashalgo, h, sldns_buffer_begin(buf), + sldns_buffer_limit(buf), reason)) { + return 0; + } + return 1; +} + +/** collate a domain's rrsets using the simple scheme */ +static int zonemd_simple_domain(struct auth_zone* z, int hashalgo, + struct secalgo_hash* h, struct auth_data* node, + struct regional* region, struct sldns_buffer* buf, char** reason) +{ + const size_t rrlistsize = 65536; + struct auth_rrset* rrlist[rrlistsize]; + size_t i, rrnum = 0; + /* see if the domain is out of scope, the zone origin, + * that would be omitted */ + if(!dname_subdomain_c(node->name, z->name)) + return 1; /* continue */ + /* loop over the rrsets in ascending order. */ + rrnum = authdata_rrsets_to_list(rrlist, rrlistsize, node->rrsets); + addrrsigtype_if_needed(rrlist, rrlistsize, &rrnum, node); + qsort(rrlist, rrnum, sizeof(*rrlist), rrlist_compare); + for(i=0; i<rrnum; i++) { + if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD && + query_dname_compare(z->name, node->name) == 0) { + /* omit type ZONEMD at apex */ + continue; + } + if(rrlist[i] == NULL || rrlist[i]->type == + LDNS_RR_TYPE_RRSIG) { + if(!zonemd_simple_rrsig(z, hashalgo, h, node, + rrlist[i], rrlist, rrnum, region, buf, reason)) + return 0; + } else if(!zonemd_simple_rrset(z, hashalgo, h, node, + rrlist[i], region, buf, reason)) { + return 0; + } + } + return 1; +} + +/** collate the zone using the simple scheme */ +static int zonemd_simple_collate(struct auth_zone* z, int hashalgo, + struct secalgo_hash* h, struct regional* region, + struct sldns_buffer* buf, char** reason) +{ + /* our tree is sorted in canonical order, so we can just loop over + * the tree */ + struct auth_data* n; + RBTREE_FOR(n, struct auth_data*, &z->data) { + if(!zonemd_simple_domain(z, hashalgo, h, n, region, buf, + reason)) + return 0; + } + return 1; +} + +int auth_zone_generate_zonemd_hash(struct auth_zone* z, int scheme, + int hashalgo, uint8_t* hash, size_t hashlen, size_t* resultlen, + struct regional* region, struct sldns_buffer* buf, char** reason) +{ + struct secalgo_hash* h = zonemd_digest_init(hashalgo, reason); + if(!h) { + if(!*reason) + *reason = "digest init fail"; + return 0; + } + if(scheme == ZONEMD_SCHEME_SIMPLE) { + if(!zonemd_simple_collate(z, hashalgo, h, region, buf, reason)) { + if(!*reason) *reason = "scheme simple collate fail"; + secalgo_hash_delete(h); + return 0; + } + } + if(!zonemd_digest_finish(hashalgo, h, hash, hashlen, resultlen, + reason)) { + secalgo_hash_delete(h); + *reason = "digest finish fail"; + return 0; + } + secalgo_hash_delete(h); + return 1; +} + +int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, + int hashalgo, uint8_t* hash, size_t hashlen, struct regional* region, + struct sldns_buffer* buf, char** reason) +{ + uint8_t gen[512]; + size_t genlen = 0; + if(!zonemd_hashalgo_supported(hashalgo)) { + *reason = "unsupported algorithm"; + return 0; + } + if(!zonemd_scheme_supported(scheme)) { + *reason = "unsupported scheme"; + return 0; + } + if(hashlen < 12) { + /* the ZONEMD draft requires digests to fail if too small */ + *reason = "digest length too small, less than 12"; + return 0; + } + /* generate digest */ + if(!auth_zone_generate_zonemd_hash(z, scheme, hashalgo, gen, + sizeof(gen), &genlen, region, buf, reason)) { + /* reason filled in by zonemd hash routine */ + return 0; + } + /* check digest length */ + if(hashlen != genlen) { + *reason = "incorrect digest length"; + if(verbosity >= VERB_ALGO) { + verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", + scheme, hashalgo); + log_hex("ZONEMD should be ", gen, genlen); + log_hex("ZONEMD to check is", hash, hashlen); + } + return 0; + } + /* check digest */ + if(memcmp(hash, gen, genlen) != 0) { + *reason = "incorrect digest"; + if(verbosity >= VERB_ALGO) { + verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", + scheme, hashalgo); + log_hex("ZONEMD should be ", gen, genlen); + log_hex("ZONEMD to check is", hash, hashlen); + } + return 0; + } + return 1; +} + +/** log auth zone message with zone name in front. */ +static void auth_zone_log(uint8_t* name, enum verbosity_value level, + const char* format, ...) ATTR_FORMAT(printf, 3, 4); +static void auth_zone_log(uint8_t* name, enum verbosity_value level, + const char* format, ...) +{ + va_list args; + va_start(args, format); + if(verbosity >= level) { + char str[255+1]; + char msg[MAXSYSLOGMSGLEN]; + dname_str(name, str); + vsnprintf(msg, sizeof(msg), format, args); + verbose(level, "auth zone %s %s", str, msg); + } + va_end(args); +} + +/** ZONEMD, dnssec verify the rrset with the dnskey */ +static int zonemd_dnssec_verify_rrset(struct auth_zone* z, + struct module_env* env, struct module_stack* mods, + struct ub_packed_rrset_key* dnskey, struct auth_data* node, + struct auth_rrset* rrset, char** why_bogus) +{ + struct ub_packed_rrset_key pk; + enum sec_status sec; + struct val_env* ve; + int m; + m = modstack_find(mods, "validator"); + if(m == -1) { + auth_zone_log(z->name, VERB_ALGO, "zonemd dnssec verify: have " + "DNSKEY chain of trust, but no validator module"); + return 0; + } + ve = (struct val_env*)env->modinfo[m]; + + memset(&pk, 0, sizeof(pk)); + pk.entry.key = &pk; + pk.entry.data = rrset->data; + pk.rk.dname = node->name; + pk.rk.dname_len = node->namelen; + pk.rk.type = htons(rrset->type); + pk.rk.rrset_class = htons(z->dclass); + if(verbosity >= VERB_ALGO) { + char typestr[32]; + typestr[0]=0; + sldns_wire2str_type_buf(rrset->type, typestr, sizeof(typestr)); + auth_zone_log(z->name, VERB_ALGO, + "zonemd: verify %s RRset with DNSKEY", typestr); + } + sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, NULL, why_bogus, + LDNS_SECTION_ANSWER, NULL); + if(sec == sec_status_secure) { + return 1; + } + if(why_bogus) + auth_zone_log(z->name, VERB_ALGO, "DNSSEC verify was bogus: %s", *why_bogus); + return 0; +} + +/** check for nsec3, the RR with params equal, if bitmap has the type */ +static int nsec3_of_param_has_type(struct auth_rrset* nsec3, int algo, + size_t iter, uint8_t* salt, size_t saltlen, uint16_t rrtype) +{ + int i, count = (int)nsec3->data->count; + struct ub_packed_rrset_key pk; + memset(&pk, 0, sizeof(pk)); + pk.entry.data = nsec3->data; + for(i=0; i<count; i++) { + int rralgo; + size_t rriter, rrsaltlen; + uint8_t* rrsalt; + if(!nsec3_get_params(&pk, i, &rralgo, &rriter, &rrsalt, + &rrsaltlen)) + continue; /* no parameters, malformed */ + if(rralgo != algo || rriter != iter || rrsaltlen != saltlen) + continue; /* different parameters */ + if(saltlen != 0) { + if(rrsalt == NULL || salt == NULL) + continue; + if(memcmp(rrsalt, salt, saltlen) != 0) + continue; /* different salt parameters */ + } + if(nsec3_has_type(&pk, i, rrtype)) + return 1; + } + return 0; +} + +/** Verify the absence of ZONEMD with DNSSEC by checking NSEC, NSEC3 type flag. + * return false on failure, reason contains description of failure. */ +static int zonemd_check_dnssec_absence(struct auth_zone* z, + struct module_env* env, struct module_stack* mods, + struct ub_packed_rrset_key* dnskey, struct auth_data* apex, + char** reason, char** why_bogus) +{ + struct auth_rrset* nsec = NULL; + if(!apex) { + *reason = "zone has no apex domain but ZONEMD missing"; + return 0; + } + nsec = az_domain_rrset(apex, LDNS_RR_TYPE_NSEC); + if(nsec) { + struct ub_packed_rrset_key pk; + /* dnssec verify the NSEC */ + if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, apex, + nsec, why_bogus)) { + *reason = "DNSSEC verify failed for NSEC RRset"; + return 0; + } + /* check type bitmap */ + memset(&pk, 0, sizeof(pk)); + pk.entry.data = nsec->data; + if(nsec_has_type(&pk, LDNS_RR_TYPE_ZONEMD)) { + *reason = "DNSSEC NSEC bitmap says type ZONEMD exists"; + return 0; + } + auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC NSEC verification of absence of ZONEMD secure"); + } else { + /* NSEC3 perhaps ? */ + int algo; + size_t iter, saltlen; + uint8_t* salt; + struct auth_rrset* nsec3param = az_domain_rrset(apex, + LDNS_RR_TYPE_NSEC3PARAM); + struct auth_data* match; + struct auth_rrset* nsec3; + if(!nsec3param) { + *reason = "zone has no NSEC information but ZONEMD missing"; + return 0; + } + if(!az_nsec3_param(z, &algo, &iter, &salt, &saltlen)) { + *reason = "zone has no NSEC information but ZONEMD missing"; + return 0; + } + /* find the NSEC3 record */ + match = az_nsec3_find_exact(z, z->name, z->namelen, algo, + iter, salt, saltlen); + if(!match) { + *reason = "zone has no NSEC3 domain for the apex but ZONEMD missing"; + return 0; + } + nsec3 = az_domain_rrset(match, LDNS_RR_TYPE_NSEC3); + if(!nsec3) { + *reason = "zone has no NSEC3 RRset for the apex but ZONEMD missing"; + return 0; + } + /* dnssec verify the NSEC3 */ + if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, match, + nsec3, why_bogus)) { + *reason = "DNSSEC verify failed for NSEC3 RRset"; + return 0; + } + /* check type bitmap */ + if(nsec3_of_param_has_type(nsec3, algo, iter, salt, saltlen, + LDNS_RR_TYPE_ZONEMD)) { + *reason = "DNSSEC NSEC3 bitmap says type ZONEMD exists"; + return 0; + } + auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC NSEC3 verification of absence of ZONEMD secure"); + } + + return 1; +} + +/** Verify the SOA and ZONEMD DNSSEC signatures. + * return false on failure, reason contains description of failure. */ +static int zonemd_check_dnssec_soazonemd(struct auth_zone* z, + struct module_env* env, struct module_stack* mods, + struct ub_packed_rrset_key* dnskey, struct auth_data* apex, + struct auth_rrset* zonemd_rrset, char** reason, char** why_bogus) +{ + struct auth_rrset* soa; + if(!apex) { + *reason = "zone has no apex domain"; + return 0; + } + soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); + if(!soa) { + *reason = "zone has no SOA RRset"; + return 0; + } + if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, apex, soa, + why_bogus)) { + *reason = "DNSSEC verify failed for SOA RRset"; + return 0; + } + if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, apex, + zonemd_rrset, why_bogus)) { + *reason = "DNSSEC verify failed for ZONEMD RRset"; + return 0; + } + auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC verification of SOA and ZONEMD RRsets secure"); + return 1; +} + +/** + * Fail the ZONEMD verification. + * @param z: auth zone that fails. + * @param env: environment with config, to ignore failure or not. + * @param reason: failure string description. + * @param why_bogus: failure string for DNSSEC verification failure. + * @param result: strdup result in here if not NULL. + */ +static void auth_zone_zonemd_fail(struct auth_zone* z, struct module_env* env, + char* reason, char* why_bogus, char** result) +{ + char zstr[255+1]; + /* if fail: log reason, and depending on config also take action + * and drop the zone, eg. it is gone from memory, set zone_expired */ + dname_str(z->name, zstr); + if(!reason) reason = "verification failed"; + if(result) { + if(why_bogus) { + char res[1024]; + snprintf(res, sizeof(res), "%s: %s", reason, + why_bogus); + *result = strdup(res); + } else { + *result = strdup(reason); + } + if(!*result) log_err("out of memory"); + } else { + log_warn("auth zone %s: ZONEMD verification failed: %s", zstr, reason); + } + + if(env->cfg->zonemd_permissive_mode) { + verbose(VERB_ALGO, "zonemd-permissive-mode enabled, " + "not blocking zone %s", zstr); + return; + } + + /* expired means the zone gives servfail and is not used by + * lookup if fallback_enabled*/ + z->zone_expired = 1; +} + +/** + * Verify the zonemd with DNSSEC and hash check, with given key. + * @param z: auth zone. + * @param env: environment with config and temp buffers. + * @param mods: module stack with validator env for verification. + * @param dnskey: dnskey that we can use, or NULL. If nonnull, the key + * has been verified and is the start of the chain of trust. + * @param is_insecure: if true, the dnskey is not used, the zone is insecure. + * And dnssec is not used. It is DNSSEC secure insecure or not under + * a trust anchor. + * @param result: if not NULL result reason copied here. + */ +static void +auth_zone_verify_zonemd_with_key(struct auth_zone* z, struct module_env* env, + struct module_stack* mods, struct ub_packed_rrset_key* dnskey, + int is_insecure, char** result) +{ + char* reason = NULL, *why_bogus = NULL; + struct auth_data* apex = NULL; + struct auth_rrset* zonemd_rrset = NULL; + int zonemd_absent = 0, zonemd_absence_dnssecok = 0; + + /* see if ZONEMD is present or absent. */ + apex = az_find_name(z, z->name, z->namelen); + if(!apex) { + zonemd_absent = 1; + } else { + zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); + if(!zonemd_rrset || zonemd_rrset->data->count==0) { + zonemd_absent = 1; + zonemd_rrset = NULL; + } + } + + /* if no DNSSEC, done. */ + /* if no ZONEMD, and DNSSEC, use DNSKEY to verify NSEC or NSEC3 for + * zone apex. Check ZONEMD bit is turned off or else fail */ + /* if ZONEMD, and DNSSEC, check DNSSEC signature on SOA and ZONEMD, + * or else fail */ + if(!dnskey && !is_insecure) { + auth_zone_zonemd_fail(z, env, "DNSKEY missing", NULL, result); + return; + } else if(!zonemd_rrset && dnskey && !is_insecure) { + /* fetch, DNSSEC verify, and check NSEC/NSEC3 */ + if(!zonemd_check_dnssec_absence(z, env, mods, dnskey, apex, + &reason, &why_bogus)) { + auth_zone_zonemd_fail(z, env, reason, why_bogus, result); + return; + } + zonemd_absence_dnssecok = 1; + } else if(zonemd_rrset && dnskey && !is_insecure) { + /* check DNSSEC verify of SOA and ZONEMD */ + if(!zonemd_check_dnssec_soazonemd(z, env, mods, dnskey, apex, + zonemd_rrset, &reason, &why_bogus)) { + auth_zone_zonemd_fail(z, env, reason, why_bogus, result); + return; + } + } + + if(zonemd_absent && z->zonemd_reject_absence) { + auth_zone_zonemd_fail(z, env, "ZONEMD absent and that is not allowed by config", NULL, result); + return; + } + if(zonemd_absent && zonemd_absence_dnssecok) { + auth_zone_log(z->name, VERB_ALGO, "DNSSEC verified nonexistence of ZONEMD"); + if(result) { + *result = strdup("DNSSEC verified nonexistence of ZONEMD"); + if(!*result) log_err("out of memory"); + } + return; + } + if(zonemd_absent) { + auth_zone_log(z->name, VERB_ALGO, "no ZONEMD present"); + if(result) { + *result = strdup("no ZONEMD present"); + if(!*result) log_err("out of memory"); + } + return; + } + + /* check ZONEMD checksum and report or else fail. */ + if(!auth_zone_zonemd_check_hash(z, env, &reason)) { + auth_zone_zonemd_fail(z, env, reason, NULL, result); + return; + } + + /* success! log the success */ + auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); + if(result) { + *result = strdup("ZONEMD verification successful"); + if(!*result) log_err("out of memory"); + } +} + +/** + * verify the zone DNSKEY rrset from the trust anchor + * This is possible because the anchor is for the zone itself, and can + * thus apply straight to the zone DNSKEY set. + * @param z: the auth zone. + * @param env: environment with time and temp buffers. + * @param mods: module stack for validator environment for dnssec validation. + * @param anchor: trust anchor to use + * @param is_insecure: returned, true if the zone is securely insecure. + * @param why_bogus: if the routine fails, returns the failure reason. + * @param keystorage: where to store the ub_packed_rrset_key that is created + * on success. A pointer to it is returned on success. + * @return the dnskey RRset, reference to zone data and keystorage, or + * NULL on failure. + */ +static struct ub_packed_rrset_key* +zonemd_get_dnskey_from_anchor(struct auth_zone* z, struct module_env* env, + struct module_stack* mods, struct trust_anchor* anchor, + int* is_insecure, char** why_bogus, + struct ub_packed_rrset_key* keystorage) +{ + struct auth_data* apex; + struct auth_rrset* dnskey_rrset; + enum sec_status sec; + struct val_env* ve; + int m; + + apex = az_find_name(z, z->name, z->namelen); + if(!apex) { + *why_bogus = "have trust anchor, but zone has no apex domain for DNSKEY"; + return 0; + } + dnskey_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_DNSKEY); + if(!dnskey_rrset || dnskey_rrset->data->count==0) { + *why_bogus = "have trust anchor, but zone has no DNSKEY"; + return 0; + } + + m = modstack_find(mods, "validator"); + if(m == -1) { + *why_bogus = "have trust anchor, but no validator module"; + return 0; + } + ve = (struct val_env*)env->modinfo[m]; + + memset(keystorage, 0, sizeof(*keystorage)); + keystorage->entry.key = keystorage; + keystorage->entry.data = dnskey_rrset->data; + keystorage->rk.dname = apex->name; + keystorage->rk.dname_len = apex->namelen; + keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY); + keystorage->rk.rrset_class = htons(z->dclass); + auth_zone_log(z->name, VERB_QUERY, + "zonemd: verify DNSKEY RRset with trust anchor"); + sec = val_verify_DNSKEY_with_TA(env, ve, keystorage, anchor->ds_rrset, + anchor->dnskey_rrset, NULL, why_bogus, NULL); + regional_free_all(env->scratch); + if(sec == sec_status_secure) { + /* success */ + *is_insecure = 0; + return keystorage; + } else if(sec == sec_status_insecure) { + /* insecure */ + *is_insecure = 1; + } else { + /* bogus */ + *is_insecure = 0; + auth_zone_log(z->name, VERB_ALGO, + "zonemd: verify DNSKEY RRset with trust anchor failed: %s", *why_bogus); + } + return NULL; +} + +/** callback for ZONEMD lookup of DNSKEY */ +void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf, + enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited)) +{ + struct auth_zone* z = (struct auth_zone*)arg; + struct module_env* env; + char* reason = NULL; + struct ub_packed_rrset_key* dnskey = NULL; + int is_insecure = 0; + + lock_rw_wrlock(&z->lock); + env = z->zonemd_callback_env; + /* release the env variable so another worker can pick up the + * ZONEMD verification task if it wants to */ + z->zonemd_callback_env = NULL; + if(!env || env->outnet->want_to_quit || z->zone_deleted) { + lock_rw_unlock(&z->lock); + return; /* stop on quit */ + } + + /* process result */ + if(sec == sec_status_bogus) { + reason = why_bogus; + if(!reason) + reason = "lookup of DNSKEY was bogus"; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was bogus: %s", reason); + } else if(rcode == LDNS_RCODE_NOERROR) { + uint16_t wanted_qtype = LDNS_RR_TYPE_DNSKEY; + struct regional* temp = env->scratch; + struct query_info rq; + struct reply_info* rep; + memset(&rq, 0, sizeof(rq)); + rep = parse_reply_in_temp_region(buf, temp, &rq); + if(rep && rq.qtype == wanted_qtype && + query_dname_compare(z->name, rq.qname) == 0 && + FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { + /* parsed successfully */ + struct ub_packed_rrset_key* answer = + reply_find_answer_rrset(&rq, rep); + if(answer && sec == sec_status_secure) { + dnskey = answer; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was secure"); + } else if(sec == sec_status_secure && !answer) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY has no content, but is secure, treat as insecure"); + } else if(sec == sec_status_insecure) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was insecure"); + } else if(sec == sec_status_indeterminate) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was indeterminate, treat as insecure"); + } else { + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY has nodata"); + reason = "lookup of DNSKEY has nodata"; + } + } else if(rep && rq.qtype == wanted_qtype && + query_dname_compare(z->name, rq.qname) == 0 && + FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && + sec == sec_status_secure) { + /* secure nxdomain, so the zone is like some RPZ zone + * that does not exist in the wider internet, with + * a secure nxdomain answer outside of it. So we + * treat the zonemd zone without a dnssec chain of + * trust, as insecure. */ + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was secure NXDOMAIN, treat as insecure"); + } else if(rep && rq.qtype == wanted_qtype && + query_dname_compare(z->name, rq.qname) == 0 && + FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && + sec == sec_status_insecure) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was insecure NXDOMAIN, treat as insecure"); + } else if(rep && rq.qtype == wanted_qtype && + query_dname_compare(z->name, rq.qname) == 0 && + FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && + sec == sec_status_indeterminate) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was indeterminate NXDOMAIN, treat as insecure"); + } else { + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY has no answer"); + reason = "lookup of DNSKEY has no answer"; + } + } else { + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY failed"); + reason = "lookup of DNSKEY failed"; + } + + if(reason) { + auth_zone_zonemd_fail(z, env, reason, NULL, NULL); + lock_rw_unlock(&z->lock); + return; + } + + auth_zone_verify_zonemd_with_key(z, env, &env->mesh->mods, dnskey, + is_insecure, NULL); + regional_free_all(env->scratch); + lock_rw_unlock(&z->lock); +} + +/** lookup DNSKEY for ZONEMD verification */ +static int +zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env) +{ + struct query_info qinfo; + uint16_t qflags = BIT_RD; + struct edns_data edns; + sldns_buffer* buf = env->scratch_buffer; + + if(z->zonemd_callback_env) { + /* another worker is already working on the callback + * for the DNSKEY lookup for ZONEMD verification. + * We do not also have to do ZONEMD verification, let that + * worker do it */ + auth_zone_log(z->name, VERB_ALGO, + "zonemd needs lookup of DNSKEY and that already worked on by another worker"); + return 1; + } + + /* use mesh_new_callback to lookup the DNSKEY, + * and then wait for them to be looked up (in cache, or query) */ + qinfo.qname_len = z->namelen; + qinfo.qname = z->name; + qinfo.qclass = z->dclass; + qinfo.qtype = LDNS_RR_TYPE_DNSKEY; + qinfo.local_alias = NULL; + if(verbosity >= VERB_ALGO) { + char buf1[512]; + char buf2[LDNS_MAX_DOMAINLEN+1]; + dname_str(z->name, buf2); + snprintf(buf1, sizeof(buf1), "auth zone %s: lookup DNSKEY " + "for zonemd verification", buf2); + log_query_info(VERB_ALGO, buf1, &qinfo); + } + edns.edns_present = 1; + edns.ext_rcode = 0; + edns.edns_version = 0; + edns.bits = EDNS_DO; + edns.opt_list = NULL; + if(sldns_buffer_capacity(buf) < 65535) + edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); + else edns.udp_size = 65535; + + /* store the worker-specific module env for the callback. + * We can then reference this when the callback executes */ + z->zonemd_callback_env = env; + /* the callback can be called straight away */ + lock_rw_unlock(&z->lock); + if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, + &auth_zonemd_dnskey_lookup_callback, z)) { + lock_rw_wrlock(&z->lock); + log_err("out of memory lookup up dnskey for zonemd"); + return 0; + } + lock_rw_wrlock(&z->lock); + return 1; +} + +void auth_zone_verify_zonemd(struct auth_zone* z, struct module_env* env, + struct module_stack* mods, char** result, int offline, int only_online) +{ + char* reason = NULL, *why_bogus = NULL; + struct trust_anchor* anchor = NULL; + struct ub_packed_rrset_key* dnskey = NULL; + struct ub_packed_rrset_key keystorage; + int is_insecure = 0; + /* verify the ZONEMD if present. + * If not present check if absence is allowed by DNSSEC */ + if(!z->zonemd_check) + return; + + /* if zone is under a trustanchor */ + /* is it equal to trustanchor - get dnskey's verified */ + /* else, find chain of trust by fetching DNSKEYs lookup for zone */ + /* result if that, if insecure, means no DNSSEC for the ZONEMD, + * otherwise we have the zone DNSKEY for the DNSSEC verification. */ + if(env->anchors) + anchor = anchors_lookup(env->anchors, z->name, z->namelen, + z->dclass); + if(anchor && anchor->numDS == 0 && anchor->numDNSKEY == 0) { + /* domain-insecure trust anchor for unsigned zones */ + lock_basic_unlock(&anchor->lock); + if(only_online) + return; + dnskey = NULL; + is_insecure = 1; + } else if(anchor && query_dname_compare(z->name, anchor->name) == 0) { + if(only_online) { + lock_basic_unlock(&anchor->lock); + return; + } + /* equal to trustanchor, no need for online lookups */ + dnskey = zonemd_get_dnskey_from_anchor(z, env, mods, anchor, + &is_insecure, &why_bogus, &keystorage); + lock_basic_unlock(&anchor->lock); + if(!dnskey && !reason && !is_insecure) { + reason = "verify DNSKEY RRset with trust anchor failed"; + } + } else if(anchor) { + lock_basic_unlock(&anchor->lock); + /* perform online lookups */ + if(offline) + return; + /* setup online lookups, and wait for them */ + if(zonemd_lookup_dnskey(z, env)) { + /* wait for the lookup */ + return; + } + reason = "could not lookup DNSKEY for chain of trust"; + } else { + /* the zone is not under a trust anchor */ + if(only_online) + return; + dnskey = NULL; + is_insecure = 1; + } + + if(reason) { + auth_zone_zonemd_fail(z, env, reason, why_bogus, result); + return; + } + + auth_zone_verify_zonemd_with_key(z, env, mods, dnskey, is_insecure, + result); + regional_free_all(env->scratch); +} + +void auth_zones_pickup_zonemd_verify(struct auth_zones* az, + struct module_env* env) +{ + struct auth_zone key; + uint8_t savezname[255+1]; + size_t savezname_len; + struct auth_zone* z; + key.node.key = &key; + lock_rw_rdlock(&az->lock); + RBTREE_FOR(z, struct auth_zone*, &az->ztree) { + lock_rw_wrlock(&z->lock); + if(!z->zonemd_check) { + lock_rw_unlock(&z->lock); + continue; + } + key.dclass = z->dclass; + key.namelabs = z->namelabs; + if(z->namelen > sizeof(savezname)) { + lock_rw_unlock(&z->lock); + log_err("auth_zones_pickup_zonemd_verify: zone name too long"); + continue; + } + savezname_len = z->namelen; + memmove(savezname, z->name, z->namelen); + lock_rw_unlock(&az->lock); + auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 1); + lock_rw_unlock(&z->lock); + lock_rw_rdlock(&az->lock); + /* find the zone we had before, it is not deleted, + * because we have a flag for that that is processed at + * apply_cfg time */ + key.namelen = savezname_len; + key.name = savezname; + z = (struct auth_zone*)rbtree_search(&az->ztree, &key); + if(!z) + break; + } + lock_rw_unlock(&az->lock); +} |
