aboutsummaryrefslogtreecommitdiff
path: root/services/authzone.c
diff options
context:
space:
mode:
Diffstat (limited to 'services/authzone.c')
-rw-r--r--services/authzone.c1405
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);
+}