aboutsummaryrefslogtreecommitdiff
path: root/contrib/ldns/dnssec_zone.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/ldns/dnssec_zone.c')
-rw-r--r--contrib/ldns/dnssec_zone.c850
1 files changed, 820 insertions, 30 deletions
diff --git a/contrib/ldns/dnssec_zone.c b/contrib/ldns/dnssec_zone.c
index f610a3cfe8fc..f95ecb31f79e 100644
--- a/contrib/ldns/dnssec_zone.c
+++ b/contrib/ldns/dnssec_zone.c
@@ -5,6 +5,7 @@
#include <ldns/config.h>
#include <ldns/ldns.h>
+#include <ldns/internal.h>
ldns_dnssec_rrs *
ldns_dnssec_rrs_new(void)
@@ -323,7 +324,7 @@ ldns_dnssec_name_new(void)
return NULL;
}
/*
- * not needed anymore because CALLOC initalizes everything to zero.
+ * not needed anymore because CALLOC initializes everything to zero.
new_name->name = NULL;
new_name->rrsets = NULL;
@@ -370,9 +371,10 @@ ldns_dnssec_name_free_internal(ldns_dnssec_name *name,
ldns_dnssec_rrs_free_internal(name->nsec_signatures, deep);
}
if (name->hashed_name) {
- if (deep) {
- ldns_rdf_deep_free(name->hashed_name);
- }
+ /* Hashed name is always allocated when signing,
+ * so always deep free
+ */
+ ldns_rdf_deep_free(name->hashed_name);
}
LDNS_FREE(name);
}
@@ -588,7 +590,7 @@ rr_is_rrsig_covering(ldns_rr* rr, ldns_rr_type t)
/* When the zone is first read into an list and then inserted into an
* ldns_dnssec_zone (rbtree) the nodes of the rbtree are allocated close (next)
* to each other. Because ldns-verify-zone (the only program that uses this
- * function) uses the rbtree mostly for sequentual walking, this results
+ * function) uses the rbtree mostly for sequential walking, this results
* in a speed increase (of 15% on linux) because we have less CPU-cache misses.
*/
#define FASTER_DNSSEC_ZONE_NEW_FRM_FP 1 /* Because of L2 cache efficiency */
@@ -606,7 +608,7 @@ ldns_todo_nsec3_ents_node_free(ldns_rbnode_t *node, void *arg) {
ldns_status
ldns_dnssec_zone_new_frm_fp_l(ldns_dnssec_zone** z, FILE* fp, const ldns_rdf* origin,
- uint32_t ttl, ldns_rr_class ATTR_UNUSED(c), int* line_nr)
+ uint32_t default_ttl, ldns_rr_class ATTR_UNUSED(c), int* line_nr)
{
ldns_rr* cur_rr;
size_t i;
@@ -626,7 +628,7 @@ ldns_dnssec_zone_new_frm_fp_l(ldns_dnssec_zone** z, FILE* fp, const ldns_rdf* or
nsec3_ents (where ent is e.n.t.; i.e. empty non terminal) will
hold the NSEC3s that still didn't have a matching name in the
zone tree, even after all names were read. They can only match
- after the zone is equiped with all the empty non terminals. */
+ after the zone is equipped with all the empty non terminals. */
ldns_rbtree_t todo_nsec3_ents;
ldns_rbnode_t *new_node;
ldns_rr_list* todo_nsec3_rrsigs = ldns_rr_list_new();
@@ -636,13 +638,19 @@ ldns_dnssec_zone_new_frm_fp_l(ldns_dnssec_zone** z, FILE* fp, const ldns_rdf* or
#ifdef FASTER_DNSSEC_ZONE_NEW_FRM_FP
ldns_zone* zone = NULL;
#else
- uint32_t my_ttl = ttl;
+ ldns_rr *prev_rr = NULL;
+ uint32_t my_ttl = default_ttl;
+ /* RFC 1035 Section 5.1, says 'Omitted class and TTL values are default
+ * to the last explicitly stated values.'
+ */
+ bool ttl_from_TTL = false;
+ bool explicit_ttl = false;
#endif
ldns_rbtree_init(&todo_nsec3_ents, ldns_dname_compare_v);
#ifdef FASTER_DNSSEC_ZONE_NEW_FRM_FP
- status = ldns_zone_new_frm_fp_l(&zone, fp, origin,ttl, c, line_nr);
+ status = ldns_zone_new_frm_fp_l(&zone, fp, origin, default_ttl, c, line_nr);
if (status != LDNS_STATUS_OK)
goto error;
#endif
@@ -672,13 +680,61 @@ ldns_dnssec_zone_new_frm_fp_l(ldns_dnssec_zone** z, FILE* fp, const ldns_rdf* or
status = LDNS_STATUS_OK;
#else
while (!feof(fp)) {
+ /* If ttl came from $TTL line, then it should be the default.
+ * (RFC 2308 Section 4)
+ * Otherwise it "defaults to the last explicitly stated value"
+ * (RFC 1035 Section 5.1)
+ */
+ if (ttl_from_TTL)
+ my_ttl = default_ttl;
status = ldns_rr_new_frm_fp_l(&cur_rr, fp, &my_ttl, &my_origin,
- &my_prev, line_nr);
-
+ &my_prev, line_nr, &explicit_ttl);
#endif
switch (status) {
case LDNS_STATUS_OK:
+#ifndef FASTER_DNSSEC_ZONE_NEW_FRM_FP
+ if (explicit_ttl) {
+ if (!ttl_from_TTL) {
+ /* No $TTL, so ttl "defaults to the
+ * last explicitly stated value"
+ * (RFC 1035 Section 5.1)
+ */
+ my_ttl = ldns_rr_ttl(cur_rr);
+ }
+ /* When ttl is implicit, try to adhere to the rules as
+ * much as possible. (also for compatibility with bind)
+ * This was changed when fixing an issue with ZONEMD
+ * which hashes the TTL too.
+ */
+ } else if (ldns_rr_get_type(cur_rr) == LDNS_RR_TYPE_SIG
+ || ldns_rr_get_type(cur_rr) == LDNS_RR_TYPE_RRSIG) {
+ if (ldns_rr_rd_count(cur_rr) >= 4
+ && ldns_rdf_get_type(ldns_rr_rdf(cur_rr, 3)) == LDNS_RDF_TYPE_INT32)
+
+ /* SIG without explicit ttl get ttl
+ * from the original_ttl field
+ * (RFC 2535 Section 7.2)
+ *
+ * Similarly for RRSIG, but stated less
+ * specifically in the spec.
+ * (RFC 4034 Section 3)
+ */
+ ldns_rr_set_ttl(cur_rr,
+ ldns_rdf2native_int32(
+ ldns_rr_rdf(rr, 3)));
+
+ } else if (prev_rr
+ && ldns_rr_get_type(prev_rr) == ldns_rr_get_type(cur_rr)
+ && ldns_dname_compare( ldns_rr_owner(prev_rr)
+ , ldns_rr_owner(cur_rr)) == 0)
+
+ /* "TTLs of all RRs in an RRSet must be the same"
+ * (RFC 2881 Section 5.2)
+ */
+ ldns_rr_set_ttl(cur_rr, ldns_rr_ttl(prev_rr));
+ prev_rr = cur_rr;
+#endif
status = ldns_dnssec_zone_add_rr(newzone, cur_rr);
if (status ==
LDNS_STATUS_DNSSEC_NSEC3_ORIGINAL_NOT_FOUND) {
@@ -698,9 +754,16 @@ ldns_dnssec_zone_new_frm_fp_l(ldns_dnssec_zone** z, FILE* fp, const ldns_rdf* or
break;
+ case LDNS_STATUS_SYNTAX_TTL: /* the ttl was set*/
+#ifndef FASTER_DNSSEC_ZONE_NEW_FRM_FP
+ default_ttl = my_ttl;
+ ttl_from_TTL = true;
+#endif
+ status = LDNS_STATUS_OK;
+ break;
+
case LDNS_STATUS_SYNTAX_EMPTY: /* empty line was seen */
- case LDNS_STATUS_SYNTAX_TTL: /* the ttl was set*/
case LDNS_STATUS_SYNTAX_ORIGIN: /* the origin was set*/
status = LDNS_STATUS_OK;
break;
@@ -746,6 +809,7 @@ ldns_dnssec_zone_new_frm_fp_l(ldns_dnssec_zone** z, FILE* fp, const ldns_rdf* or
newzone = NULL;
} else {
ldns_dnssec_zone_free(newzone);
+ newzone = NULL;
}
error:
@@ -792,10 +856,21 @@ ldns_dnssec_name_node_deep_free(ldns_rbnode_t *node, void *arg) {
LDNS_FREE(node);
}
+static void
+ldns_hashed_names_node_free(ldns_rbnode_t *node, void *arg) {
+ (void) arg;
+ LDNS_FREE(node);
+}
+
void
ldns_dnssec_zone_free(ldns_dnssec_zone *zone)
{
if (zone) {
+ if (zone->hashed_names) {
+ ldns_traverse_postorder(zone->hashed_names,
+ ldns_hashed_names_node_free, NULL);
+ LDNS_FREE(zone->hashed_names);
+ }
if (zone->names) {
/* destroy all name structures within the tree */
ldns_traverse_postorder(zone->names,
@@ -811,6 +886,11 @@ void
ldns_dnssec_zone_deep_free(ldns_dnssec_zone *zone)
{
if (zone) {
+ if (zone->hashed_names) {
+ ldns_traverse_postorder(zone->hashed_names,
+ ldns_hashed_names_node_free, NULL);
+ LDNS_FREE(zone->hashed_names);
+ }
if (zone->names) {
/* destroy all name structures within the tree */
ldns_traverse_postorder(zone->names,
@@ -833,12 +913,6 @@ ldns_dnssec_name_make_hashed_name(ldns_dnssec_zone *zone,
ldns_dnssec_name* name, ldns_rr* nsec3rr);
static void
-ldns_hashed_names_node_free(ldns_rbnode_t *node, void *arg) {
- (void) arg;
- LDNS_FREE(node);
-}
-
-static void
ldns_dnssec_zone_hashed_names_from_nsec3(
ldns_dnssec_zone* zone, ldns_rr* nsec3rr)
{
@@ -907,20 +981,22 @@ ldns_dnssec_name_make_hashed_name(ldns_dnssec_zone *zone,
static ldns_rbnode_t *
ldns_dnssec_zone_find_nsec3_original(ldns_dnssec_zone *zone, ldns_rr *rr) {
ldns_rdf *hashed_name;
+ ldns_rbnode_t *to_return;
- hashed_name = ldns_dname_label(ldns_rr_owner(rr), 0);
- if (hashed_name == NULL) {
- return NULL;
- }
if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_NSEC3 && ! zone->_nsec3params){
ldns_dnssec_zone_hashed_names_from_nsec3(zone, rr);
}
if (zone->hashed_names == NULL) {
- ldns_rdf_deep_free(hashed_name);
return NULL;
}
- return ldns_rbtree_search(zone->hashed_names, hashed_name);
+ hashed_name = ldns_dname_label(ldns_rr_owner(rr), 0);
+ if (hashed_name == NULL) {
+ return NULL;
+ }
+ to_return = ldns_rbtree_search(zone->hashed_names, hashed_name);
+ ldns_rdf_deep_free(hashed_name);
+ return to_return;
}
ldns_status
@@ -1105,8 +1181,12 @@ ldns_dnssec_zone_add_empty_nonterminals_nsec3(
ldns_rdf *ent_name;
if (!(ent_name = ldns_dname_clone_from(
- next_name, i)))
+ next_name, i))) {
+
+ ldns_rdf_deep_free(l1);
+ ldns_rdf_deep_free(l2);
return LDNS_STATUS_MEM_ERR;
+ }
if (nsec3s && zone->_nsec3params) {
ldns_rdf *ent_hashed_name;
@@ -1114,28 +1194,35 @@ ldns_dnssec_zone_add_empty_nonterminals_nsec3(
if (!(ent_hashed_name =
ldns_nsec3_hash_name_frm_nsec3(
zone->_nsec3params,
- ent_name)))
+ ent_name))) {
+ ldns_rdf_deep_free(l1);
+ ldns_rdf_deep_free(l2);
+ ldns_rdf_deep_free(ent_name);
return LDNS_STATUS_MEM_ERR;
+ }
node = ldns_rbtree_search(nsec3s,
ent_hashed_name);
+ ldns_rdf_deep_free(ent_hashed_name);
if (!node) {
ldns_rdf_deep_free(l1);
ldns_rdf_deep_free(l2);
+ ldns_rdf_deep_free(ent_name);
continue;
}
}
new_name = ldns_dnssec_name_new();
if (!new_name) {
+ ldns_rdf_deep_free(l1);
+ ldns_rdf_deep_free(l2);
+ ldns_rdf_deep_free(ent_name);
return LDNS_STATUS_MEM_ERR;
}
new_name->name = ent_name;
- if (!new_name->name) {
- ldns_dnssec_name_free(new_name);
- return LDNS_STATUS_MEM_ERR;
- }
new_name->name_alloced = true;
new_node = LDNS_MALLOC(ldns_rbnode_t);
if (!new_node) {
+ ldns_rdf_deep_free(l1);
+ ldns_rdf_deep_free(l2);
ldns_dnssec_name_free(new_name);
return LDNS_STATUS_MEM_ERR;
}
@@ -1190,3 +1277,706 @@ ldns_dnssec_zone_is_nsec3_optout(const ldns_dnssec_zone* zone)
}
return false;
}
+
+/*
+ * Stuff for calculating and verifying zone digests
+ */
+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;
+ ldns_rdf *apex_name;
+ uint8_t apex_labs;
+} dnssec_zone_rr_iter;
+
+INLINE void
+dnssec_zone_rr_iter_set_state_for_next_name(dnssec_zone_rr_iter *i)
+{
+ /* Make sure the i->name is "in zone" (i.e. below the apex) */
+ if (i->apex_name) {
+ ldns_rdf *name = (ldns_rdf *)i->node->key;
+
+ while (i->name && name != i->apex_name /* not apex */
+
+ && ( ldns_dname_label_count(name) != i->apex_labs
+ || ldns_dname_compare(name, i->apex_name)) /* not apex */
+
+ && !ldns_dname_is_subdomain(name, i->apex_name) /* no sub */) {
+
+ /* next name */
+ i->node = ldns_rbtree_next(i->node);
+ if (i->node == LDNS_RBTREE_NULL)
+ i->name = NULL;
+ else {
+ i->name = (ldns_dnssec_name *)i->node->data;
+ name = (ldns_rdf *)i->node->key;
+ }
+ }
+ }
+ /* determine state */
+ 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;
+ if (zone->soa && zone->soa->name) {
+ i->apex_name = zone->soa->name;
+ i->apex_labs = ldns_dname_label_count(i->apex_name);
+ } else
+ i->apex_name = NULL;
+
+
+ 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;
+ unsigned double_sha384 : 1;
+ unsigned double_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:
+ if (zd->double_sha384)
+ return LDNS_STATUS_ZONEMD_DOUBLE_OCCURRENCE;
+
+ else if (zd->simple_sha384) {
+ zd->simple_sha384 = 0;
+ zd->double_sha384 = 1;
+ return LDNS_STATUS_ZONEMD_DOUBLE_OCCURRENCE;
+ }
+ ldns_sha384_init(&zd->sha384_CTX);
+ zd->simple_sha384 = 1;
+ break;
+
+ case ZONEMD_HASH_SHA512:
+ if (zd->double_sha512)
+ return LDNS_STATUS_ZONEMD_DOUBLE_OCCURRENCE;
+
+ else if (zd->simple_sha512) {
+ zd->simple_sha512 = 0;
+ zd->double_sha512 = 1;
+ return LDNS_STATUS_ZONEMD_DOUBLE_OCCURRENCE;
+ }
+ 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_canonical(&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-existence 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
+static ldns_status
+rr_list2dnssec_rrs(ldns_rr_list *rr_list, ldns_dnssec_rrs **rrs,
+ ldns_rr_list *new_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);
+ if (new_rrs)
+ ldns_rr_list_push_rr(new_rrs, (*rrs)->rr);
+ }
+ 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;
+ } else if (new_rrs)
+ ldns_rr_list_push_rr(new_rrs, rr);
+ }
+ return LDNS_STATUS_OK;
+}
+
+
+ldns_status
+dnssec_zone_equip_zonemd(ldns_dnssec_zone *zone,
+ ldns_rr_list *new_rrs, ldns_key_list *key_list, int signflags)
+{
+ 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, new_rrs);
+ if (!st)
+ st = rr_list2dnssec_rrs( zonemd_rr_list
+ , &zonemd_rrset->rrs, new_rrs);
+ ldns_rr_list_deep_free(zonemd_rr_list);
+ ldns_rr_list_deep_free(zonemd_rrsigs);
+ return st;
+}
+
+#endif /* HAVE_SSL */
+