From: Willem Toorop Date: Mon, 16 Nov 2020 22:14:59 +0000 (+0100) Subject: More generalized zonemd functions + verification X-Git-Tag: 1.8.0-rc.1~45^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b72f8eb8ff0e70e10b4526a6f1f24b7ec889deeb;p=thirdparty%2Fldns.git More generalized zonemd functions + verification --- diff --git a/buffer.c b/buffer.c index e008831f..8f6aab59 100644 --- a/buffer.c +++ b/buffer.c @@ -63,6 +63,7 @@ ldns_buffer_set_capacity(ldns_buffer *buffer, size_t capacity) ldns_buffer_invariant(buffer); assert(buffer->_position <= capacity); + assert(!buffer->_fixed); data = (uint8_t *) LDNS_XREALLOC(buffer->_data, uint8_t, capacity); if (!data) { @@ -79,7 +80,6 @@ bool ldns_buffer_reserve(ldns_buffer *buffer, size_t amount) { ldns_buffer_invariant(buffer); - assert(!buffer->_fixed); if (buffer->_capacity < buffer->_position + amount) { size_t new_capacity = buffer->_capacity * 3 / 2; diff --git a/dnssec_sign.c b/dnssec_sign.c index c7623f36..07754b19 100644 --- a/dnssec_sign.c +++ b/dnssec_sign.c @@ -24,6 +24,9 @@ #endif #endif /* HAVE_SSL */ +#define LDNS_SIGN_WITH_ZONEMD ( LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA384 \ + | LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA512 ) + ldns_rr * ldns_create_empty_rrsig(const ldns_rr_list *rrset, const ldns_key *current_key) @@ -1365,6 +1368,8 @@ ldns_dnssec_zone_sign(ldns_dnssec_zone *zone, return ldns_dnssec_zone_sign_flg(zone, new_rrs, key_list, func, arg, 0); } +ldns_status dnssec_zone_equip_zonemd( + ldns_dnssec_zone *zone, int flags, ldns_key_list *key_list); ldns_status ldns_dnssec_zone_sign_flg(ldns_dnssec_zone *zone, ldns_rr_list *new_rrs, @@ -1374,17 +1379,33 @@ ldns_dnssec_zone_sign_flg(ldns_dnssec_zone *zone, int flags) { ldns_status result = LDNS_STATUS_OK; + ldns_dnssec_rrsets zonemd_rrset; + bool zonemd_added = false; if (!zone || !new_rrs || !key_list) { return LDNS_STATUS_ERR; } - + if (flags & LDNS_SIGN_WITH_ZONEMD) { + ldns_dnssec_rrsets **rrsets_ref = &zone->soa->rrsets; + + while (*rrsets_ref + && (*rrsets_ref)->type < LDNS_RR_TYPE_ZONEMD) + rrsets_ref = &(*rrsets_ref)->next; + if (!*rrsets_ref + || (*rrsets_ref)->type > LDNS_RR_TYPE_ZONEMD) { + zonemd_rrset.rrs = NULL; + zonemd_rrset.type = LDNS_RR_TYPE_ZONEMD; + zonemd_rrset.signatures = NULL; + zonemd_rrset.next = *rrsets_ref; + *rrsets_ref = &zonemd_rrset; + zonemd_added = true; + } + } /* zone is already sorted */ result = ldns_dnssec_zone_mark_glue(zone); if (result != LDNS_STATUS_OK) { return result; } - /* check whether we need to add nsecs */ if (zone->names && !((ldns_dnssec_name *)zone->names->root->data)->nsec) { result = ldns_dnssec_zone_create_nsecs(zone, new_rrs); @@ -1392,7 +1413,6 @@ ldns_dnssec_zone_sign_flg(ldns_dnssec_zone *zone, return result; } } - result = ldns_dnssec_zone_create_rrsigs_flg(zone, new_rrs, key_list, @@ -1400,7 +1420,18 @@ ldns_dnssec_zone_sign_flg(ldns_dnssec_zone *zone, arg, flags); - return result; + if (zonemd_added) { + ldns_dnssec_rrsets **rrsets_ref + = &zone->soa->rrsets; + + while (*rrsets_ref + && (*rrsets_ref)->type < LDNS_RR_TYPE_ZONEMD) + rrsets_ref = &(*rrsets_ref)->next; + *rrsets_ref = zonemd_rrset.next; + } + return flags & LDNS_SIGN_WITH_ZONEMD + ? dnssec_zone_equip_zonemd(zone, flags, key_list) + : result; } ldns_status @@ -1436,6 +1467,8 @@ ldns_dnssec_zone_sign_nsec3_flg_mkmap(ldns_dnssec_zone *zone, { ldns_rr *nsec3, *nsec3param; ldns_status result = LDNS_STATUS_OK; + bool zonemd_added = false; + ldns_dnssec_rrsets zonemd_rrset; /* zone is already sorted */ result = ldns_dnssec_zone_mark_glue(zone); @@ -1481,6 +1514,23 @@ ldns_dnssec_zone_sign_nsec3_flg_mkmap(ldns_dnssec_zone *zone, } ldns_rr_list_push_rr(new_rrs, nsec3param); } + if (signflags & LDNS_SIGN_WITH_ZONEMD) { + ldns_dnssec_rrsets **rrsets_ref + = &zone->soa->rrsets; + + while (*rrsets_ref + && (*rrsets_ref)->type < LDNS_RR_TYPE_ZONEMD) + rrsets_ref = &(*rrsets_ref)->next; + if (!*rrsets_ref + || (*rrsets_ref)->type > LDNS_RR_TYPE_ZONEMD) { + zonemd_rrset.rrs = NULL; + zonemd_rrset.type = LDNS_RR_TYPE_ZONEMD; + zonemd_rrset.signatures = NULL; + zonemd_rrset.next = *rrsets_ref; + *rrsets_ref = &zonemd_rrset; + zonemd_added = true; + } + } result = ldns_dnssec_zone_create_nsec3s_mkmap(zone, new_rrs, algorithm, @@ -1489,6 +1539,15 @@ ldns_dnssec_zone_sign_nsec3_flg_mkmap(ldns_dnssec_zone *zone, salt_length, salt, map); + if (zonemd_added) { + ldns_dnssec_rrsets **rrsets_ref + = &zone->soa->rrsets; + + while (*rrsets_ref + && (*rrsets_ref)->type < LDNS_RR_TYPE_ZONEMD) + rrsets_ref = &(*rrsets_ref)->next; + *rrsets_ref = zonemd_rrset.next; + } if (result != LDNS_STATUS_OK) { return result; } @@ -1501,8 +1560,12 @@ ldns_dnssec_zone_sign_nsec3_flg_mkmap(ldns_dnssec_zone *zone, arg, signflags); } + if (result || !zone->names) + return result; - return result; + return signflags & LDNS_SIGN_WITH_ZONEMD + ? dnssec_zone_equip_zonemd(zone, signflags, key_list) + : result; } ldns_status diff --git a/dnssec_zone.c b/dnssec_zone.c index e089754d..c307510d 100644 --- a/dnssec_zone.c +++ b/dnssec_zone.c @@ -1201,3 +1201,653 @@ ldns_dnssec_zone_is_nsec3_optout(const ldns_dnssec_zone* zone) } return false; } + +/* + * Stuff for calculating and verifying zone digests + */ +static ldns_status +rr_list2dnssec_rrs(ldns_rr_list *rr_list, ldns_dnssec_rrs **rrs) +{ + ldns_rr *rr = NULL; + + if (!rr_list || !rrs) + return LDNS_STATUS_NULL; + + if (ldns_rr_list_rr_count(rr_list) == 0) + return LDNS_STATUS_OK; + + if (!*rrs) { + if (!(*rrs = ldns_dnssec_rrs_new())) + return LDNS_STATUS_MEM_ERR; + (*rrs)->rr = ldns_rr_list_pop_rr(rr_list); + + } + while ((rr = ldns_rr_list_pop_rr(rr_list))) { + ldns_status st; + + if ((st = ldns_dnssec_rrs_add_rr(*rrs, rr))) { + ldns_rr_list_push_rr(rr_list, rr); + return st; + } + } + return LDNS_STATUS_OK; +} + +typedef enum dnssec_zone_rr_iter_state { + DNSSEC_ZONE_RR_ITER_LT_RRSIG + , DNSSEC_ZONE_RR_ITER_RRSIGs_NO_NSEC + , DNSSEC_ZONE_RR_ITER_REST + , DNSSEC_ZONE_RR_ITER_RRSIGs_NSEC + , DNSSEC_ZONE_RR_ITER_RRSIGs_NSEC_REST + , DNSSEC_ZONE_RR_ITER_NSEC3 + , DNSSEC_ZONE_RR_ITER_FINI +} dnssec_zone_rr_iter_state; + +typedef struct dnssec_zone_rr_iter { + ldns_dnssec_zone *zone; + ldns_rbnode_t *node; + ldns_dnssec_name *name; + ldns_dnssec_rrsets *rrsets; + ldns_dnssec_rrs *rrs; + ldns_dnssec_rrsets *rrsets4rrsigs; + ldns_rbnode_t *nsec3_node; + ldns_dnssec_name *nsec3_name; + dnssec_zone_rr_iter_state state; +} dnssec_zone_rr_iter; + +INLINE void +dnssec_zone_rr_iter_set_state_for_next_name(dnssec_zone_rr_iter *i) +{ + if(!i->name) { + if (!i->nsec3_name) + i->state = DNSSEC_ZONE_RR_ITER_FINI; + else { + i->rrs = i->nsec3_name->nsec_signatures; + i->state = DNSSEC_ZONE_RR_ITER_NSEC3; + } + } else if (!i->nsec3_name) { + i->rrsets = i->name->rrsets; + i->state = DNSSEC_ZONE_RR_ITER_LT_RRSIG; + + } else if (ldns_dname_compare( ldns_rr_owner(i->nsec3_name->nsec) + , (ldns_rdf *)i->node->key) < 0) { + i->rrs = i->nsec3_name->nsec_signatures; + i->state = DNSSEC_ZONE_RR_ITER_NSEC3; + } else { + i->rrsets = i->name->rrsets; + i->state = DNSSEC_ZONE_RR_ITER_LT_RRSIG; + } +} + +/** + * Iterate over the RR's in the ldns_dnssec_zone in canonical order. + * There are three possible paths through the RR's in a ldns_dnssec_name. + * + * 1. There is no NSEC: + * + * 1.1. All the RRs in the name->rrsets with type < RRSIG, + * state: DNSSEC_ZONE_RR_ITER_LT_RRSIG + * + * 1.2. Then all the RRSIGs from name->rrsets (likely none) + * state: DNSSEC_ZONE_RR_ITER_RRSIGs_NO_NSEC + * + * 1.3. Finally the remaining RRs in name->rrsets (type > RRSIG) + * state: DNSSEC_ZONE_RR_ITER_REST + * + * + * 2. There is a NSEC of type NSEC with this name: + * + * 2.1. All the RRs in the name->rrsets with type < RRSIG, + * state: DNSSEC_ZONE_RR_ITER_LT_RRSIG + * + * 2.2. Then all the RRSIGs from name->rrsets with type < NSEC + * state: DNSSEC_ZONE_RR_ITER_RRSIGs_NO_NSEC + * + * 2.3. Then the signatures of the NSEC RR, followed by + * the signatures of the remaining name->rrsets (type > NSEC), + * followed by the NSEC rr. + * state: DNSSEC_ZONE_RR_ITER_RRSIGs_NO_NSEC + * + * 2.4. Finally the remaining RRs in name->rrsets (type > RRSIG) + * state: DNSSEC_ZONE_RR_ITER_REST + * + * + * 3. There is a NSEC of type NSEC3 for this name: + * + * 3.1. If the NSEC3 name is before the name for other RRsets in the zone, + * Then all signatures of the NSEC3 RR, followed by the NSEC3 + * state: DNSSEC_ZONE_RR_ITER_NSEC3 + * + * otherwise follow path for "no NSEC" for the name for other RRsets + */ +static ldns_rr * +dnssec_zone_rr_iter_next(dnssec_zone_rr_iter *i) +{ + ldns_rr *nsec3; + + for (;;) { + if (i->rrs) { + ldns_rr *rr = i->rrs->rr; + i->rrs = i->rrs->next; + return rr; + } + switch (i->state) { + case DNSSEC_ZONE_RR_ITER_LT_RRSIG: + if (i->rrsets + && i->rrsets->type < LDNS_RR_TYPE_RRSIG) { + + i->rrs = i->rrsets->rrs; + i->rrsets = i->rrsets->next; + break; + } + i->rrsets4rrsigs = i->name->rrsets; + if (i->name->nsec && ldns_rr_get_type(i->name->nsec) + == LDNS_RR_TYPE_NSEC) { + + i->state = DNSSEC_ZONE_RR_ITER_RRSIGs_NSEC; + break; + } + i->state = DNSSEC_ZONE_RR_ITER_RRSIGs_NO_NSEC; + /* fallthrough */ + + case DNSSEC_ZONE_RR_ITER_RRSIGs_NO_NSEC: + if (i->rrsets4rrsigs) { + i->rrs = i->rrsets4rrsigs->signatures; + i->rrsets4rrsigs = i->rrsets4rrsigs->next; + break; + } + i->state = DNSSEC_ZONE_RR_ITER_REST; + /* fallthrough */ + + case DNSSEC_ZONE_RR_ITER_REST: + if (i->rrsets) { + i->rrs = i->rrsets->rrs; + i->rrsets = i->rrsets->next; + break; + } + /* next name */ + i->node = ldns_rbtree_next(i->node); + i->name = i->node == LDNS_RBTREE_NULL ? NULL + : (ldns_dnssec_name *)i->node->data; + + dnssec_zone_rr_iter_set_state_for_next_name(i); + break; + + case DNSSEC_ZONE_RR_ITER_RRSIGs_NSEC: + if (i->rrsets4rrsigs + && i->rrsets4rrsigs->type < LDNS_RR_TYPE_NSEC) { + + i->rrs = i->rrsets4rrsigs->signatures; + i->rrsets4rrsigs = i->rrsets4rrsigs->next; + break; + } + i->state = DNSSEC_ZONE_RR_ITER_RRSIGs_NSEC_REST; + i->rrs = i->name->nsec_signatures; + break; + + case DNSSEC_ZONE_RR_ITER_RRSIGs_NSEC_REST: + if (i->rrsets4rrsigs) { + i->rrs = i->rrsets4rrsigs->signatures; + i->rrsets4rrsigs = i->rrsets4rrsigs->next; + break; + } + i->state = DNSSEC_ZONE_RR_ITER_REST; + return i->name->nsec; + + case DNSSEC_ZONE_RR_ITER_NSEC3: + nsec3 = i->nsec3_name->nsec; + + /* next nsec3 */ + do { + i->nsec3_node + = ldns_rbtree_next(i->nsec3_node); + i->nsec3_name + = i->nsec3_node == LDNS_RBTREE_NULL ? NULL + : (ldns_dnssec_name*)i->nsec3_node->data; + + /* names for glue can be in the hashed_names + * tree, but will not have a NSEC3 + */ + } while (i->nsec3_name && !i->nsec3_name->nsec); + + dnssec_zone_rr_iter_set_state_for_next_name(i); + return nsec3; + + case DNSSEC_ZONE_RR_ITER_FINI: + return NULL; + } + } +} + +static ldns_rr * +dnssec_zone_rr_iter_first(dnssec_zone_rr_iter *i, ldns_dnssec_zone *zone) +{ + if (!i || !zone) + return NULL; + + memset(i, 0, sizeof(*i)); + i->zone = zone; + + i->node = ldns_rbtree_first(zone->names); + i->name = i->node == LDNS_RBTREE_NULL ? NULL + : (ldns_dnssec_name *)i->node->data; + + if (zone->hashed_names) { + do { + i->nsec3_node = ldns_rbtree_first(zone->hashed_names); + i->nsec3_name = i->nsec3_node == LDNS_RBTREE_NULL ?NULL + : (ldns_dnssec_name*)i->nsec3_node->data; + } while (i->nsec3_name && !i->nsec3_name->nsec); + } + dnssec_zone_rr_iter_set_state_for_next_name(i); + return dnssec_zone_rr_iter_next(i); +} + +enum enum_zonemd_scheme { + ZONEMD_SCHEME_FIRST = 1, + ZONEMD_SCHEME_SIMPLE = 1, + ZONEMD_SCHEME_LAST = 1 +}; +typedef enum enum_zonemd_scheme zonemd_scheme; + +enum enum_zonemd_hash { + ZONEMD_HASH_FIRST = 1, + ZONEMD_HASH_SHA384 = 1, + ZONEMD_HASH_SHA512 = 2, + ZONEMD_HASH_LAST = 2 +}; +typedef enum enum_zonemd_hash zonemd_hash; + +struct struct_zone_digester { + ldns_sha384_CTX sha384_CTX; + ldns_sha512_CTX sha512_CTX; + unsigned simple_sha384 : 1; + unsigned simple_sha512 : 1; +}; +typedef struct struct_zone_digester zone_digester; + +INLINE bool zone_digester_set(zone_digester *zd) +{ return zd && (zd->simple_sha384 || zd->simple_sha512); } + +INLINE void zone_digester_init(zone_digester *zd) +{ memset(zd, 0, sizeof(*zd)); } + +static ldns_status +zone_digester_add(zone_digester *zd, zonemd_scheme scheme, zonemd_hash hash) +{ + if (!zd) + return LDNS_STATUS_NULL; + + switch (scheme) { + case ZONEMD_SCHEME_SIMPLE: + switch (hash) { + case ZONEMD_HASH_SHA384: + ldns_sha384_init(&zd->sha384_CTX); + zd->simple_sha384 = 1; + break; + + case ZONEMD_HASH_SHA512: + ldns_sha512_init(&zd->sha512_CTX); + zd->simple_sha512 = 1; + break; + default: + return LDNS_STATUS_ZONEMD_UNKNOWN_HASH; + } + break; + default: + return LDNS_STATUS_ZONEMD_UNKNOWN_SCHEME; + } + return LDNS_STATUS_OK; +} + +static ldns_status +zone_digester_update(zone_digester *zd, ldns_rr *rr) +{ + uint8_t data[65536]; + ldns_buffer buf; + ldns_status st; + + buf._data = data; + buf._position = 0; + buf._limit = sizeof(data); + buf._capacity = sizeof(data); + buf._fixed = 1; + buf._status = LDNS_STATUS_OK; + + if ((st = ldns_rr2buffer_wire(&buf, rr, LDNS_SECTION_ANSWER))) + return st; + + if (zd->simple_sha384) + ldns_sha384_update(&zd->sha384_CTX, data, buf._position); + + if (zd->simple_sha512) + ldns_sha512_update(&zd->sha512_CTX, data, buf._position); + + return LDNS_STATUS_OK; +} + +INLINE ldns_rr * +new_zonemd(ldns_rr *soa, zonemd_hash hash) +{ + ldns_rr *rr = NULL; + uint8_t *data = NULL; + ldns_rdf *rdf; + size_t md_len = hash == ZONEMD_HASH_SHA384 + ? LDNS_SHA384_DIGEST_LENGTH + : LDNS_SHA512_DIGEST_LENGTH; + + if (!(rr = ldns_rr_new_frm_type(LDNS_RR_TYPE_ZONEMD))) + return NULL; + + if (!(rdf = ldns_rdf_clone(ldns_rr_owner(soa)))) + goto error; + + ldns_rr_set_owner(rr, rdf); + ldns_rr_set_class(rr, ldns_rr_get_class(soa)); + ldns_rr_set_ttl(rr, ldns_rr_ttl(soa)); + + if (!(rdf = ldns_rdf_clone(ldns_rr_rdf(soa, 2)))) + goto error; + ldns_rr_set_rdf(rr, rdf, 0); + + if (!(rdf = ldns_native2rdf_int8(LDNS_RDF_TYPE_INT8, 1))) + goto error; + ldns_rr_set_rdf(rr, rdf, 1); + + if (!(rdf = ldns_native2rdf_int8(LDNS_RDF_TYPE_INT8, hash))) + goto error; + ldns_rr_set_rdf(rr, rdf, 2); + + if (!(data = LDNS_XMALLOC(uint8_t, md_len))) + goto error; + + if (!(rdf = ldns_rdf_new(LDNS_RDF_TYPE_HEX, md_len, data))) + goto error; + ldns_rr_set_rdf(rr, rdf, 3); + + return rr; +error: + if (data) + LDNS_FREE(data); + ldns_rr_free(rr); + return NULL; +} + +static ldns_rr_list * +zone_digester_export( + zone_digester *zd, ldns_rr *soa, ldns_status *ret_st) +{ + ldns_status st = LDNS_STATUS_OK; + ldns_rr_list *rr_list = NULL; + ldns_rr *sha384 = NULL; + ldns_rr *sha512 = NULL; + + if (!zd || !soa) + st = LDNS_STATUS_NULL; + + else if (ldns_rr_get_type(soa) != LDNS_RR_TYPE_SOA + || ldns_rr_rd_count(soa) < 3) + st = LDNS_STATUS_ZONEMD_INVALID_SOA; + + else if (!(rr_list = ldns_rr_list_new())) + st = LDNS_STATUS_MEM_ERR; + + else if (zd->simple_sha384 + && !(sha384 = new_zonemd(soa, ZONEMD_HASH_SHA384))) + st = LDNS_STATUS_MEM_ERR; + + else if (zd->simple_sha512 + && !(sha512 = new_zonemd(soa, ZONEMD_HASH_SHA512))) + st = LDNS_STATUS_MEM_ERR; + + else if (zd->simple_sha384 + && !ldns_rr_list_push_rr(rr_list, sha384)) + st = LDNS_STATUS_MEM_ERR; + + else if (zd->simple_sha512 + && !ldns_rr_list_push_rr(rr_list, sha512)) { + if (zd->simple_sha384) + sha384 = NULL; /* deleted by ldns_rr_list_deep_free */ + st = LDNS_STATUS_MEM_ERR; + + } else { + if (sha384) + ldns_sha384_final( ldns_rdf_data(ldns_rr_rdf(sha384,3)) + , &zd->sha384_CTX); + if (sha512) + ldns_sha512_final( ldns_rdf_data(ldns_rr_rdf(sha512,3)) + , &zd->sha512_CTX); + return rr_list; + } + if (ret_st) + *ret_st = st; + if (sha384) + ldns_rr_free(sha384); + if (sha512) + ldns_rr_free(sha512); + if (rr_list) + ldns_rr_list_deep_free(rr_list); + return NULL; +} + +static ldns_status +ldns_digest_zone(ldns_dnssec_zone *zone, zone_digester *zd) +{ + ldns_status st = LDNS_STATUS_OK; + dnssec_zone_rr_iter rr_iter; + ldns_rr *rr; + ldns_rdf *apex_name; /* name of zone apex */ + + if (!zone || !zd || !zone->soa || !zone->soa->name) + return LDNS_STATUS_NULL; + + apex_name = zone->soa->name; + for ( rr = dnssec_zone_rr_iter_first(&rr_iter, zone) + ; rr && !st + ; rr = dnssec_zone_rr_iter_next(&rr_iter)) { + /* Skip apex ZONEMD RRs */ + if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_ZONEMD + && !ldns_dname_compare(ldns_rr_owner(rr), apex_name)) + continue; + /* Skip RRSIGs for apex ZONEMD RRs */ + if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_RRSIG + && LDNS_RR_TYPE_ZONEMD == ldns_rdf2rr_type( + ldns_rr_rrsig_typecovered(rr)) + && !ldns_dname_compare(ldns_rr_owner(rr), apex_name)) + continue; + st = zone_digester_update(zd, rr); + } + return st; +} + +ldns_status +ldns_dnssec_zone_verify_zonemd(ldns_dnssec_zone *zone) +{ + ldns_dnssec_rrsets *zonemd, *soa; + zone_digester zd; + ldns_dnssec_rrs *rrs; + ldns_rr *soa_rr; + ldns_status st; + uint8_t simple_sha384[LDNS_SHA384_DIGEST_LENGTH]; + uint8_t simple_sha512[LDNS_SHA512_DIGEST_LENGTH]; + size_t valid_zonemds; + + if (!zone) + return LDNS_STATUS_NULL; + + zonemd = ldns_dnssec_zone_find_rrset( + zone, zone->soa->name, LDNS_RR_TYPE_ZONEMD); + if (!zonemd) { + ldns_rbnode_t *nsec3_node; + + /* we need proof of non-existance for ZONEMD at the apex */ + if (zone->soa->nsec) { + if (ldns_nsec_bitmap_covers_type(ldns_nsec_get_bitmap( + zone->soa->nsec), + LDNS_RR_TYPE_ZONEMD)) + return LDNS_STATUS_NO_ZONEMD; + + } else if (!zone->soa->hashed_name || !zone->hashed_names) + return LDNS_STATUS_NO_ZONEMD; + + else if (LDNS_RBTREE_NULL == + (nsec3_node = ldns_rbtree_search( zone->hashed_names + , zone->soa->hashed_name))) + return LDNS_STATUS_NO_ZONEMD; + else { + ldns_dnssec_name *nsec3 + = (ldns_dnssec_name *)nsec3_node->data; + if (ldns_nsec_bitmap_covers_type(ldns_nsec_get_bitmap( + nsec3->nsec), + LDNS_RR_TYPE_ZONEMD)) + return LDNS_STATUS_NO_ZONEMD; + } + /* ZONEMD at apex does really not exist */ + return LDNS_STATUS_OK; + } + soa = ldns_dnssec_zone_find_rrset( + zone, zone->soa->name, LDNS_RR_TYPE_SOA); + if (!soa || !soa->rrs || !soa->rrs->rr) + return LDNS_STATUS_ZONEMD_INVALID_SOA; + + soa_rr = soa->rrs->rr; + if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA + || ldns_rr_rd_count(soa_rr) < 3) + return LDNS_STATUS_ZONEMD_INVALID_SOA; + + zone_digester_init(&zd); + for (rrs = zonemd->rrs; rrs; rrs = rrs->next) { + if (!rrs->rr + || ldns_rr_get_type(rrs->rr) != LDNS_RR_TYPE_ZONEMD + || ldns_rr_rd_count(rrs->rr) < 4) + continue; + + /* serial should match SOA's serial */ + if (ldns_rdf2native_int32(ldns_rr_rdf(soa_rr, 2)) + != ldns_rdf2native_int32(ldns_rr_rdf(rrs->rr, 0))) + continue; + + /* Add (scheme, hash) to digester */ + zone_digester_add(&zd, + ldns_rdf2native_int8(ldns_rr_rdf(rrs->rr, 1)), + ldns_rdf2native_int8(ldns_rr_rdf(rrs->rr, 2))); + } + if (!zone_digester_set(&zd)) + return LDNS_STATUS_NO_VALID_ZONEMD; + + if ((st = ldns_digest_zone(zone, &zd))) + return st; + + if (zd.simple_sha384) + ldns_sha384_final(simple_sha384, &zd.sha384_CTX); + if (zd.simple_sha512) + ldns_sha512_final(simple_sha512, &zd.sha512_CTX); + + valid_zonemds = 0; + for (rrs = zonemd->rrs; rrs; rrs = rrs->next) { + if (!rrs->rr + || ldns_rr_get_type(rrs->rr) != LDNS_RR_TYPE_ZONEMD + || ldns_rr_rd_count(rrs->rr) < 4) + continue; + + /* serial should match SOA's serial */ + if (ldns_rdf2native_int32(ldns_rr_rdf(soa_rr, 2)) + != ldns_rdf2native_int32(ldns_rr_rdf(rrs->rr, 0))) + continue; + + if (ZONEMD_SCHEME_SIMPLE != + ldns_rdf2native_int8(ldns_rr_rdf(rrs->rr, 1))) + continue; + + if (ZONEMD_HASH_SHA384 + == ldns_rdf2native_int8(ldns_rr_rdf(rrs->rr,2)) + && LDNS_SHA384_DIGEST_LENGTH + == ldns_rdf_size(ldns_rr_rdf(rrs->rr, 3)) + && memcmp( simple_sha384 + , ldns_rdf_data(ldns_rr_rdf(rrs->rr, 3)) + , LDNS_SHA384_DIGEST_LENGTH) == 0) + + valid_zonemds += 1; + + if (ZONEMD_HASH_SHA512 + == ldns_rdf2native_int8(ldns_rr_rdf(rrs->rr,2)) + && LDNS_SHA512_DIGEST_LENGTH + == ldns_rdf_size(ldns_rr_rdf(rrs->rr, 3)) + && memcmp( simple_sha512 + , ldns_rdf_data(ldns_rr_rdf(rrs->rr, 3)) + , LDNS_SHA512_DIGEST_LENGTH) == 0) + + valid_zonemds += 1; + } + return valid_zonemds ? LDNS_STATUS_OK : LDNS_STATUS_NO_VALID_ZONEMD; +} + +#ifdef HAVE_SSL +ldns_status +dnssec_zone_equip_zonemd( + ldns_dnssec_zone *zone, int signflags, ldns_key_list *key_list) +{ + ldns_status st = LDNS_STATUS_OK; + zone_digester zd; + ldns_rr_list *zonemd_rr_list = NULL; + ldns_rr_list *zonemd_rrsigs = NULL; + ldns_dnssec_rrsets *soa_rrset; + ldns_rr *soa_rr = NULL; + ldns_dnssec_rrsets **rrset_ref; + ldns_dnssec_rrsets *zonemd_rrset; + + zone_digester_init(&zd); + if (signflags & LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA384) + zone_digester_add(&zd, ZONEMD_SCHEME_SIMPLE + , ZONEMD_HASH_SHA384); + + if (signflags & LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA512) + zone_digester_add(&zd, ZONEMD_SCHEME_SIMPLE + , ZONEMD_HASH_SHA512); + + if ((st = ldns_digest_zone(zone, &zd))) + return st; + + soa_rrset = ldns_dnssec_zone_find_rrset( + zone, zone->soa->name, LDNS_RR_TYPE_SOA); + if (!soa_rrset || !soa_rrset->rrs || !soa_rrset->rrs->rr) + return LDNS_STATUS_ZONEMD_INVALID_SOA; + soa_rr = soa_rrset->rrs->rr; + + if (!(zonemd_rr_list = zone_digester_export(&zd, soa_rr, &st))) + return st; + + /* - replace or add ZONEMD rrset */ + rrset_ref = &zone->soa->rrsets; /* scan rrsets at apex */ + while (*rrset_ref && (*rrset_ref)->type < LDNS_RR_TYPE_ZONEMD) + rrset_ref = &(*rrset_ref)->next; + if (*rrset_ref && (*rrset_ref)->type == LDNS_RR_TYPE_ZONEMD) { + /* reuse zonemd rrset */ + zonemd_rrset = *rrset_ref; + ldns_dnssec_rrs_free(zonemd_rrset->rrs); + zonemd_rrset->rrs = NULL; + ldns_dnssec_rrs_free(zonemd_rrset->signatures); + zonemd_rrset->signatures = NULL; + } else { + /* insert zonemd rrset */ + zonemd_rrset = ldns_dnssec_rrsets_new(); + if (!zonemd_rrset) { + ldns_rr_list_deep_free(zonemd_rr_list); + return LDNS_STATUS_MEM_ERR; + } + zonemd_rrset->type = LDNS_RR_TYPE_ZONEMD; + zonemd_rrset->next = *rrset_ref; + *rrset_ref = zonemd_rrset; + } + if ((zonemd_rrsigs = ldns_sign_public(zonemd_rr_list, key_list))) + st = rr_list2dnssec_rrs( zonemd_rrsigs + , &zonemd_rrset->signatures); + if (!st) + st = rr_list2dnssec_rrs(zonemd_rr_list, &zonemd_rrset->rrs); + ldns_rr_list_deep_free(zonemd_rr_list); + ldns_rr_list_deep_free(zonemd_rrsigs); + return st; +} + +#endif /* HAVE_SSL */ + diff --git a/error.c b/error.c index 7943017a..29e167e3 100644 --- a/error.c +++ b/error.c @@ -157,6 +157,15 @@ ldns_lookup_table ldns_error_str[] = { "X509_STORE_CTX_set0_dane() functions within OpenSSL >= 1.1.0 " "to be able to verify the DANE-TA usage type." }, #endif + { LDNS_STATUS_ZONEMD_UNKNOWN_SCHEME, "Unknown ZONEMD " }, + { LDNS_STATUS_ZONEMD_UNKNOWN_HASH, "Unknown ZONEMD hash algorithm" }, + { LDNS_STATUS_ZONEMD_INVALID_SOA, + "Missing or invalid SOA to associate with ZONEMD RR" }, + { LDNS_STATUS_NO_ZONEMD, + "NSEC and NSEC3 RRs say that ZONEMD exists, " + "but it is not present in the zone" }, + { LDNS_STATUS_NO_VALID_ZONEMD, + "No ZONEMD matching the zone data was found" }, { 0, NULL } }; diff --git a/examples/ldns-signzone.c b/examples/ldns-signzone.c index 9387876c..0b17ea7a 100644 --- a/examples/ldns-signzone.c +++ b/examples/ldns-signzone.c @@ -564,45 +564,39 @@ shutdown_openssl ( ENGINE * const e ) } #endif -static bool ldns_rr_list_push_rrs(ldns_rr_list *rr_list, ldns_dnssec_rrs *rrs) +int str2zonemd_signflag(const char *str, const char **reason) { - while (rrs && ldns_rr_list_push_rr(rr_list, rrs->rr)) - rrs = rrs->next; - return !rrs; -} - -static ldns_rr_list *ldns_dnssec_zone2rr_list(ldns_dnssec_zone *zone) -{ - ldns_rr_list *rr_list = zone ? ldns_rr_list_new() : NULL; - ldns_rbnode_t *node; - ldns_dnssec_name *name; - ldns_dnssec_rrsets *rrsets; - - if (!rr_list) - return NULL; - - /* Walking the zone->names rbtree starts at the apex */ - node = ldns_rbtree_first(zone->names); - while (node != LDNS_RBTREE_NULL) { - name = (ldns_dnssec_name *) node->data; - for (rrsets = name->rrsets; rrsets; rrsets = rrsets->next) { - if (!ldns_rr_list_push_rrs(rr_list, rrsets->rrs)) - goto error; - if (!ldns_rr_list_push_rrs(rr_list, rrsets->signatures)) - goto error; + char *colon; + + static const char *reasons[] = { + "Unknown , should be \"simple\"" + , "Syntax error in , should be \"sha384\" or \"sha512\"" + , "Unknown , should be \"sha384\" or \"sha512\"" + }; + + if (!str) + return LDNS_STATUS_NULL; + + if ((colon = strchr(str, ':'))) { + if ((colon - str != 1 || str[0] != '1') + && (colon - str != 6 || strncasecmp(str, "simple", 6))) { + if (reason) *reason = reasons[0]; + return 0; } - if (name->nsec) { - if (!ldns_rr_list_push_rr(rr_list, name->nsec)) - goto error; - if (!ldns_rr_list_push_rrs(rr_list, name->nsec_signatures)) - goto error; + + if (strchr(colon + 1, ':')) { + if (reason) *reason = reasons[1]; + return 0; } - node = ldns_rbtree_next(node); + return str2zonemd_signflag(colon + 1, reason); } - return rr_list; -error: - ldns_rr_list_free(rr_list); - return NULL; + if (!strcasecmp(str, "1") || !strcasecmp(str, "sha384")) + return LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA384; + if (!strcasecmp(str, "2") || !strcasecmp(str, "sha512")) + return LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA512; + + if (reason) *reason = reason[2]; + return 0; } int @@ -665,11 +659,9 @@ main(int argc, char *argv[]) ldns_output_format_storage fmt_st; ldns_output_format* fmt = ldns_output_format_init(&fmt_st); - const EVP_MD *add_zonemd[2] = { NULL, NULL }; - const char *zonemd_algs[2] = { "SHA384", "SHA512" }; - ldns_rr *zonemd_rrs[2] = { NULL, NULL }; - size_t n_zonemd_algs = sizeof(add_zonemd) / sizeof(const EVP_MD *); - bool zonemds = false; + /* For parson zone digest parameters */ + int flag; + const char *reason = NULL; prog = strdup(argv[0]); inception = 0; @@ -770,32 +762,15 @@ main(int argc, char *argv[]) exit(EXIT_SUCCESS); break; case 'z': - switch (atoi(optarg)) { - case 1: - if (add_zonemd[0]) - break; - add_zonemd[0] = EVP_get_digestbyname("sha384"); - if (!add_zonemd[0]) - fprintf(stderr, "SHA384 not supported " - "for ZONEMD. Continuing without it.\n"); - else - zonemds = true; - break; - case 2: if (add_zonemd[1]) - break; - add_zonemd[1] = EVP_get_digestbyname("sha512"); - if (!add_zonemd[1]) - fprintf(stderr, "SHA512 not supported " - "for ZONEMD. Continuing without it.\n"); - else - zonemds = true; - break; - default: - fprintf(stderr, "Unknown ZONEMD : %s.\n" - "Should be 1 for SHA384 or 2 for SHA512\n", - optarg); + flag = str2zonemd_signflag(optarg, &reason); + if (flag) + signflags |= flag; + else { + fprintf( stderr + , "%s\nwith zone digest parameters:" + " \"%s\"\n" + , reason, optarg); exit(EXIT_FAILURE); - break; } break; case 'A': @@ -1044,120 +1019,6 @@ main(int argc, char *argv[]) ldns_rr_list_rr(ldns_zone_rrs(orig_zone), i)); } } - if (zonemds) { - /* Remove apex ZONEMD resource records from zone - */ - ldns_rbnode_t *node; - - /* - find the apex */ - node = ldns_rbtree_search( - signed_zone->names, ldns_rr_owner(orig_soa)); - if (node != NULL && node != LDNS_RBTREE_NULL) { - ldns_dnssec_name *apex = (ldns_dnssec_name *)node->data; - ldns_dnssec_rrsets **rrsets_ref = &apex->rrsets; - - /* - find ZONEMD at apex */ - for ( rrsets_ref = &apex->rrsets - ; *rrsets_ref - ; rrsets_ref = &(*rrsets_ref)->next) { - ldns_dnssec_rrsets *zonemd_rrset; - - if ((*rrsets_ref)->type != LDNS_RR_TYPE_ZONEMD) - continue; - zonemd_rrset = (*rrsets_ref); - - /* - unlink the found ZONEMD rrset */ - *rrsets_ref = zonemd_rrset->next; - - /* - free the ZONEMD rrset */ - zonemd_rrset->next = NULL; - ldns_dnssec_rrsets_free(zonemd_rrset); - break; - } - } - } - /* Add fresh ZONEMD records at apex */ - for (i = 0; i < n_zonemd_algs; i++) { - ldns_rr *zonemd_rr; - ldns_rdf *rdf = NULL; - - if (!add_zonemd[i]) - continue; - zonemd_rr = ldns_rr_new_frm_type(LDNS_RR_TYPE_ZONEMD); - if (!zonemd_rr) { - fprintf(stderr, "Error creating " - "ZONEMD rr with algorithm %s. " - "Continuing without it.\n" - , zonemd_algs[i]); - continue; - } - ldns_rr_set_class(zonemd_rr, ldns_rr_get_class(orig_soa)); - ldns_rr_set_ttl(zonemd_rr, ldns_rr_ttl(orig_soa)); - do { - ldns_status st; - void *md_buf; - - /* - set owner */ - rdf = ldns_rdf_clone(ldns_rr_owner(orig_soa)); - if (!rdf) - break; - ldns_rr_set_owner(zonemd_rr, rdf); - - /* - set serial rdata field */ - rdf = ldns_rdf_clone(ldns_rr_rdf(orig_soa, 2)); - if (!rdf) - break; - (void) ldns_rr_set_rdf(zonemd_rr, rdf, 0); - - /* - set scheme rdata field */ - /* currently we only support SIMPLE scheme */ - rdf = ldns_native2rdf_int8( - LDNS_RDF_TYPE_INT8, 1); - if (!rdf) - break; - (void) ldns_rr_set_rdf(zonemd_rr, rdf, 1); - - /* - set algorithm rdata field */ - rdf = ldns_native2rdf_int8( - LDNS_RDF_TYPE_INT8, (uint8_t)(i + 1)); - if (!rdf) - break; - (void) ldns_rr_set_rdf(zonemd_rr, rdf, 2); - - /* - set digest rdata field */ - md_buf = calloc(EVP_MD_size(add_zonemd[i]), 1); - if (!md_buf) { - rdf = NULL; - break; - } - rdf = ldns_rdf_new( LDNS_RDF_TYPE_HEX - , EVP_MD_size(add_zonemd[i]) - , md_buf); - if (!rdf) - break; - (void) ldns_rr_set_rdf(zonemd_rr, rdf, 3); - - st = ldns_dnssec_zone_add_rr( - signed_zone, zonemd_rr); - if (st) { - fprintf( stderr - , "Error adding ZONEMD to zone:" - " %s\nContinuing without it.\n" - , ldns_get_errorstr_by_id(st)); - ldns_rr_free(zonemd_rr); - break; - } - zonemd_rrs[i] = zonemd_rr; - } while (0); - if (!rdf) { - fprintf(stderr, "Memory error allocating for " - "ZONEMD RR with algorithm %s. " - "Continuing without it.\n" - , zonemd_algs[i]); - ldns_rr_free(zonemd_rr); - continue; - } - } /* list to store newly created rrs, so we can free them later */ added_rrs = ldns_rr_list_new(); @@ -1193,150 +1054,6 @@ main(int argc, char *argv[]) } if (signed_zone) { - ldns_status zonemd_result = LDNS_STATUS_OK; - EVP_MD_CTX *ctxs[2] = { NULL, NULL }; - - zonemds = false; - for (i = 0; i < n_zonemd_algs; i++) { - if (zonemd_rrs[i]) { - ctxs[i] = EVP_MD_CTX_create(); - if (!EVP_DigestInit(ctxs[i], add_zonemd[i])) { - fprintf( stderr - , "Error initializing hashing " - "algorithm %s for ZONEMD. " - "Continuing without it.\n" - , zonemd_algs[i]); - EVP_MD_CTX_destroy(ctxs[i]); - ctxs[i] = NULL; - } else - zonemds = true; - } - } - if (zonemds) do { - ldns_buffer *buf = ldns_buffer_new(LDNS_MAX_PACKETLEN); - ldns_rr_list *rr_list; - size_t j; - ldns_dnssec_rrsets *zonemd; - ldns_rr_list *rrsigs; - - if (!buf) { - fprintf( stderr - , "Error allocating buffer for " - "ZONEMD digest(s)\n"); - break; - } - rr_list = ldns_dnssec_zone2rr_list(signed_zone); - if (!rr_list) { - fprintf( stderr - , "Error making ZONEMD digest(s)\n"); - ldns_buffer_free(buf); - break; - } - ldns_rr_list_sort(rr_list); - for (i = 0; i < ldns_rr_list_rr_count(rr_list); i++) { - ldns_rr *rr = ldns_rr_list_rr(rr_list, i); - - /* Skip apex ZONEMD RRs */ - if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_ZONEMD - && !ldns_dname_compare( ldns_rr_owner(rr) - , ldns_rr_owner(orig_soa))) { - continue; - } - /* Skip RRSIGs for apex ZONEMD RRs */ - if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_RRSIG - && LDNS_RR_TYPE_ZONEMD == ldns_rdf2rr_type( - ldns_rr_rrsig_typecovered(rr)) - && !ldns_dname_compare( ldns_rr_owner(rr) - , ldns_rr_owner(orig_soa))) { - continue; - } - zonemd_result = ldns_rr2buffer_wire( - buf, rr, LDNS_SECTION_ANSWER); - if (zonemd_result) - break; - - for (j = 0; j < n_zonemd_algs; j++) { - if (!ctxs[j]) - continue; - EVP_DigestUpdate(ctxs[j], - ldns_buffer_begin(buf), - ldns_buffer_position(buf)); - } - ldns_buffer_clear(buf); - } - if (zonemd_result) { - ldns_rr_list_free(rr_list); - ldns_buffer_free(buf); - break; - } - for (j = 0; j < n_zonemd_algs; j++) { - if (!ctxs[j]) - continue; - EVP_DigestFinal_ex(ctxs[j], - ldns_rdf_data(ldns_rr_rdf(zonemd_rrs[j], 3)), - 0); - } - ldns_buffer_free(buf); - - /* Resign ZONEMD rrset */ - /* - sign (create new signatures) */ - ldns_rr_list_set_rr_count(rr_list, 0); - for (j = 0; j < n_zonemd_algs; j++) { - if (!zonemd_rrs[j]) - continue; - ldns_rr_list_push_rr(rr_list, zonemd_rrs[j]); - } - rrsigs = ldns_sign_public(rr_list, keys); - if (!rrsigs) { - zonemd_result = LDNS_STATUS_MEM_ERR; - ldns_rr_list_free(rr_list); - break; - } - - /* - find ZONEMD rrset */ - zonemd = ldns_dnssec_zone_find_rrset(signed_zone, - ldns_rr_owner(orig_soa), LDNS_RR_TYPE_ZONEMD); - assert(zonemd); - - /* - remove old signatures */ - ldns_dnssec_rrs_free(zonemd->signatures); - zonemd->signatures = NULL; - - /* - provision the new signatures */ - if (ldns_rr_list_rr_count(rrsigs)) { - ldns_rr *rrsig = ldns_rr_list_rr(rrsigs, 0); - - zonemd->signatures = ldns_dnssec_rrs_new(); - if (!zonemd->signatures) { - zonemd_result = LDNS_STATUS_MEM_ERR; - ldns_rr_list_free(rrsigs); - ldns_rr_list_free(rr_list); - break; - } - zonemd->signatures->rr = rrsig; - if (added_rrs) - ldns_rr_list_push_rr(added_rrs, rrsig); - } - for (i = 1; i < ldns_rr_list_rr_count(rrsigs); i++) { - ldns_rr *rrsig = ldns_rr_list_rr(rrsigs, i); - - zonemd_result = ldns_dnssec_rrs_add_rr( - zonemd->signatures, rrsig); - if (zonemd_result) - break; - if (added_rrs) - ldns_rr_list_push_rr(added_rrs, rrsig); - } - - ldns_rr_list_free(rrsigs); - ldns_rr_list_free(rr_list); - } while(0); - if (zonemd_result != LDNS_STATUS_OK) { - fprintf( stderr - , "Error calculating ZONEMD digest(s): %s\n" - , ldns_get_errorstr_by_id(result)); - } - if (strncmp(outputfile_name, "-", 2) == 0) { ldns_dnssec_zone_print(stdout, signed_zone); } else { diff --git a/examples/ldns-verify-zone.c b/examples/ldns-verify-zone.c index a5a1d003..68534d00 100644 --- a/examples/ldns-verify-zone.c +++ b/examples/ldns-verify-zone.c @@ -922,6 +922,13 @@ main(int argc, char **argv) dnssec_zone->soa->name, keys, apexonly, percentage); + if (!result) { + result = ldns_dnssec_zone_verify_zonemd(dnssec_zone); + if (result && verbosity > 3) { + fprintf(myerr, + "Could not validate zone digest\n"); + } + } if (result == LDNS_STATUS_OK) { if (verbosity >= 3) { fprintf(myout, diff --git a/ldns/dnssec_sign.h b/ldns/dnssec_sign.h index a88a46af..96bc8cdd 100644 --- a/ldns/dnssec_sign.h +++ b/ldns/dnssec_sign.h @@ -14,6 +14,8 @@ extern "C" { /** Sign flag that makes DNSKEY type signed by all keys, not only by SEP keys*/ #define LDNS_SIGN_DNSKEY_WITH_ZSK 1 #define LDNS_SIGN_WITH_ALL_ALGORITHMS 2 +#define LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA384 4 +#define LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA512 8 /** * Create an empty RRSIG RR (i.e. without the actual signature data) diff --git a/ldns/dnssec_zone.h b/ldns/dnssec_zone.h index e97ea30c..bc8e4042 100644 --- a/ldns/dnssec_zone.h +++ b/ldns/dnssec_zone.h @@ -476,6 +476,8 @@ ldns_status ldns_dnssec_zone_add_empty_nonterminals(ldns_dnssec_zone *zone); */ bool ldns_dnssec_zone_is_nsec3_optout(const ldns_dnssec_zone* zone); +ldns_status ldns_dnssec_zone_verify_zonemd(ldns_dnssec_zone *zone); + #ifdef __cplusplus } #endif diff --git a/ldns/error.h b/ldns/error.h index 15f49a28..e5580fcf 100644 --- a/ldns/error.h +++ b/ldns/error.h @@ -129,7 +129,12 @@ enum ldns_enum_status { LDNS_STATUS_RDATA_OVERFLOW, LDNS_STATUS_SYNTAX_SUPERFLUOUS_TEXT_ERR, LDNS_STATUS_NSEC3_DOMAINNAME_OVERFLOW, - LDNS_STATUS_DANE_NEED_OPENSSL_GE_1_1_FOR_DANE_TA + LDNS_STATUS_DANE_NEED_OPENSSL_GE_1_1_FOR_DANE_TA, + LDNS_STATUS_ZONEMD_UNKNOWN_SCHEME, + LDNS_STATUS_ZONEMD_UNKNOWN_HASH, + LDNS_STATUS_ZONEMD_INVALID_SOA, + LDNS_STATUS_NO_ZONEMD, + LDNS_STATUS_NO_VALID_ZONEMD }; typedef enum ldns_enum_status ldns_status;