From: W.C.A. Wijngaards Date: Tue, 6 Oct 2020 15:07:24 +0000 (+0200) Subject: zonemd, loop over zone and canonicalize data, test call in unit test. X-Git-Tag: release-1.13.2rc1~269^2~73 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3163a93121cae6826484c95db4c2ac8d88d94073;p=thirdparty%2Funbound.git zonemd, loop over zone and canonicalize data, test call in unit test. --- diff --git a/services/authzone.c b/services/authzone.c index a26d1003a..b4217bd55 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -68,6 +68,7 @@ #include "sldns/keyraw.h" #include "validator/val_nsec3.h" #include "validator/val_secalgo.h" +#include "validator/val_sigcrypt.h" #include /** bytes to use for NSEC3 hash buffer. 20 for sha1 */ @@ -6965,3 +6966,366 @@ compare_serial(uint32_t a, uint32_t b) return 1; } } + +/** returns true if a zonemd hash algo is supported */ +static int zonemd_hashalgo_supported(int hashalgo) +{ + if(hashalgo == 1) return 1; + if(hashalgo == 2) return 1; + return 0; +} + +/** returns true if a zonemd scheme is supported */ +static int zonemd_scheme_supported(int scheme) +{ + if(scheme == 1) return 1; + return 0; +} + +/** initialize hash for hashing with zonemd hash algo */ +static void* zonemd_digest_init(int hashalgo) +{ + if(hashalgo == 1) { + /* sha384 */ + //return secalgo_digest_start_sha384(); + } else if(hashalgo == 2) { + /* sha512 */ + //return secalgo_digest_start_sha512(); + } + /* unknown hash algo */ + return NULL; +} + +/** 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+1 >= 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)+1 >= 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, void* hash, + 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 */ + 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; jdata->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; itype == 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; itype == LDNS_RR_TYPE_ZONEMD && + query_dname_compare(z->name, node->name)==0) { + /* omit RRSIGs over type ZONEMD at apex */ + continue; + } + for(j = 0; jdata->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 need 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; jdata->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, void* hash, + 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(rrset->type); + 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 */ + return 1; +} + +/** collate a domain's rrsets using the simple scheme */ +static int zonemd_simple_domain(struct auth_zone* z, void* hash, + 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; itype == 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, hash, node, rrlist[i], + rrlist, rrnum, region, buf, reason)) + return 0; + } else if(!zonemd_simple_rrset(z, hash, 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, void* hash, + 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, hash, 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) +{ + void* h = zonemd_digest_init(hashalgo); + if(!h) { + *reason = "digest init fail"; + return 0; + } + if(scheme == 1) { + if(!zonemd_simple_collate(z, h, region, buf, reason)) { + if(!*reason) *reason = "scheme simple collate fail"; + return 0; + } + } + /* + if(!zonemd_digest_finish(hashalgo, hash, hashlen, resultlen)) { + *reason = "digest finish fail"; + return 0; + } + */ + 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"; + return 0; + } + /* check digest */ + if(memcmp(hash, gen, genlen) != 0) { + *reason = "incorrect digest"; + return 0; + } + return 1; +} diff --git a/services/authzone.h b/services/authzone.h index 3d94f30d6..813fc0e87 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -685,4 +685,39 @@ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, */ int compare_serial(uint32_t a, uint32_t b); +/** + * Generate ZONEMD digest for the auth zone. + * @param z: the auth zone to digest. + * omits zonemd at apex and its RRSIG from the digest. + * @param scheme: the collation scheme to use. Numbers as defined for ZONEMD. + * @param hashalgo: the hash algo, from the registry defined for ZONEMD type. + * @param hash: the result buffer. + * @param buflen: size of the result buffer, must be large enough. or the + * routine fails. + * @param resultlen: size of the hash in the result buffer of the result. + * @param region: temp region for allocs during canonicalisation. + * @param buf: temp buffer during canonicalisation. + * @param reason: failure reason, returns a string, NULL on success. + * @return false on failure. + */ +int auth_zone_generate_zonemd_hash(struct auth_zone* z, int scheme, + int hashalgo, uint8_t* hash, size_t buflen, size_t* resultlen, + struct regional* region, struct sldns_buffer* buf, char** reason); + +/** + * Check ZONEMD digest for the auth zone. + * @param z: auth zone to digest. + * @param scheme: zonemd scheme. + * @param hashalgo: zonemd hash algorithm. + * @param hash: the hash to check. + * @param buflen: length of hash buffer. + * @param region: temp region for allocs during canonicalisation. + * @param buf: temp buffer during canonicalisation. + * @param reason: string returned with failure reason. + * @return false on failure. + */ +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); + #endif /* SERVICES_AUTHZONE_H */ diff --git a/testcode/unitauth.c b/testcode/unitauth.c index 4b3410c9e..184573ab6 100644 --- a/testcode/unitauth.c +++ b/testcode/unitauth.c @@ -517,8 +517,8 @@ del_tmp_file(char* fname) } /** Add zone from file for testing */ -static struct auth_zone* -addzone(struct auth_zones* az, const char* name, char* fname) +struct auth_zone* +authtest_addzone(struct auth_zones* az, const char* name, char* fname) { struct auth_zone* z; size_t nmlen; @@ -593,7 +593,7 @@ check_read_exact(const char* name, const char* zone) az = auth_zones_create(); unit_assert(az); - z = addzone(az, name, fname); + z = authtest_addzone(az, name, fname); unit_assert(z); outf = create_tmp_file(NULL); if(!auth_zone_write_file(z, outf)) { @@ -844,7 +844,7 @@ check_queries(const char* name, const char* zone, struct q_ans* queries) fname = create_tmp_file(zone); az = auth_zones_create(); if(!az) fatal_exit("out of memory"); - z = addzone(az, name, fname); + z = authtest_addzone(az, name, fname); if(!z) fatal_exit("could not read zone for queries test"); del_tmp_file(fname); diff --git a/testcode/unitmain.c b/testcode/unitmain.c index a42be424e..df560a466 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -839,6 +839,52 @@ static void respip_test(void) respip_conf_actions_test(); } +#include "services/authzone.h" +#include "util/data/dname.h" +#include "util/regional.h" +/** Add zone from file for testing */ +struct auth_zone* authtest_addzone(struct auth_zones* az, const char* name, + char* fname); +/** zonemd unit tests */ +static void zonemd_test(void) +{ + uint8_t zonemd_hash[512]; + struct auth_zones* az; + struct auth_zone* z; + int scheme = 1, hashalgo = 2; + size_t hashlen = 0; + int result; + char* reason = NULL; + struct regional* region = NULL; + struct sldns_buffer* buf = NULL; + unit_show_feature("zonemd"); + region = regional_create(); + unit_assert(region); + buf = sldns_buffer_new(65535); + unit_assert(buf); + az = auth_zones_create(); + unit_assert(az); + z = authtest_addzone(az, "example.org", "testdata/zonemd.example1.zone"); + unit_assert(z); + + /* zonemd test on zone */ + result = auth_zone_generate_zonemd_hash(z, scheme, hashalgo, + zonemd_hash, sizeof(zonemd_hash), &hashlen, region, buf, + &reason); + if(reason) printf("zonemd failure reason: %s\n", reason); + unit_assert(result); + if(1) { + char zname[255+1]; + dname_str(z->name, zname); + printf("zonemd generated for %s in %s with scheme=%d, hashalgo=%d\n", zname, z->zonefile, scheme, hashalgo); + log_hex("digest", zonemd_hash, hashlen); + } + + auth_zones_delete(az); + regional_destroy(region); + sldns_buffer_free(buf); +} + void unit_show_func(const char* file, const char* func) { printf("test %s:%s\n", file, func); @@ -889,6 +935,7 @@ main(int argc, char* argv[]) fatal_exit("could not init NSS"); #endif /* HAVE_SSL or HAVE_NSS*/ checklock_start(); + zonemd_test(); authzone_test(); neg_test(); rnd_test(); diff --git a/testdata/zonemd.example1.zone b/testdata/zonemd.example1.zone new file mode 100644 index 000000000..b1a44895f --- /dev/null +++ b/testdata/zonemd.example1.zone @@ -0,0 +1,4 @@ +example.org. IN SOA ns.example.org. hostmaster.example.org. 200154054 28800 7200 604800 3600 +example.org. IN NS ns.example.org. +www.example.org. IN A 127.0.0.1 +ns.example.org. IN A 127.0.0.1 diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index de730f681..10cf2caee 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -1199,6 +1199,59 @@ rrset_canonical(struct regional* region, sldns_buffer* buf, return 1; } +int +rrset_canonicalize_to_buffer(struct regional* region, sldns_buffer* buf, + struct ub_packed_rrset_key* k) +{ + struct rbtree_type* sortree = NULL; + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + uint8_t* can_owner = NULL; + size_t can_owner_len = 0; + struct canon_rr* walk; + struct canon_rr* rrs; + + sortree = (struct rbtree_type*)regional_alloc(region, + sizeof(rbtree_type)); + if(!sortree) + return 0; + if(d->count > RR_COUNT_MAX) + return 0; /* integer overflow protection */ + rrs = regional_alloc(region, sizeof(struct canon_rr)*d->count); + if(!rrs) { + return 0; + } + rbtree_init(sortree, &canonical_tree_compare); + canonical_sort(k, d, sortree, rrs); + + sldns_buffer_clear(buf); + RBTREE_FOR(walk, struct canon_rr*, sortree) { + /* see if there is enough space left in the buffer */ + if(sldns_buffer_remaining(buf) < can_owner_len + 2 + 2 + 4 + + d->rr_len[walk->rr_idx]) { + log_err("verify: failed to canonicalize, " + "rrset too big"); + return 0; + } + /* determine canonical owner name */ + if(can_owner) + sldns_buffer_write(buf, can_owner, can_owner_len); + else { + can_owner = sldns_buffer_current(buf); + sldns_buffer_write(buf, k->rk.dname, k->rk.dname_len); + query_dname_tolower(can_owner); + can_owner_len = k->rk.dname_len; + } + sldns_buffer_write(buf, &k->rk.type, 2); + sldns_buffer_write(buf, &k->rk.rrset_class, 2); + sldns_buffer_write_u32(buf, d->rr_ttl[walk->rr_idx]); + sldns_buffer_write(buf, d->rr_data[walk->rr_idx], + d->rr_len[walk->rr_idx]); + canonicalize_rdata(buf, k, d->rr_len[walk->rr_idx]); + } + sldns_buffer_flip(buf); + return 1; +} + /** pretty print rrsig error with dates */ static void sigdate_error(const char* str, int32_t expi, int32_t incep, int32_t now) diff --git a/validator/val_sigcrypt.h b/validator/val_sigcrypt.h index 755a1d6e1..23ca1d91b 100644 --- a/validator/val_sigcrypt.h +++ b/validator/val_sigcrypt.h @@ -334,4 +334,16 @@ int canonical_tree_compare(const void* k1, const void* k2); int rrset_canonical_equal(struct regional* region, struct ub_packed_rrset_key* k1, struct ub_packed_rrset_key* k2); +/** + * Canonicalize an rrset into the buffer. For an auth zone record, so + * this does not use a signature, or the RRSIG TTL or the wildcard label + * count from the RRSIG. + * @param region: temporary region. + * @param buf: the buffer to use. + * @param k: the rrset to insert. + * @return false on alloc error. + */ +int rrset_canonicalize_to_buffer(struct regional* region, + struct sldns_buffer* buf, struct ub_packed_rrset_key* k); + #endif /* VALIDATOR_VAL_SIGCRYPT_H */