zonemd, loop over zone and canonicalize data, test call in unit test.

This commit is contained in:
W.C.A. Wijngaards 2020-10-06 17:07:24 +02:00
parent ca31ea7dc8
commit 3163a93121
7 changed files with 519 additions and 4 deletions

View File

@ -68,6 +68,7 @@
#include "sldns/keyraw.h" #include "sldns/keyraw.h"
#include "validator/val_nsec3.h" #include "validator/val_nsec3.h"
#include "validator/val_secalgo.h" #include "validator/val_secalgo.h"
#include "validator/val_sigcrypt.h"
#include <ctype.h> #include <ctype.h>
/** bytes to use for NSEC3 hash buffer. 20 for sha1 */ /** bytes to use for NSEC3 hash buffer. 20 for sha1 */
@ -6965,3 +6966,366 @@ compare_serial(uint32_t a, uint32_t b)
return 1; 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; 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 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; 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, 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; 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, 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;
}

View File

@ -685,4 +685,39 @@ void auth_xfer_transfer_lookup_callback(void* arg, int rcode,
*/ */
int compare_serial(uint32_t a, uint32_t b); 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 */ #endif /* SERVICES_AUTHZONE_H */

View File

@ -517,8 +517,8 @@ del_tmp_file(char* fname)
} }
/** Add zone from file for testing */ /** Add zone from file for testing */
static struct auth_zone* struct auth_zone*
addzone(struct auth_zones* az, const char* name, char* fname) authtest_addzone(struct auth_zones* az, const char* name, char* fname)
{ {
struct auth_zone* z; struct auth_zone* z;
size_t nmlen; size_t nmlen;
@ -593,7 +593,7 @@ check_read_exact(const char* name, const char* zone)
az = auth_zones_create(); az = auth_zones_create();
unit_assert(az); unit_assert(az);
z = addzone(az, name, fname); z = authtest_addzone(az, name, fname);
unit_assert(z); unit_assert(z);
outf = create_tmp_file(NULL); outf = create_tmp_file(NULL);
if(!auth_zone_write_file(z, outf)) { 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); fname = create_tmp_file(zone);
az = auth_zones_create(); az = auth_zones_create();
if(!az) fatal_exit("out of memory"); 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"); if(!z) fatal_exit("could not read zone for queries test");
del_tmp_file(fname); del_tmp_file(fname);

View File

@ -839,6 +839,52 @@ static void respip_test(void)
respip_conf_actions_test(); 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) void unit_show_func(const char* file, const char* func)
{ {
printf("test %s:%s\n", file, func); printf("test %s:%s\n", file, func);
@ -889,6 +935,7 @@ main(int argc, char* argv[])
fatal_exit("could not init NSS"); fatal_exit("could not init NSS");
#endif /* HAVE_SSL or HAVE_NSS*/ #endif /* HAVE_SSL or HAVE_NSS*/
checklock_start(); checklock_start();
zonemd_test();
authzone_test(); authzone_test();
neg_test(); neg_test();
rnd_test(); rnd_test();

4
testdata/zonemd.example1.zone vendored Normal file
View File

@ -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

View File

@ -1199,6 +1199,59 @@ rrset_canonical(struct regional* region, sldns_buffer* buf,
return 1; 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 */ /** pretty print rrsig error with dates */
static void static void
sigdate_error(const char* str, int32_t expi, int32_t incep, int32_t now) sigdate_error(const char* str, int32_t expi, int32_t incep, int32_t now)

View File

@ -334,4 +334,16 @@ int canonical_tree_compare(const void* k1, const void* k2);
int rrset_canonical_equal(struct regional* region, int rrset_canonical_equal(struct regional* region,
struct ub_packed_rrset_key* k1, struct ub_packed_rrset_key* k2); 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 */ #endif /* VALIDATOR_VAL_SIGCRYPT_H */