diff options
author | Dag-Erling Smørgrav <des@FreeBSD.org> | 2012-07-04 14:24:26 +0000 |
---|---|---|
committer | Dag-Erling Smørgrav <des@FreeBSD.org> | 2012-07-04 14:24:26 +0000 |
commit | afb79913ce00d885b8b43f7478e1e054edadb567 (patch) | |
tree | b9037afac70edd3c6342318cedbbadc648b799ca /validator | |
download | src-test-afb79913ce00d885b8b43f7478e1e054edadb567.tar.gz src-test-afb79913ce00d885b8b43f7478e1e054edadb567.zip |
Notes
Diffstat (limited to 'validator')
-rw-r--r-- | validator/autotrust.c | 2203 | ||||
-rw-r--r-- | validator/autotrust.h | 205 | ||||
-rw-r--r-- | validator/val_anchor.c | 1263 | ||||
-rw-r--r-- | validator/val_anchor.h | 218 | ||||
-rw-r--r-- | validator/val_kcache.c | 172 | ||||
-rw-r--r-- | validator/val_kcache.h | 118 | ||||
-rw-r--r-- | validator/val_kentry.c | 412 | ||||
-rw-r--r-- | validator/val_kentry.h | 220 | ||||
-rw-r--r-- | validator/val_neg.c | 1455 | ||||
-rw-r--r-- | validator/val_neg.h | 314 | ||||
-rw-r--r-- | validator/val_nsec.c | 603 | ||||
-rw-r--r-- | validator/val_nsec.h | 182 | ||||
-rw-r--r-- | validator/val_nsec3.c | 1446 | ||||
-rw-r--r-- | validator/val_nsec3.h | 378 | ||||
-rw-r--r-- | validator/val_sigcrypt.c | 1824 | ||||
-rw-r--r-- | validator/val_sigcrypt.h | 311 | ||||
-rw-r--r-- | validator/val_utils.c | 1082 | ||||
-rw-r--r-- | validator/val_utils.h | 403 | ||||
-rw-r--r-- | validator/validator.c | 2958 | ||||
-rw-r--r-- | validator/validator.h | 294 |
20 files changed, 16061 insertions, 0 deletions
diff --git a/validator/autotrust.c b/validator/autotrust.c new file mode 100644 index 0000000000000..9896943245e44 --- /dev/null +++ b/validator/autotrust.c @@ -0,0 +1,2203 @@ +/* + * validator/autotrust.c - RFC5011 trust anchor management for unbound. + * + * Copyright (c) 2009, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * Contains autotrust implementation. The implementation was taken from + * the autotrust daemon (BSD licensed), written by Matthijs Mekking. + * It was modified to fit into unbound. The state table process is the same. + */ +#include "config.h" +#include <ldns/ldns.h> +#include "validator/autotrust.h" +#include "validator/val_anchor.h" +#include "validator/val_utils.h" +#include "validator/val_sigcrypt.h" +#include "util/data/dname.h" +#include "util/data/packed_rrset.h" +#include "util/log.h" +#include "util/module.h" +#include "util/net_help.h" +#include "util/config_file.h" +#include "util/regional.h" +#include "util/random.h" +#include "util/data/msgparse.h" +#include "services/mesh.h" +#include "services/cache/rrset.h" +#include "validator/val_kcache.h" + +/** number of times a key must be seen before it can become valid */ +#define MIN_PENDINGCOUNT 2 + +/** Event: Revoked */ +static void do_revoked(struct module_env* env, struct autr_ta* anchor, int* c); + +struct autr_global_data* autr_global_create(void) +{ + struct autr_global_data* global; + global = (struct autr_global_data*)malloc(sizeof(*global)); + if(!global) + return NULL; + rbtree_init(&global->probe, &probetree_cmp); + return global; +} + +void autr_global_delete(struct autr_global_data* global) +{ + if(!global) + return; + /* elements deleted by parent */ + memset(global, 0, sizeof(*global)); + free(global); +} + +int probetree_cmp(const void* x, const void* y) +{ + struct trust_anchor* a = (struct trust_anchor*)x; + struct trust_anchor* b = (struct trust_anchor*)y; + log_assert(a->autr && b->autr); + if(a->autr->next_probe_time < b->autr->next_probe_time) + return -1; + if(a->autr->next_probe_time > b->autr->next_probe_time) + return 1; + /* time is equal, sort on trust point identity */ + return anchor_cmp(x, y); +} + +size_t +autr_get_num_anchors(struct val_anchors* anchors) +{ + size_t res = 0; + if(!anchors) + return 0; + lock_basic_lock(&anchors->lock); + if(anchors->autr) + res = anchors->autr->probe.count; + lock_basic_unlock(&anchors->lock); + return res; +} + +/** Position in string */ +static int +position_in_string(char *str, const char* sub) +{ + char* pos = strstr(str, sub); + if(pos) + return (int)(pos-str)+(int)strlen(sub); + return -1; +} + +/** Debug routine to print pretty key information */ +static void +verbose_key(struct autr_ta* ta, enum verbosity_value level, + const char* format, ...) ATTR_FORMAT(printf, 3, 4); + +/** + * Implementation of debug pretty key print + * @param ta: trust anchor key with DNSKEY data. + * @param level: verbosity level to print at. + * @param format: printf style format string. + */ +static void +verbose_key(struct autr_ta* ta, enum verbosity_value level, + const char* format, ...) +{ + va_list args; + va_start(args, format); + if(verbosity >= level) { + char* str = ldns_rdf2str(ldns_rr_owner(ta->rr)); + int keytag = (int)ldns_calc_keytag(ta->rr); + char msg[MAXSYSLOGMSGLEN]; + vsnprintf(msg, sizeof(msg), format, args); + verbose(level, "%s key %d %s", str?str:"??", keytag, msg); + free(str); + } + va_end(args); +} + +/** + * Parse comments + * @param str: to parse + * @param ta: trust key autotrust metadata + * @return false on failure. + */ +static int +parse_comments(char* str, struct autr_ta* ta) +{ + int len = (int)strlen(str), pos = 0, timestamp = 0; + char* comment = (char*) malloc(sizeof(char)*len+1); + char* comments = comment; + if(!comment) { + log_err("malloc failure in parse"); + return 0; + } + /* skip over whitespace and data at start of line */ + while (*str != '\0' && *str != ';') + str++; + if (*str == ';') + str++; + /* copy comments */ + while (*str != '\0') + { + *comments = *str; + comments++; + str++; + } + *comments = '\0'; + + comments = comment; + + /* read state */ + pos = position_in_string(comments, "state="); + if (pos >= (int) strlen(comments)) + { + log_err("parse error"); + free(comment); + return 0; + } + if (pos <= 0) + ta->s = AUTR_STATE_VALID; + else + { + int s = (int) comments[pos] - '0'; + switch(s) + { + case AUTR_STATE_START: + case AUTR_STATE_ADDPEND: + case AUTR_STATE_VALID: + case AUTR_STATE_MISSING: + case AUTR_STATE_REVOKED: + case AUTR_STATE_REMOVED: + ta->s = s; + break; + default: + verbose_key(ta, VERB_OPS, "has undefined " + "state, considered NewKey"); + ta->s = AUTR_STATE_START; + break; + } + } + /* read pending count */ + pos = position_in_string(comments, "count="); + if (pos >= (int) strlen(comments)) + { + log_err("parse error"); + free(comment); + return 0; + } + if (pos <= 0) + ta->pending_count = 0; + else + { + comments += pos; + ta->pending_count = (uint8_t)atoi(comments); + } + + /* read last change */ + pos = position_in_string(comments, "lastchange="); + if (pos >= (int) strlen(comments)) + { + log_err("parse error"); + free(comment); + return 0; + } + if (pos >= 0) + { + comments += pos; + timestamp = atoi(comments); + } + if (pos < 0 || !timestamp) + ta->last_change = 0; + else + ta->last_change = (uint32_t)timestamp; + + free(comment); + return 1; +} + +/** Check if a line contains data (besides comments) */ +static int +str_contains_data(char* str, char comment) +{ + while (*str != '\0') { + if (*str == comment || *str == '\n') + return 0; + if (*str != ' ' && *str != '\t') + return 1; + str++; + } + return 0; +} + +/** Get DNSKEY flags */ +static int +dnskey_flags(ldns_rr* rr) +{ + if(ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY) + return 0; + return (int)ldns_read_uint16(ldns_rdf_data(ldns_rr_dnskey_flags(rr))); +} + + +/** Check if KSK DNSKEY */ +static int +rr_is_dnskey_sep(ldns_rr* rr) +{ + return (dnskey_flags(rr)&DNSKEY_BIT_SEP); +} + +/** Check if REVOKED DNSKEY */ +static int +rr_is_dnskey_revoked(ldns_rr* rr) +{ + return (dnskey_flags(rr)&LDNS_KEY_REVOKE_KEY); +} + +/** create ta */ +static struct autr_ta* +autr_ta_create(ldns_rr* rr) +{ + struct autr_ta* ta = (struct autr_ta*)calloc(1, sizeof(*ta)); + if(!ta) { + ldns_rr_free(rr); + return NULL; + } + ta->rr = rr; + return ta; +} + +/** create tp */ +static struct trust_anchor* +autr_tp_create(struct val_anchors* anchors, ldns_rdf* own, uint16_t dc) +{ + struct trust_anchor* tp = (struct trust_anchor*)calloc(1, sizeof(*tp)); + if(!tp) return NULL; + tp->name = memdup(ldns_rdf_data(own), ldns_rdf_size(own)); + if(!tp->name) { + free(tp); + return NULL; + } + tp->namelen = ldns_rdf_size(own); + tp->namelabs = dname_count_labels(tp->name); + tp->node.key = tp; + tp->dclass = dc; + tp->autr = (struct autr_point_data*)calloc(1, sizeof(*tp->autr)); + if(!tp->autr) { + free(tp->name); + free(tp); + return NULL; + } + tp->autr->pnode.key = tp; + + lock_basic_lock(&anchors->lock); + if(!rbtree_insert(anchors->tree, &tp->node)) { + lock_basic_unlock(&anchors->lock); + log_err("trust anchor presented twice"); + free(tp->name); + free(tp->autr); + free(tp); + return NULL; + } + if(!rbtree_insert(&anchors->autr->probe, &tp->autr->pnode)) { + (void)rbtree_delete(anchors->tree, tp); + lock_basic_unlock(&anchors->lock); + log_err("trust anchor in probetree twice"); + free(tp->name); + free(tp->autr); + free(tp); + return NULL; + } + lock_basic_unlock(&anchors->lock); + lock_basic_init(&tp->lock); + lock_protect(&tp->lock, tp, sizeof(*tp)); + lock_protect(&tp->lock, tp->autr, sizeof(*tp->autr)); + return tp; +} + +/** delete assembled rrsets */ +static void +autr_rrset_delete(struct ub_packed_rrset_key* r) +{ + if(r) { + free(r->rk.dname); + free(r->entry.data); + free(r); + } +} + +void autr_point_delete(struct trust_anchor* tp) +{ + if(!tp) + return; + lock_unprotect(&tp->lock, tp); + lock_unprotect(&tp->lock, tp->autr); + lock_basic_destroy(&tp->lock); + autr_rrset_delete(tp->ds_rrset); + autr_rrset_delete(tp->dnskey_rrset); + if(tp->autr) { + struct autr_ta* p = tp->autr->keys, *np; + while(p) { + np = p->next; + ldns_rr_free(p->rr); + free(p); + p = np; + } + free(tp->autr->file); + free(tp->autr); + } + free(tp->name); + free(tp); +} + +/** find or add a new trust point for autotrust */ +static struct trust_anchor* +find_add_tp(struct val_anchors* anchors, ldns_rr* rr) +{ + struct trust_anchor* tp; + ldns_rdf* own = ldns_rr_owner(rr); + tp = anchor_find(anchors, ldns_rdf_data(own), + dname_count_labels(ldns_rdf_data(own)), + ldns_rdf_size(own), ldns_rr_get_class(rr)); + if(tp) { + if(!tp->autr) { + log_err("anchor cannot be with and without autotrust"); + lock_basic_unlock(&tp->lock); + return NULL; + } + return tp; + } + tp = autr_tp_create(anchors, ldns_rr_owner(rr), ldns_rr_get_class(rr)); + lock_basic_lock(&tp->lock); + return tp; +} + +/** Add trust anchor from RR */ +static struct autr_ta* +add_trustanchor_frm_rr(struct val_anchors* anchors, ldns_rr* rr, + struct trust_anchor** tp) +{ + struct autr_ta* ta = autr_ta_create(rr); + if(!ta) + return NULL; + *tp = find_add_tp(anchors, rr); + if(!*tp) { + ldns_rr_free(ta->rr); + free(ta); + return NULL; + } + /* add ta to tp */ + ta->next = (*tp)->autr->keys; + (*tp)->autr->keys = ta; + lock_basic_unlock(&(*tp)->lock); + return ta; +} + +/** + * Add new trust anchor from a string in file. + * @param anchors: all anchors + * @param str: string with anchor and comments, if any comments. + * @param tp: trust point returned. + * @param origin: what to use for @ + * @param prev: previous rr name + * @param skip: if true, the result is NULL, but not an error, skip it. + * @return new key in trust point. + */ +static struct autr_ta* +add_trustanchor_frm_str(struct val_anchors* anchors, char* str, + struct trust_anchor** tp, ldns_rdf* origin, ldns_rdf** prev, int* skip) +{ + ldns_rr* rr; + ldns_status lstatus; + if (!str_contains_data(str, ';')) { + *skip = 1; + return NULL; /* empty line */ + } + if (LDNS_STATUS_OK != + (lstatus = ldns_rr_new_frm_str(&rr, str, 0, origin, prev))) + { + log_err("ldns error while converting string to RR: %s", + ldns_get_errorstr_by_id(lstatus)); + return NULL; + } + if(ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY && + ldns_rr_get_type(rr) != LDNS_RR_TYPE_DS) { + ldns_rr_free(rr); + *skip = 1; + return NULL; /* only DS and DNSKEY allowed */ + } + return add_trustanchor_frm_rr(anchors, rr, tp); +} + +/** + * Load single anchor + * @param anchors: all points. + * @param str: comments line + * @param fname: filename + * @param origin: $ORIGIN. + * @param prev: passed to ldns. + * @param skip: if true, the result is NULL, but not an error, skip it. + * @return false on failure, otherwise the tp read. + */ +static struct trust_anchor* +load_trustanchor(struct val_anchors* anchors, char* str, const char* fname, + ldns_rdf* origin, ldns_rdf** prev, int* skip) +{ + struct autr_ta* ta = NULL; + struct trust_anchor* tp = NULL; + + ta = add_trustanchor_frm_str(anchors, str, &tp, origin, prev, skip); + if(!ta) + return NULL; + lock_basic_lock(&tp->lock); + if(!parse_comments(str, ta)) { + lock_basic_unlock(&tp->lock); + return NULL; + } + if(!tp->autr->file) { + tp->autr->file = strdup(fname); + if(!tp->autr->file) { + lock_basic_unlock(&tp->lock); + log_err("malloc failure"); + return NULL; + } + } + lock_basic_unlock(&tp->lock); + return tp; +} + +/** + * Assemble the trust anchors into DS and DNSKEY packed rrsets. + * Uses only VALID and MISSING DNSKEYs. + * Read the ldns_rrs and builds packed rrsets + * @param tp: the trust point. Must be locked. + * @return false on malloc failure. + */ +static int +autr_assemble(struct trust_anchor* tp) +{ + ldns_rr_list* ds, *dnskey; + struct autr_ta* ta; + struct ub_packed_rrset_key* ubds=NULL, *ubdnskey=NULL; + + ds = ldns_rr_list_new(); + dnskey = ldns_rr_list_new(); + if(!ds || !dnskey) { + ldns_rr_list_free(ds); + ldns_rr_list_free(dnskey); + return 0; + } + for(ta = tp->autr->keys; ta; ta = ta->next) { + if(ldns_rr_get_type(ta->rr) == LDNS_RR_TYPE_DS) { + if(!ldns_rr_list_push_rr(ds, ta->rr)) { + ldns_rr_list_free(ds); + ldns_rr_list_free(dnskey); + return 0; + } + } else if(ta->s == AUTR_STATE_VALID || + ta->s == AUTR_STATE_MISSING) { + if(!ldns_rr_list_push_rr(dnskey, ta->rr)) { + ldns_rr_list_free(ds); + ldns_rr_list_free(dnskey); + return 0; + } + } + } + + /* make packed rrset keys - malloced with no ID number, they + * are not in the cache */ + /* make packed rrset data (if there is a key) */ + + if(ldns_rr_list_rr_count(ds) > 0) { + ubds = ub_packed_rrset_heap_key(ds); + if(!ubds) + goto error_cleanup; + ubds->entry.data = packed_rrset_heap_data(ds); + if(!ubds->entry.data) + goto error_cleanup; + } + if(ldns_rr_list_rr_count(dnskey) > 0) { + ubdnskey = ub_packed_rrset_heap_key(dnskey); + if(!ubdnskey) + goto error_cleanup; + ubdnskey->entry.data = packed_rrset_heap_data(dnskey); + if(!ubdnskey->entry.data) { + error_cleanup: + autr_rrset_delete(ubds); + autr_rrset_delete(ubdnskey); + ldns_rr_list_free(ds); + ldns_rr_list_free(dnskey); + return 0; + } + } + /* we have prepared the new keys so nothing can go wrong any more. + * And we are sure we cannot be left without trustanchor after + * any errors. Put in the new keys and remove old ones. */ + + /* free the old data */ + autr_rrset_delete(tp->ds_rrset); + autr_rrset_delete(tp->dnskey_rrset); + + /* assign the data to replace the old */ + tp->ds_rrset = ubds; + tp->dnskey_rrset = ubdnskey; + tp->numDS = ldns_rr_list_rr_count(ds); + tp->numDNSKEY = ldns_rr_list_rr_count(dnskey); + + ldns_rr_list_free(ds); + ldns_rr_list_free(dnskey); + return 1; +} + +/** parse integer */ +static unsigned int +parse_int(char* line, int* ret) +{ + char *e; + unsigned int x = (unsigned int)strtol(line, &e, 10); + if(line == e) { + *ret = -1; /* parse error */ + return 0; + } + *ret = 1; /* matched */ + return x; +} + +/** parse id sequence for anchor */ +static struct trust_anchor* +parse_id(struct val_anchors* anchors, char* line) +{ + struct trust_anchor *tp; + int r; + ldns_rdf* rdf; + uint16_t dclass; + /* read the owner name */ + char* next = strchr(line, ' '); + if(!next) + return NULL; + next[0] = 0; + rdf = ldns_dname_new_frm_str(line); + if(!rdf) + return NULL; + + /* read the class */ + dclass = parse_int(next+1, &r); + if(r == -1) { + ldns_rdf_deep_free(rdf); + return NULL; + } + + /* find the trust point */ + tp = autr_tp_create(anchors, rdf, dclass); + ldns_rdf_deep_free(rdf); + return tp; +} + +/** + * Parse variable from trustanchor header + * @param line: to parse + * @param anchors: the anchor is added to this, if "id:" is seen. + * @param anchor: the anchor as result value or previously returned anchor + * value to read the variable lines into. + * @return: 0 no match, -1 failed syntax error, +1 success line read. + * +2 revoked trust anchor file. + */ +static int +parse_var_line(char* line, struct val_anchors* anchors, + struct trust_anchor** anchor) +{ + struct trust_anchor* tp = *anchor; + int r = 0; + if(strncmp(line, ";;id: ", 6) == 0) { + *anchor = parse_id(anchors, line+6); + if(!*anchor) return -1; + else return 1; + } else if(strncmp(line, ";;REVOKED", 9) == 0) { + if(tp) { + log_err("REVOKED statement must be at start of file"); + return -1; + } + return 2; + } else if(strncmp(line, ";;last_queried: ", 16) == 0) { + if(!tp) return -1; + lock_basic_lock(&tp->lock); + tp->autr->last_queried = (time_t)parse_int(line+16, &r); + lock_basic_unlock(&tp->lock); + } else if(strncmp(line, ";;last_success: ", 16) == 0) { + if(!tp) return -1; + lock_basic_lock(&tp->lock); + tp->autr->last_success = (time_t)parse_int(line+16, &r); + lock_basic_unlock(&tp->lock); + } else if(strncmp(line, ";;next_probe_time: ", 19) == 0) { + if(!tp) return -1; + lock_basic_lock(&anchors->lock); + lock_basic_lock(&tp->lock); + (void)rbtree_delete(&anchors->autr->probe, tp); + tp->autr->next_probe_time = (time_t)parse_int(line+19, &r); + (void)rbtree_insert(&anchors->autr->probe, &tp->autr->pnode); + lock_basic_unlock(&tp->lock); + lock_basic_unlock(&anchors->lock); + } else if(strncmp(line, ";;query_failed: ", 16) == 0) { + if(!tp) return -1; + lock_basic_lock(&tp->lock); + tp->autr->query_failed = (uint8_t)parse_int(line+16, &r); + lock_basic_unlock(&tp->lock); + } else if(strncmp(line, ";;query_interval: ", 18) == 0) { + if(!tp) return -1; + lock_basic_lock(&tp->lock); + tp->autr->query_interval = (uint32_t)parse_int(line+18, &r); + lock_basic_unlock(&tp->lock); + } else if(strncmp(line, ";;retry_time: ", 14) == 0) { + if(!tp) return -1; + lock_basic_lock(&tp->lock); + tp->autr->retry_time = (uint32_t)parse_int(line+14, &r); + lock_basic_unlock(&tp->lock); + } + return r; +} + +/** handle origin lines */ +static int +handle_origin(char* line, ldns_rdf** origin) +{ + while(isspace((int)*line)) + line++; + if(strncmp(line, "$ORIGIN", 7) != 0) + return 0; + ldns_rdf_deep_free(*origin); + line += 7; + while(isspace((int)*line)) + line++; + *origin = ldns_dname_new_frm_str(line); + if(!*origin) + log_warn("malloc failure or parse error in $ORIGIN"); + return 1; +} + +/** Read one line and put multiline RRs onto one line string */ +static int +read_multiline(char* buf, size_t len, FILE* in, int* linenr) +{ + char* pos = buf; + size_t left = len; + int depth = 0; + buf[len-1] = 0; + while(left > 0 && fgets(pos, (int)left, in) != NULL) { + size_t i, poslen = strlen(pos); + (*linenr)++; + + /* check what the new depth is after the line */ + /* this routine cannot handle braces inside quotes, + say for TXT records, but this routine only has to read keys */ + for(i=0; i<poslen; i++) { + if(pos[i] == '(') { + depth++; + } else if(pos[i] == ')') { + if(depth == 0) { + log_err("mismatch: too many ')'"); + return -1; + } + depth--; + } else if(pos[i] == ';') { + break; + } + } + + /* normal oneline or last line: keeps newline and comments */ + if(depth == 0) { + return 1; + } + + /* more lines expected, snip off comments and newline */ + if(poslen>0) + pos[poslen-1] = 0; /* strip newline */ + if(strchr(pos, ';')) + strchr(pos, ';')[0] = 0; /* strip comments */ + + /* move to paste other lines behind this one */ + poslen = strlen(pos); + pos += poslen; + left -= poslen; + /* the newline is changed into a space */ + if(left <= 2 /* space and eos */) { + log_err("line too long"); + return -1; + } + pos[0] = ' '; + pos[1] = 0; + pos += 1; + left -= 1; + } + if(depth != 0) { + log_err("mismatch: too many '('"); + return -1; + } + if(pos != buf) + return 1; + return 0; +} + +int autr_read_file(struct val_anchors* anchors, const char* nm) +{ + /* the file descriptor */ + FILE* fd; + /* keep track of line numbers */ + int line_nr = 0; + /* single line */ + char line[10240]; + /* trust point being read */ + struct trust_anchor *tp = NULL, *tp2; + int r; + /* for $ORIGIN parsing */ + ldns_rdf *origin=NULL, *prev=NULL; + + if (!(fd = fopen(nm, "r"))) { + log_err("unable to open %s for reading: %s", + nm, strerror(errno)); + return 0; + } + verbose(VERB_ALGO, "reading autotrust anchor file %s", nm); + while ( (r=read_multiline(line, sizeof(line), fd, &line_nr)) != 0) { + if(r == -1 || (r = parse_var_line(line, anchors, &tp)) == -1) { + log_err("could not parse auto-trust-anchor-file " + "%s line %d", nm, line_nr); + fclose(fd); + ldns_rdf_deep_free(origin); + ldns_rdf_deep_free(prev); + return 0; + } else if(r == 1) { + continue; + } else if(r == 2) { + log_warn("trust anchor %s has been revoked", nm); + fclose(fd); + ldns_rdf_deep_free(origin); + ldns_rdf_deep_free(prev); + return 1; + } + if (!str_contains_data(line, ';')) + continue; /* empty lines allowed */ + if(handle_origin(line, &origin)) + continue; + r = 0; + if(!(tp2=load_trustanchor(anchors, line, nm, origin, &prev, + &r))) { + if(!r) log_err("failed to load trust anchor from %s " + "at line %i, skipping", nm, line_nr); + /* try to do the rest */ + continue; + } + if(tp && tp != tp2) { + log_err("file %s has mismatching data inside: " + "the file may only contain keys for one name, " + "remove keys for other domain names", nm); + fclose(fd); + ldns_rdf_deep_free(origin); + ldns_rdf_deep_free(prev); + return 0; + } + tp = tp2; + } + fclose(fd); + ldns_rdf_deep_free(origin); + ldns_rdf_deep_free(prev); + if(!tp) { + log_err("failed to read %s", nm); + return 0; + } + + /* now assemble the data into DNSKEY and DS packed rrsets */ + lock_basic_lock(&tp->lock); + if(!autr_assemble(tp)) { + lock_basic_unlock(&tp->lock); + log_err("malloc failure assembling %s", nm); + return 0; + } + lock_basic_unlock(&tp->lock); + return 1; +} + +/** string for a trustanchor state */ +static const char* +trustanchor_state2str(autr_state_t s) +{ + switch (s) { + case AUTR_STATE_START: return " START "; + case AUTR_STATE_ADDPEND: return " ADDPEND "; + case AUTR_STATE_VALID: return " VALID "; + case AUTR_STATE_MISSING: return " MISSING "; + case AUTR_STATE_REVOKED: return " REVOKED "; + case AUTR_STATE_REMOVED: return " REMOVED "; + } + return " UNKNOWN "; +} + +/** print ID to file */ +static int +print_id(FILE* out, char* fname, struct module_env* env, + uint8_t* nm, size_t nmlen, uint16_t dclass) +{ + ldns_rdf rdf; +#ifdef UNBOUND_DEBUG + ldns_status s; +#endif + + memset(&rdf, 0, sizeof(rdf)); + ldns_rdf_set_data(&rdf, nm); + ldns_rdf_set_size(&rdf, nmlen); + ldns_rdf_set_type(&rdf, LDNS_RDF_TYPE_DNAME); + + ldns_buffer_clear(env->scratch_buffer); +#ifdef UNBOUND_DEBUG + s = +#endif + ldns_rdf2buffer_str_dname(env->scratch_buffer, &rdf); + log_assert(s == LDNS_STATUS_OK); + ldns_buffer_write_u8(env->scratch_buffer, 0); + ldns_buffer_flip(env->scratch_buffer); + if(fprintf(out, ";;id: %s %d\n", + (char*)ldns_buffer_begin(env->scratch_buffer), + (int)dclass) < 0) { + log_err("could not write to %s: %s", fname, strerror(errno)); + return 0; + } + return 1; +} + +static int +autr_write_contents(FILE* out, char* fn, struct module_env* env, + struct trust_anchor* tp) +{ + char tmi[32]; + struct autr_ta* ta; + char* str; + + /* write pretty header */ + if(fprintf(out, "; autotrust trust anchor file\n") < 0) { + log_err("could not write to %s: %s", fn, strerror(errno)); + return 0; + } + if(tp->autr->revoked) { + if(fprintf(out, ";;REVOKED\n") < 0 || + fprintf(out, "; The zone has all keys revoked, and is\n" + "; considered as if it has no trust anchors.\n" + "; the remainder of the file is the last probe.\n" + "; to restart the trust anchor, overwrite this file.\n" + "; with one containing valid DNSKEYs or DSes.\n") < 0) { + log_err("could not write to %s: %s", fn, strerror(errno)); + return 0; + } + } + if(!print_id(out, fn, env, tp->name, tp->namelen, tp->dclass)) { + return 0; + } + if(fprintf(out, ";;last_queried: %u ;;%s", + (unsigned int)tp->autr->last_queried, + ctime_r(&(tp->autr->last_queried), tmi)) < 0 || + fprintf(out, ";;last_success: %u ;;%s", + (unsigned int)tp->autr->last_success, + ctime_r(&(tp->autr->last_success), tmi)) < 0 || + fprintf(out, ";;next_probe_time: %u ;;%s", + (unsigned int)tp->autr->next_probe_time, + ctime_r(&(tp->autr->next_probe_time), tmi)) < 0 || + fprintf(out, ";;query_failed: %d\n", (int)tp->autr->query_failed)<0 + || fprintf(out, ";;query_interval: %d\n", + (int)tp->autr->query_interval) < 0 || + fprintf(out, ";;retry_time: %d\n", (int)tp->autr->retry_time) < 0) { + log_err("could not write to %s: %s", fn, strerror(errno)); + return 0; + } + + /* write anchors */ + for(ta=tp->autr->keys; ta; ta=ta->next) { + /* by default do not store START and REMOVED keys */ + if(ta->s == AUTR_STATE_START) + continue; + if(ta->s == AUTR_STATE_REMOVED) + continue; + /* only store keys */ + if(ldns_rr_get_type(ta->rr) != LDNS_RR_TYPE_DNSKEY) + continue; + str = ldns_rr2str(ta->rr); + if(!str || !str[0]) { + free(str); + log_err("malloc failure writing %s", fn); + return 0; + } + str[strlen(str)-1] = 0; /* remove newline */ + if(fprintf(out, "%s ;;state=%d [%s] ;;count=%d " + ";;lastchange=%u ;;%s", str, (int)ta->s, + trustanchor_state2str(ta->s), (int)ta->pending_count, + (unsigned int)ta->last_change, + ctime_r(&(ta->last_change), tmi)) < 0) { + log_err("could not write to %s: %s", fn, strerror(errno)); + free(str); + return 0; + } + free(str); + } + return 1; +} + +void autr_write_file(struct module_env* env, struct trust_anchor* tp) +{ + FILE* out; + char* fname = tp->autr->file; + char tempf[2048]; + log_assert(tp->autr); + /* unique name with pid number and thread number */ + snprintf(tempf, sizeof(tempf), "%s.%d-%d", fname, (int)getpid(), + env&&env->worker?*(int*)env->worker:0); + verbose(VERB_ALGO, "autotrust: write to disk: %s", tempf); + out = fopen(tempf, "w"); + if(!out) { + log_err("could not open autotrust file for writing, %s: %s", + tempf, strerror(errno)); + return; + } + if(!autr_write_contents(out, tempf, env, tp)) { + /* failed to write contents (completely) */ + fclose(out); + unlink(tempf); + log_err("could not completely write: %s", fname); + return; + } + /* success; overwrite actual file */ + fclose(out); + verbose(VERB_ALGO, "autotrust: replaced %s", fname); +#ifdef UB_ON_WINDOWS + (void)unlink(fname); /* windows does not replace file with rename() */ +#endif + if(rename(tempf, fname) < 0) { + log_err("rename(%s to %s): %s", tempf, fname, strerror(errno)); + } +} + +/** + * Verify if dnskey works for trust point + * @param env: environment (with time) for verification + * @param ve: validator environment (with options) for verification. + * @param tp: trust point to verify with + * @param rrset: DNSKEY rrset to verify. + * @return false on failure, true if verification successful. + */ +static int +verify_dnskey(struct module_env* env, struct val_env* ve, + struct trust_anchor* tp, struct ub_packed_rrset_key* rrset) +{ + char* reason = NULL; + uint8_t sigalg[ALGO_NEEDS_MAX+1]; + int downprot = 1; + enum sec_status sec = val_verify_DNSKEY_with_TA(env, ve, rrset, + tp->ds_rrset, tp->dnskey_rrset, downprot?sigalg:NULL, &reason); + /* sigalg is ignored, it returns algorithms signalled to exist, but + * in 5011 there are no other rrsets to check. if downprot is + * enabled, then it checks that the DNSKEY is signed with all + * algorithms available in the trust store. */ + verbose(VERB_ALGO, "autotrust: validate DNSKEY with anchor: %s", + sec_status_to_string(sec)); + return sec == sec_status_secure; +} + +/** Find minimum expiration interval from signatures */ +static uint32_t +min_expiry(struct module_env* env, ldns_rr_list* rrset) +{ + size_t i; + uint32_t t, r = 15 * 24 * 3600; /* 15 days max */ + for(i=0; i<ldns_rr_list_rr_count(rrset); i++) { + ldns_rr* rr = ldns_rr_list_rr(rrset, i); + if(ldns_rr_get_type(rr) != LDNS_RR_TYPE_RRSIG) + continue; + t = ldns_rdf2native_int32(ldns_rr_rrsig_expiration(rr)); + if(t - *env->now > 0) { + t -= *env->now; + if(t < r) + r = t; + } + } + return r; +} + +/** Is rr self-signed revoked key */ +static int +rr_is_selfsigned_revoked(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey_rrset, size_t i) +{ + enum sec_status sec; + char* reason = NULL; + verbose(VERB_ALGO, "seen REVOKE flag, check self-signed, rr %d", + (int)i); + /* no algorithm downgrade protection necessary, if it is selfsigned + * revoked it can be removed. */ + sec = dnskey_verify_rrset(env, ve, dnskey_rrset, dnskey_rrset, i, + &reason); + return (sec == sec_status_secure); +} + +/** Set fetched value */ +static void +seen_trustanchor(struct autr_ta* ta, uint8_t seen) +{ + ta->fetched = seen; + if(ta->pending_count < 250) /* no numerical overflow, please */ + ta->pending_count++; +} + +/** set revoked value */ +static void +seen_revoked_trustanchor(struct autr_ta* ta, uint8_t revoked) +{ + ta->revoked = revoked; +} + +/** revoke a trust anchor */ +static void +revoke_dnskey(struct autr_ta* ta, int off) +{ + ldns_rdf* rdf; + uint16_t flags; + log_assert(ta && ta->rr); + if(ldns_rr_get_type(ta->rr) != LDNS_RR_TYPE_DNSKEY) + return; + rdf = ldns_rr_dnskey_flags(ta->rr); + flags = ldns_read_uint16(ldns_rdf_data(rdf)); + + if (off && (flags&LDNS_KEY_REVOKE_KEY)) + flags ^= LDNS_KEY_REVOKE_KEY; /* flip */ + else + flags |= LDNS_KEY_REVOKE_KEY; + ldns_write_uint16(ldns_rdf_data(rdf), flags); +} + +/** Compare two RR buffers skipping the REVOKED bit */ +static int +ldns_rr_compare_wire_skip_revbit(ldns_buffer* rr1_buf, ldns_buffer* rr2_buf) +{ + size_t rr1_len, rr2_len, min_len, i, offset; + rr1_len = ldns_buffer_capacity(rr1_buf); + rr2_len = ldns_buffer_capacity(rr2_buf); + /* jump past dname (checked in earlier part) and especially past TTL */ + offset = 0; + while (offset < rr1_len && *ldns_buffer_at(rr1_buf, offset) != 0) + offset += *ldns_buffer_at(rr1_buf, offset) + 1; + /* jump to rdata section (PAST the rdata length field) */ + offset += 11; /* 0-dname-end + type + class + ttl + rdatalen */ + min_len = (rr1_len < rr2_len) ? rr1_len : rr2_len; + /* compare RRs RDATA byte for byte. */ + for(i = offset; i < min_len; i++) + { + uint8_t *rdf1, *rdf2; + rdf1 = ldns_buffer_at(rr1_buf, i); + rdf2 = ldns_buffer_at(rr2_buf, i); + if (i==(offset+1)) + { + /* this is the second part of the flags field */ + *rdf1 = *rdf1 | LDNS_KEY_REVOKE_KEY; + *rdf2 = *rdf2 | LDNS_KEY_REVOKE_KEY; + } + if (*rdf1 < *rdf2) return -1; + else if (*rdf1 > *rdf2) return 1; + } + return 0; +} + +/** Compare two RRs skipping the REVOKED bit */ +static int +ldns_rr_compare_skip_revbit(const ldns_rr* rr1, const ldns_rr* rr2, int* result) +{ + size_t rr1_len, rr2_len; + ldns_buffer* rr1_buf; + ldns_buffer* rr2_buf; + + *result = ldns_rr_compare_no_rdata(rr1, rr2); + if (*result == 0) + { + rr1_len = ldns_rr_uncompressed_size(rr1); + rr2_len = ldns_rr_uncompressed_size(rr2); + rr1_buf = ldns_buffer_new(rr1_len); + rr2_buf = ldns_buffer_new(rr2_len); + if(!rr1_buf || !rr2_buf) { + ldns_buffer_free(rr1_buf); + ldns_buffer_free(rr2_buf); + return 0; + } + if (ldns_rr2buffer_wire_canonical(rr1_buf, rr1, + LDNS_SECTION_ANY) != LDNS_STATUS_OK) + { + ldns_buffer_free(rr1_buf); + ldns_buffer_free(rr2_buf); + return 0; + } + if (ldns_rr2buffer_wire_canonical(rr2_buf, rr2, + LDNS_SECTION_ANY) != LDNS_STATUS_OK) { + ldns_buffer_free(rr1_buf); + ldns_buffer_free(rr2_buf); + return 0; + } + *result = ldns_rr_compare_wire_skip_revbit(rr1_buf, rr2_buf); + ldns_buffer_free(rr1_buf); + ldns_buffer_free(rr2_buf); + } + return 1; +} + + +/** compare two trust anchors */ +static int +ta_compare(ldns_rr* a, ldns_rr* b, int* result) +{ + if (!a && !b) *result = 0; + else if (!a) *result = -1; + else if (!b) *result = 1; + else if (ldns_rr_get_type(a) != ldns_rr_get_type(b)) + *result = (int)ldns_rr_get_type(a) - (int)ldns_rr_get_type(b); + else if (ldns_rr_get_type(a) == LDNS_RR_TYPE_DNSKEY) { + if(!ldns_rr_compare_skip_revbit(a, b, result)) + return 0; + } + else if (ldns_rr_get_type(a) == LDNS_RR_TYPE_DS) + *result = ldns_rr_compare(a, b); + else *result = -1; + return 1; +} + +/** + * Find key + * @param tp: to search in + * @param rr: to look for + * @param result: returns NULL or the ta key looked for. + * @return false on malloc failure during search. if true examine result. + */ +static int +find_key(struct trust_anchor* tp, ldns_rr* rr, struct autr_ta** result) +{ + struct autr_ta* ta; + int ret; + if(!tp || !rr) + return 0; + for(ta=tp->autr->keys; ta; ta=ta->next) { + if(!ta_compare(ta->rr, rr, &ret)) + return 0; + if(ret == 0) { + *result = ta; + return 1; + } + } + *result = NULL; + return 1; +} + +/** add key and clone RR and tp already locked */ +static struct autr_ta* +add_key(struct trust_anchor* tp, ldns_rr* rr) +{ + ldns_rr* c; + struct autr_ta* ta; + c = ldns_rr_clone(rr); + if(!c) return NULL; + ta = autr_ta_create(c); + if(!ta) { + ldns_rr_free(c); + return NULL; + } + /* link in, tp already locked */ + ta->next = tp->autr->keys; + tp->autr->keys = ta; + return ta; +} + +/** get TTL from DNSKEY rrset */ +static uint32_t +key_ttl(struct ub_packed_rrset_key* k) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + return d->ttl; +} + +/** update the time values for the trustpoint */ +static void +set_tp_times(struct trust_anchor* tp, uint32_t rrsig_exp_interval, + uint32_t origttl, int* changed) +{ + uint32_t x, qi = tp->autr->query_interval, rt = tp->autr->retry_time; + + /* x = MIN(15days, ttl/2, expire/2) */ + x = 15 * 24 * 3600; + if(origttl/2 < x) + x = origttl/2; + if(rrsig_exp_interval/2 < x) + x = rrsig_exp_interval/2; + /* MAX(1hr, x) */ + if(x < 3600) + tp->autr->query_interval = 3600; + else tp->autr->query_interval = x; + + /* x= MIN(1day, ttl/10, expire/10) */ + x = 24 * 3600; + if(origttl/10 < x) + x = origttl/10; + if(rrsig_exp_interval/10 < x) + x = rrsig_exp_interval/10; + /* MAX(1hr, x) */ + if(x < 3600) + tp->autr->retry_time = 3600; + else tp->autr->retry_time = x; + + if(qi != tp->autr->query_interval || rt != tp->autr->retry_time) { + *changed = 1; + verbose(VERB_ALGO, "orig_ttl is %d", (int)origttl); + verbose(VERB_ALGO, "rrsig_exp_interval is %d", + (int)rrsig_exp_interval); + verbose(VERB_ALGO, "query_interval: %d, retry_time: %d", + (int)tp->autr->query_interval, + (int)tp->autr->retry_time); + } +} + +/** init events to zero */ +static void +init_events(struct trust_anchor* tp) +{ + struct autr_ta* ta; + for(ta=tp->autr->keys; ta; ta=ta->next) { + ta->fetched = 0; + } +} + +/** check for revoked keys without trusting any other information */ +static void +check_contains_revoked(struct module_env* env, struct val_env* ve, + struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, + int* changed) +{ + ldns_rr_list* r = packed_rrset_to_rr_list(dnskey_rrset, + env->scratch_buffer); + size_t i; + if(!r) { + log_err("malloc failure"); + return; + } + for(i=0; i<ldns_rr_list_rr_count(r); i++) { + ldns_rr* rr = ldns_rr_list_rr(r, i); + struct autr_ta* ta = NULL; + if(ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY) + continue; + if(!rr_is_dnskey_sep(rr) || !rr_is_dnskey_revoked(rr)) + continue; /* not a revoked KSK */ + if(!find_key(tp, rr, &ta)) { + log_err("malloc failure"); + continue; /* malloc fail in compare*/ + } + if(!ta) + continue; /* key not found */ + if(rr_is_selfsigned_revoked(env, ve, dnskey_rrset, i)) { + /* checked if there is an rrsig signed by this key. */ + log_assert(dnskey_calc_keytag(dnskey_rrset, i) == + ldns_calc_keytag(rr)); /* checks conversion*/ + verbose_key(ta, VERB_ALGO, "is self-signed revoked"); + if(!ta->revoked) + *changed = 1; + seen_revoked_trustanchor(ta, 1); + do_revoked(env, ta, changed); + } + } + ldns_rr_list_deep_free(r); +} + +/** See if a DNSKEY is verified by one of the DSes */ +static int +key_matches_a_ds(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey_rrset, size_t key_idx, + struct ub_packed_rrset_key* ds_rrset) +{ + struct packed_rrset_data* dd = (struct packed_rrset_data*) + ds_rrset->entry.data; + size_t ds_idx, num = dd->count; + int d = val_favorite_ds_algo(ds_rrset); + char* reason = ""; + for(ds_idx=0; ds_idx<num; ds_idx++) { + if(!ds_digest_algo_is_supported(ds_rrset, ds_idx) || + !ds_key_algo_is_supported(ds_rrset, ds_idx) || + ds_get_digest_algo(ds_rrset, ds_idx) != d) + continue; + if(ds_get_key_algo(ds_rrset, ds_idx) + != dnskey_get_algo(dnskey_rrset, key_idx) + || dnskey_calc_keytag(dnskey_rrset, key_idx) + != ds_get_keytag(ds_rrset, ds_idx)) { + continue; + } + if(!ds_digest_match_dnskey(env, dnskey_rrset, key_idx, + ds_rrset, ds_idx)) { + verbose(VERB_ALGO, "DS match attempt failed"); + continue; + } + if(dnskey_verify_rrset(env, ve, dnskey_rrset, + dnskey_rrset, key_idx, &reason) == sec_status_secure) { + return 1; + } else { + verbose(VERB_ALGO, "DS match failed because the key " + "does not verify the keyset: %s", reason); + } + } + return 0; +} + +/** Set update events */ +static int +update_events(struct module_env* env, struct val_env* ve, + struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, + int* changed) +{ + ldns_rr_list* r = packed_rrset_to_rr_list(dnskey_rrset, + env->scratch_buffer); + size_t i; + if(!r) + return 0; + init_events(tp); + for(i=0; i<ldns_rr_list_rr_count(r); i++) { + ldns_rr* rr = ldns_rr_list_rr(r, i); + struct autr_ta* ta = NULL; + if(ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY) + continue; + if(!rr_is_dnskey_sep(rr)) + continue; + if(rr_is_dnskey_revoked(rr)) { + /* self-signed revoked keys already detected before, + * other revoked keys are not 'added' again */ + continue; + } + /* is a key of this type supported?. Note rr_list and + * packed_rrset are in the same order. */ + if(!dnskey_algo_is_supported(dnskey_rrset, i)) { + /* skip unknown algorithm key, it is useless to us */ + log_nametypeclass(VERB_DETAIL, "trust point has " + "unsupported algorithm at", + tp->name, LDNS_RR_TYPE_DNSKEY, tp->dclass); + continue; + } + + /* is it new? if revocation bit set, find the unrevoked key */ + if(!find_key(tp, rr, &ta)) { + ldns_rr_list_deep_free(r); /* malloc fail in compare*/ + return 0; + } + if(!ta) { + ta = add_key(tp, rr); + *changed = 1; + /* first time seen, do we have DSes? if match: VALID */ + if(ta && tp->ds_rrset && key_matches_a_ds(env, ve, + dnskey_rrset, i, tp->ds_rrset)) { + verbose_key(ta, VERB_ALGO, "verified by DS"); + ta->s = AUTR_STATE_VALID; + } + } + if(!ta) { + ldns_rr_list_deep_free(r); + return 0; + } + seen_trustanchor(ta, 1); + verbose_key(ta, VERB_ALGO, "in DNS response"); + } + set_tp_times(tp, min_expiry(env, r), key_ttl(dnskey_rrset), changed); + ldns_rr_list_deep_free(r); + return 1; +} + +/** + * Check if the holddown time has already exceeded + * setting: add-holddown: add holddown timer + * setting: del-holddown: del holddown timer + * @param env: environment with current time + * @param ta: trust anchor to check for. + * @param holddown: the timer value + * @return number of seconds the holddown has passed. + */ +static int +check_holddown(struct module_env* env, struct autr_ta* ta, + unsigned int holddown) +{ + unsigned int elapsed; + if((unsigned)*env->now < (unsigned)ta->last_change) { + log_warn("time goes backwards. delaying key holddown"); + return 0; + } + elapsed = (unsigned)*env->now - (unsigned)ta->last_change; + if (elapsed > holddown) { + return (int) (elapsed-holddown); + } + verbose_key(ta, VERB_ALGO, "holddown time %d seconds to go", + (int) (holddown-elapsed)); + return 0; +} + + +/** Set last_change to now */ +static void +reset_holddown(struct module_env* env, struct autr_ta* ta, int* changed) +{ + ta->last_change = *env->now; + *changed = 1; +} + +/** Set the state for this trust anchor */ +static void +set_trustanchor_state(struct module_env* env, struct autr_ta* ta, int* changed, + autr_state_t s) +{ + verbose_key(ta, VERB_ALGO, "update: %s to %s", + trustanchor_state2str(ta->s), trustanchor_state2str(s)); + ta->s = s; + reset_holddown(env, ta, changed); +} + + +/** Event: NewKey */ +static void +do_newkey(struct module_env* env, struct autr_ta* anchor, int* c) +{ + if (anchor->s == AUTR_STATE_START) + set_trustanchor_state(env, anchor, c, AUTR_STATE_ADDPEND); +} + +/** Event: AddTime */ +static void +do_addtime(struct module_env* env, struct autr_ta* anchor, int* c) +{ + /* This not according to RFC, this is 30 days, but the RFC demands + * MAX(30days, TTL expire time of first DNSKEY set with this key), + * The value may be too small if a very large TTL was used. */ + int exceeded = check_holddown(env, anchor, env->cfg->add_holddown); + if (exceeded && anchor->s == AUTR_STATE_ADDPEND) { + verbose_key(anchor, VERB_ALGO, "add-holddown time exceeded " + "%d seconds ago, and pending-count %d", exceeded, + anchor->pending_count); + if(anchor->pending_count >= MIN_PENDINGCOUNT) { + set_trustanchor_state(env, anchor, c, AUTR_STATE_VALID); + anchor->pending_count = 0; + return; + } + verbose_key(anchor, VERB_ALGO, "add-holddown time sanity check " + "failed (pending count: %d)", anchor->pending_count); + } +} + +/** Event: RemTime */ +static void +do_remtime(struct module_env* env, struct autr_ta* anchor, int* c) +{ + int exceeded = check_holddown(env, anchor, env->cfg->del_holddown); + if(exceeded && anchor->s == AUTR_STATE_REVOKED) { + verbose_key(anchor, VERB_ALGO, "del-holddown time exceeded " + "%d seconds ago", exceeded); + set_trustanchor_state(env, anchor, c, AUTR_STATE_REMOVED); + } +} + +/** Event: KeyRem */ +static void +do_keyrem(struct module_env* env, struct autr_ta* anchor, int* c) +{ + if(anchor->s == AUTR_STATE_ADDPEND) { + set_trustanchor_state(env, anchor, c, AUTR_STATE_START); + anchor->pending_count = 0; + } else if(anchor->s == AUTR_STATE_VALID) + set_trustanchor_state(env, anchor, c, AUTR_STATE_MISSING); +} + +/** Event: KeyPres */ +static void +do_keypres(struct module_env* env, struct autr_ta* anchor, int* c) +{ + if(anchor->s == AUTR_STATE_MISSING) + set_trustanchor_state(env, anchor, c, AUTR_STATE_VALID); +} + +/* Event: Revoked */ +static void +do_revoked(struct module_env* env, struct autr_ta* anchor, int* c) +{ + if(anchor->s == AUTR_STATE_VALID || anchor->s == AUTR_STATE_MISSING) { + set_trustanchor_state(env, anchor, c, AUTR_STATE_REVOKED); + verbose_key(anchor, VERB_ALGO, "old id, prior to revocation"); + revoke_dnskey(anchor, 0); + verbose_key(anchor, VERB_ALGO, "new id, after revocation"); + } +} + +/** Do statestable transition matrix for anchor */ +static void +anchor_state_update(struct module_env* env, struct autr_ta* anchor, int* c) +{ + log_assert(anchor); + switch(anchor->s) { + /* START */ + case AUTR_STATE_START: + /* NewKey: ADDPEND */ + if (anchor->fetched) + do_newkey(env, anchor, c); + break; + /* ADDPEND */ + case AUTR_STATE_ADDPEND: + /* KeyRem: START */ + if (!anchor->fetched) + do_keyrem(env, anchor, c); + /* AddTime: VALID */ + else do_addtime(env, anchor, c); + break; + /* VALID */ + case AUTR_STATE_VALID: + /* RevBit: REVOKED */ + if (anchor->revoked) + do_revoked(env, anchor, c); + /* KeyRem: MISSING */ + else if (!anchor->fetched) + do_keyrem(env, anchor, c); + else if(!anchor->last_change) { + verbose_key(anchor, VERB_ALGO, "first seen"); + reset_holddown(env, anchor, c); + } + break; + /* MISSING */ + case AUTR_STATE_MISSING: + /* RevBit: REVOKED */ + if (anchor->revoked) + do_revoked(env, anchor, c); + /* KeyPres */ + else if (anchor->fetched) + do_keypres(env, anchor, c); + break; + /* REVOKED */ + case AUTR_STATE_REVOKED: + if (anchor->fetched) + reset_holddown(env, anchor, c); + /* RemTime: REMOVED */ + else do_remtime(env, anchor, c); + break; + /* REMOVED */ + case AUTR_STATE_REMOVED: + default: + break; + } +} + +/** if ZSK init then trust KSKs */ +static int +init_zsk_to_ksk(struct module_env* env, struct trust_anchor* tp, int* changed) +{ + /* search for VALID ZSKs */ + struct autr_ta* anchor; + int validzsk = 0; + int validksk = 0; + for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { + /* last_change test makes sure it was manually configured */ + if (ldns_rr_get_type(anchor->rr) == LDNS_RR_TYPE_DNSKEY && + anchor->last_change == 0 && + !rr_is_dnskey_sep(anchor->rr) && + anchor->s == AUTR_STATE_VALID) + validzsk++; + } + if(validzsk == 0) + return 0; + for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { + if (rr_is_dnskey_sep(anchor->rr) && + anchor->s == AUTR_STATE_ADDPEND) { + verbose_key(anchor, VERB_ALGO, "trust KSK from " + "ZSK(config)"); + set_trustanchor_state(env, anchor, changed, + AUTR_STATE_VALID); + validksk++; + } + } + return validksk; +} + +/** Remove missing trustanchors so the list does not grow forever */ +static void +remove_missing_trustanchors(struct module_env* env, struct trust_anchor* tp, + int* changed) +{ + struct autr_ta* anchor; + int exceeded; + int valid = 0; + /* see if we have anchors that are valid */ + for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { + /* Only do KSKs */ + if (!rr_is_dnskey_sep(anchor->rr)) + continue; + if (anchor->s == AUTR_STATE_VALID) + valid++; + } + /* if there are no SEP Valid anchors, see if we started out with + * a ZSK (last-change=0) anchor, which is VALID and there are KSKs + * now that can be made valid. Do this immediately because there + * is no guarantee that the ZSKs get announced long enough. Usually + * this is immediately after init with a ZSK trusted, unless the domain + * was not advertising any KSKs at all. In which case we perfectly + * track the zero number of KSKs. */ + if(valid == 0) { + valid = init_zsk_to_ksk(env, tp, changed); + if(valid == 0) + return; + } + + for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { + /* ignore ZSKs if newly added */ + if(anchor->s == AUTR_STATE_START) + continue; + /* remove ZSKs if a KSK is present */ + if (!rr_is_dnskey_sep(anchor->rr)) { + if(valid > 0) { + verbose_key(anchor, VERB_ALGO, "remove ZSK " + "[%d key(s) VALID]", valid); + set_trustanchor_state(env, anchor, changed, + AUTR_STATE_REMOVED); + } + continue; + } + /* Only do MISSING keys */ + if (anchor->s != AUTR_STATE_MISSING) + continue; + if(env->cfg->keep_missing == 0) + continue; /* keep forever */ + + exceeded = check_holddown(env, anchor, env->cfg->keep_missing); + /* If keep_missing has exceeded and we still have more than + * one valid KSK: remove missing trust anchor */ + if (exceeded && valid > 0) { + verbose_key(anchor, VERB_ALGO, "keep-missing time " + "exceeded %d seconds ago, [%d key(s) VALID]", + exceeded, valid); + set_trustanchor_state(env, anchor, changed, + AUTR_STATE_REMOVED); + } + } +} + +/** Do the statetable from RFC5011 transition matrix */ +static int +do_statetable(struct module_env* env, struct trust_anchor* tp, int* changed) +{ + struct autr_ta* anchor; + for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { + /* Only do KSKs */ + if(!rr_is_dnskey_sep(anchor->rr)) + continue; + anchor_state_update(env, anchor, changed); + } + remove_missing_trustanchors(env, tp, changed); + return 1; +} + +/** See if time alone makes ADDPEND to VALID transition */ +static void +autr_holddown_exceed(struct module_env* env, struct trust_anchor* tp, int* c) +{ + struct autr_ta* anchor; + for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { + if(rr_is_dnskey_sep(anchor->rr) && + anchor->s == AUTR_STATE_ADDPEND) + do_addtime(env, anchor, c); + } +} + +/** cleanup key list */ +static void +autr_cleanup_keys(struct trust_anchor* tp) +{ + struct autr_ta* p, **prevp; + prevp = &tp->autr->keys; + p = tp->autr->keys; + while(p) { + /* do we want to remove this key? */ + if(p->s == AUTR_STATE_START || p->s == AUTR_STATE_REMOVED || + ldns_rr_get_type(p->rr) != LDNS_RR_TYPE_DNSKEY) { + struct autr_ta* np = p->next; + /* remove */ + ldns_rr_free(p->rr); + free(p); + /* snip and go to next item */ + *prevp = np; + p = np; + continue; + } + /* remove pending counts if no longer pending */ + if(p->s != AUTR_STATE_ADDPEND) + p->pending_count = 0; + prevp = &p->next; + p = p->next; + } +} + +/** calculate next probe time */ +static time_t +calc_next_probe(struct module_env* env, uint32_t wait) +{ + /* make it random, 90-100% */ + uint32_t rnd, rest; + if(wait < 3600) + wait = 3600; + rnd = wait/10; + rest = wait-rnd; + rnd = (uint32_t)ub_random_max(env->rnd, (long int)rnd); + return (time_t)(*env->now + rest + rnd); +} + +/** what is first probe time (anchors must be locked) */ +static time_t +wait_probe_time(struct val_anchors* anchors) +{ + rbnode_t* t = rbtree_first(&anchors->autr->probe); + if(t != RBTREE_NULL) + return ((struct trust_anchor*)t->key)->autr->next_probe_time; + return 0; +} + +/** reset worker timer */ +static void +reset_worker_timer(struct module_env* env) +{ + struct timeval tv; +#ifndef S_SPLINT_S + uint32_t next = (uint32_t)wait_probe_time(env->anchors); + /* in case this is libunbound, no timer */ + if(!env->probe_timer) + return; + if(next > *env->now) + tv.tv_sec = (time_t)(next - *env->now); + else tv.tv_sec = 0; +#endif + tv.tv_usec = 0; + comm_timer_set(env->probe_timer, &tv); + verbose(VERB_ALGO, "scheduled next probe in %d sec", (int)tv.tv_sec); +} + +/** set next probe for trust anchor */ +static int +set_next_probe(struct module_env* env, struct trust_anchor* tp, + struct ub_packed_rrset_key* dnskey_rrset) +{ + struct trust_anchor key, *tp2; + time_t mold, mnew; + /* use memory allocated in rrset for temporary name storage */ + key.node.key = &key; + key.name = dnskey_rrset->rk.dname; + key.namelen = dnskey_rrset->rk.dname_len; + key.namelabs = dname_count_labels(key.name); + key.dclass = tp->dclass; + lock_basic_unlock(&tp->lock); + + /* fetch tp again and lock anchors, so that we can modify the trees */ + lock_basic_lock(&env->anchors->lock); + tp2 = (struct trust_anchor*)rbtree_search(env->anchors->tree, &key); + if(!tp2) { + verbose(VERB_ALGO, "trustpoint was deleted in set_next_probe"); + lock_basic_unlock(&env->anchors->lock); + return 0; + } + log_assert(tp == tp2); + lock_basic_lock(&tp->lock); + + /* schedule */ + mold = wait_probe_time(env->anchors); + (void)rbtree_delete(&env->anchors->autr->probe, tp); + tp->autr->next_probe_time = calc_next_probe(env, + tp->autr->query_interval); + (void)rbtree_insert(&env->anchors->autr->probe, &tp->autr->pnode); + mnew = wait_probe_time(env->anchors); + + lock_basic_unlock(&env->anchors->lock); + verbose(VERB_ALGO, "next probe set in %d seconds", + (int)tp->autr->next_probe_time - (int)*env->now); + if(mold != mnew) { + reset_worker_timer(env); + } + return 1; +} + +/** Revoke and Delete a trust point */ +static void +autr_tp_remove(struct module_env* env, struct trust_anchor* tp, + struct ub_packed_rrset_key* dnskey_rrset) +{ + struct trust_anchor key; + struct autr_point_data pd; + time_t mold, mnew; + + log_nametypeclass(VERB_OPS, "trust point was revoked", + tp->name, LDNS_RR_TYPE_DNSKEY, tp->dclass); + tp->autr->revoked = 1; + + /* use space allocated for dnskey_rrset to save name of anchor */ + memset(&key, 0, sizeof(key)); + memset(&pd, 0, sizeof(pd)); + key.autr = &pd; + key.node.key = &key; + pd.pnode.key = &key; + pd.next_probe_time = tp->autr->next_probe_time; + key.name = dnskey_rrset->rk.dname; + key.namelen = tp->namelen; + key.namelabs = tp->namelabs; + key.dclass = tp->dclass; + + /* unlock */ + lock_basic_unlock(&tp->lock); + + /* take from tree. It could be deleted by someone else,hence (void). */ + lock_basic_lock(&env->anchors->lock); + (void)rbtree_delete(env->anchors->tree, &key); + mold = wait_probe_time(env->anchors); + (void)rbtree_delete(&env->anchors->autr->probe, &key); + mnew = wait_probe_time(env->anchors); + anchors_init_parents_locked(env->anchors); + lock_basic_unlock(&env->anchors->lock); + + /* save on disk */ + tp->autr->next_probe_time = 0; /* no more probing for it */ + autr_write_file(env, tp); + + /* delete */ + autr_point_delete(tp); + if(mold != mnew) { + reset_worker_timer(env); + } +} + +int autr_process_prime(struct module_env* env, struct val_env* ve, + struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset) +{ + int changed = 0; + log_assert(tp && tp->autr); + /* autotrust update trust anchors */ + /* the tp is locked, and stays locked unless it is deleted */ + + /* we could just catch the anchor here while another thread + * is busy deleting it. Just unlock and let the other do its job */ + if(tp->autr->revoked) { + log_nametypeclass(VERB_ALGO, "autotrust not processed, " + "trust point revoked", tp->name, + LDNS_RR_TYPE_DNSKEY, tp->dclass); + lock_basic_unlock(&tp->lock); + return 0; /* it is revoked */ + } + + /* query_dnskeys(): */ + tp->autr->last_queried = *env->now; + + log_nametypeclass(VERB_ALGO, "autotrust process for", + tp->name, LDNS_RR_TYPE_DNSKEY, tp->dclass); + /* see if time alone makes some keys valid */ + autr_holddown_exceed(env, tp, &changed); + if(changed) { + verbose(VERB_ALGO, "autotrust: morekeys, reassemble"); + if(!autr_assemble(tp)) { + log_err("malloc failure assembling autotrust keys"); + return 1; /* unchanged */ + } + } + /* did we get any data? */ + if(!dnskey_rrset) { + verbose(VERB_ALGO, "autotrust: no dnskey rrset"); + /* no update of query_failed, because then we would have + * to write to disk. But we cannot because we maybe are + * still 'initialising' with DS records, that we cannot write + * in the full format (which only contains KSKs). */ + return 1; /* trust point exists */ + } + /* check for revoked keys to remove immediately */ + check_contains_revoked(env, ve, tp, dnskey_rrset, &changed); + if(changed) { + verbose(VERB_ALGO, "autotrust: revokedkeys, reassemble"); + if(!autr_assemble(tp)) { + log_err("malloc failure assembling autotrust keys"); + return 1; /* unchanged */ + } + if(!tp->ds_rrset && !tp->dnskey_rrset) { + /* no more keys, all are revoked */ + /* this is a success for this probe attempt */ + tp->autr->last_success = *env->now; + autr_tp_remove(env, tp, dnskey_rrset); + return 0; /* trust point removed */ + } + } + /* verify the dnskey rrset and see if it is valid. */ + if(!verify_dnskey(env, ve, tp, dnskey_rrset)) { + verbose(VERB_ALGO, "autotrust: dnskey did not verify."); + /* only increase failure count if this is not the first prime, + * this means there was a previous succesful probe */ + if(tp->autr->last_success) { + tp->autr->query_failed += 1; + autr_write_file(env, tp); + } + return 1; /* trust point exists */ + } + + tp->autr->last_success = *env->now; + tp->autr->query_failed = 0; + + /* Add new trust anchors to the data structure + * - note which trust anchors are seen this probe. + * Set trustpoint query_interval and retry_time. + * - find minimum rrsig expiration interval + */ + if(!update_events(env, ve, tp, dnskey_rrset, &changed)) { + log_err("malloc failure in autotrust update_events. " + "trust point unchanged."); + return 1; /* trust point unchanged, so exists */ + } + + /* - for every SEP key do the 5011 statetable. + * - remove missing trustanchors (if veryold and we have new anchors). + */ + if(!do_statetable(env, tp, &changed)) { + log_err("malloc failure in autotrust do_statetable. " + "trust point unchanged."); + return 1; /* trust point unchanged, so exists */ + } + + autr_cleanup_keys(tp); + if(!set_next_probe(env, tp, dnskey_rrset)) + return 0; /* trust point does not exist */ + autr_write_file(env, tp); + if(changed) { + verbose(VERB_ALGO, "autotrust: changed, reassemble"); + if(!autr_assemble(tp)) { + log_err("malloc failure assembling autotrust keys"); + return 1; /* unchanged */ + } + if(!tp->ds_rrset && !tp->dnskey_rrset) { + /* no more keys, all are revoked */ + autr_tp_remove(env, tp, dnskey_rrset); + return 0; /* trust point removed */ + } + } else verbose(VERB_ALGO, "autotrust: no changes"); + + return 1; /* trust point exists */ +} + +/** debug print a trust anchor key */ +static void +autr_debug_print_ta(struct autr_ta* ta) +{ + char buf[32]; + char* str = ldns_rr2str(ta->rr); + if(!str) { + log_info("out of memory in debug_print_ta"); + return; + } + if(str && str[0]) str[strlen(str)-1]=0; /* remove newline */ + ctime_r(&ta->last_change, buf); + if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ + log_info("[%s] %s ;;state:%d ;;pending_count:%d%s%s last:%s", + trustanchor_state2str(ta->s), str, ta->s, ta->pending_count, + ta->fetched?" fetched":"", ta->revoked?" revoked":"", buf); + free(str); +} + +/** debug print a trust point */ +static void +autr_debug_print_tp(struct trust_anchor* tp) +{ + struct autr_ta* ta; + char buf[257]; + if(!tp->autr) + return; + dname_str(tp->name, buf); + log_info("trust point %s : %d", buf, (int)tp->dclass); + log_info("assembled %d DS and %d DNSKEYs", + (int)tp->numDS, (int)tp->numDNSKEY); + if(0) { /* turned off because it prints to stderr */ + ldns_buffer* bf = ldns_buffer_new(70000); + ldns_rr_list* list; + if(tp->ds_rrset) { + list = packed_rrset_to_rr_list(tp->ds_rrset, bf); + ldns_rr_list_print(stderr, list); + ldns_rr_list_deep_free(list); + } + if(tp->dnskey_rrset) { + list = packed_rrset_to_rr_list(tp->dnskey_rrset, bf); + ldns_rr_list_print(stderr, list); + ldns_rr_list_deep_free(list); + } + ldns_buffer_free(bf); + } + log_info("file %s", tp->autr->file); + ctime_r(&tp->autr->last_queried, buf); + if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ + log_info("last_queried: %u %s", (unsigned)tp->autr->last_queried, buf); + ctime_r(&tp->autr->last_success, buf); + if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ + log_info("last_success: %u %s", (unsigned)tp->autr->last_success, buf); + ctime_r(&tp->autr->next_probe_time, buf); + if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ + log_info("next_probe_time: %u %s", (unsigned)tp->autr->next_probe_time, + buf); + log_info("query_interval: %u", (unsigned)tp->autr->query_interval); + log_info("retry_time: %u", (unsigned)tp->autr->retry_time); + log_info("query_failed: %u", (unsigned)tp->autr->query_failed); + + for(ta=tp->autr->keys; ta; ta=ta->next) { + autr_debug_print_ta(ta); + } +} + +void +autr_debug_print(struct val_anchors* anchors) +{ + struct trust_anchor* tp; + lock_basic_lock(&anchors->lock); + RBTREE_FOR(tp, struct trust_anchor*, anchors->tree) { + lock_basic_lock(&tp->lock); + autr_debug_print_tp(tp); + lock_basic_unlock(&tp->lock); + } + lock_basic_unlock(&anchors->lock); +} + +void probe_answer_cb(void* arg, int ATTR_UNUSED(rcode), + ldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(sec), + char* ATTR_UNUSED(why_bogus)) +{ + /* retry was set before the query was done, + * re-querytime is set when query succeeded, but that may not + * have reset this timer because the query could have been + * handled by another thread. In that case, this callback would + * get called after the original timeout is done. + * By not resetting the timer, it may probe more often, but not + * less often. + * Unless the new lookup resulted in smaller TTLs and thus smaller + * timeout values. In that case one old TTL could be mistakenly done. + */ + struct module_env* env = (struct module_env*)arg; + verbose(VERB_ALGO, "autotrust probe answer cb"); + reset_worker_timer(env); +} + +/** probe a trust anchor DNSKEY and unlocks tp */ +static void +probe_anchor(struct module_env* env, struct trust_anchor* tp) +{ + struct query_info qinfo; + uint16_t qflags = BIT_RD; + struct edns_data edns; + ldns_buffer* buf = env->scratch_buffer; + qinfo.qname = regional_alloc_init(env->scratch, tp->name, tp->namelen); + if(!qinfo.qname) { + log_err("out of memory making 5011 probe"); + return; + } + qinfo.qname_len = tp->namelen; + qinfo.qtype = LDNS_RR_TYPE_DNSKEY; + qinfo.qclass = tp->dclass; + log_query_info(VERB_ALGO, "autotrust probe", &qinfo); + verbose(VERB_ALGO, "retry probe set in %d seconds", + (int)tp->autr->next_probe_time - (int)*env->now); + edns.edns_present = 1; + edns.ext_rcode = 0; + edns.edns_version = 0; + edns.bits = EDNS_DO; + if(ldns_buffer_capacity(buf) < 65535) + edns.udp_size = (uint16_t)ldns_buffer_capacity(buf); + else edns.udp_size = 65535; + + /* can't hold the lock while mesh_run is processing */ + lock_basic_unlock(&tp->lock); + + /* delete the DNSKEY from rrset and key cache so an active probe + * is done. First the rrset so another thread does not use it + * to recreate the key entry in a race condition. */ + rrset_cache_remove(env->rrset_cache, qinfo.qname, qinfo.qname_len, + qinfo.qtype, qinfo.qclass, 0); + key_cache_remove(env->key_cache, qinfo.qname, qinfo.qname_len, + qinfo.qclass); + + if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, + &probe_answer_cb, env)) { + log_err("out of memory making 5011 probe"); + } +} + +/** fetch first to-probe trust-anchor and lock it and set retrytime */ +static struct trust_anchor* +todo_probe(struct module_env* env, uint32_t* next) +{ + struct trust_anchor* tp; + rbnode_t* el; + /* get first one */ + lock_basic_lock(&env->anchors->lock); + if( (el=rbtree_first(&env->anchors->autr->probe)) == RBTREE_NULL) { + /* in case of revoked anchors */ + lock_basic_unlock(&env->anchors->lock); + return NULL; + } + tp = (struct trust_anchor*)el->key; + lock_basic_lock(&tp->lock); + + /* is it eligible? */ + if((uint32_t)tp->autr->next_probe_time > *env->now) { + /* no more to probe */ + *next = (uint32_t)tp->autr->next_probe_time - *env->now; + lock_basic_unlock(&tp->lock); + lock_basic_unlock(&env->anchors->lock); + return NULL; + } + + /* reset its next probe time */ + (void)rbtree_delete(&env->anchors->autr->probe, tp); + tp->autr->next_probe_time = calc_next_probe(env, tp->autr->retry_time); + (void)rbtree_insert(&env->anchors->autr->probe, &tp->autr->pnode); + lock_basic_unlock(&env->anchors->lock); + + return tp; +} + +uint32_t +autr_probe_timer(struct module_env* env) +{ + struct trust_anchor* tp; + uint32_t next_probe = 3600; + int num = 0; + verbose(VERB_ALGO, "autotrust probe timer callback"); + /* while there are still anchors to probe */ + while( (tp = todo_probe(env, &next_probe)) ) { + /* make a probe for this anchor */ + probe_anchor(env, tp); + num++; + } + regional_free_all(env->scratch); + if(num == 0) + return 0; /* no trust points to probe */ + verbose(VERB_ALGO, "autotrust probe timer %d callbacks done", num); + return next_probe; +} diff --git a/validator/autotrust.h b/validator/autotrust.h new file mode 100644 index 0000000000000..4e88ed32042a3 --- /dev/null +++ b/validator/autotrust.h @@ -0,0 +1,205 @@ +/* + * validator/autotrust.h - RFC5011 trust anchor management for unbound. + * + * Copyright (c) 2009, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * Contains autotrust definitions. + */ + +#ifndef VALIDATOR_AUTOTRUST_H +#define VALIDATOR_AUTOTRUST_H +#include "util/rbtree.h" +#include "util/data/packed_rrset.h" +struct val_anchors; +struct trust_anchor; +struct ub_packed_rrset_key; +struct module_env; +struct val_env; + +/** Autotrust anchor states */ +typedef enum { + AUTR_STATE_START = 0, + AUTR_STATE_ADDPEND = 1, + AUTR_STATE_VALID = 2, + AUTR_STATE_MISSING = 3, + AUTR_STATE_REVOKED = 4, + AUTR_STATE_REMOVED = 5 +} autr_state_t; + +/** + * Autotrust metadata for one trust anchor key. + */ +struct autr_ta { + /** next key */ + struct autr_ta* next; + /** the RR */ + ldns_rr* rr; + /** last update of key state (new pending count keeps date the same) */ + time_t last_change; + /** 5011 state */ + autr_state_t s; + /** pending count */ + uint8_t pending_count; + /** fresh TA was seen */ + uint8_t fetched; + /** revoked TA was seen */ + uint8_t revoked; +}; + +/** + * Autotrust metadata for a trust point. + * This is part of the struct trust_anchor data. + */ +struct autr_point_data { + /** file to store the trust point in. chrootdir already applied. */ + char* file; + /** rbtree node for probe sort, key is struct trust_anchor */ + rbnode_t pnode; + + /** the keys */ + struct autr_ta* keys; + + /** last queried DNSKEY set + * Not all failures are captured in this entry. + * If the validator did not even start (e.g. timeout or localservfail), + * then the last_queried and query_failed values are not updated. + */ + time_t last_queried; + /** last successful DNSKEY set */ + time_t last_success; + /** next probe time */ + time_t next_probe_time; + + /** when to query if !failed */ + uint32_t query_interval; + /** when to retry if failed */ + uint32_t retry_time; + + /** + * How many times did it fail. diagnostic only (has no effect). + * Only updated if there was a dnskey rrset that failed to verify. + */ + uint8_t query_failed; + /** true if the trust point has been revoked */ + uint8_t revoked; +}; + +/** + * Autotrust global metadata. + */ +struct autr_global_data { + /** rbtree of autotrust anchors sorted by next probe time. + * When time is equal, sorted by anchor class, name. */ + rbtree_t probe; +}; + +/** + * Create new global 5011 data structure. + * @return new structure or NULL on malloc failure. + */ +struct autr_global_data* autr_global_create(void); + +/** + * Delete global 5011 data structure. + * @param global: global autotrust state to delete. + */ +void autr_global_delete(struct autr_global_data* global); + +/** + * See if autotrust anchors are configured and how many. + * @param anchors: the trust anchors structure. + * @return number of autotrust trust anchors + */ +size_t autr_get_num_anchors(struct val_anchors* anchors); + +/** + * Process probe timer. Add new probes if needed. + * @param env: module environment with time, with anchors and with the mesh. + * @return time of next probe (in seconds from now). + * If 0, then there is no next probe anymore (trust points deleted). + */ +uint32_t autr_probe_timer(struct module_env* env); + +/** probe tree compare function */ +int probetree_cmp(const void* x, const void* y); + +/** + * Read autotrust file. + * @param anchors: the anchors structure. + * @param nm: name of the file (copied). + * @return false on failure. + */ +int autr_read_file(struct val_anchors* anchors, const char* nm); + +/** + * Write autotrust file. + * @param env: environment with scratch space. + * @param tp: trust point to write. + */ +void autr_write_file(struct module_env* env, struct trust_anchor* tp); + +/** + * Delete autr anchor, deletes the autr data but does not do + * unlinking from trees, caller does that. + * @param tp: trust point to delete. + */ +void autr_point_delete(struct trust_anchor* tp); + +/** + * Perform autotrust processing. + * @param env: qstate environment with the anchors structure. + * @param ve: validator environment for verification of rrsigs. + * @param tp: trust anchor to process. + * @param dnskey_rrset: DNSKEY rrset probed (can be NULL if bad prime result). + * allocated in a region. Has not been validated yet. + * @return false if trust anchor was revoked completely. + * Otherwise logs errors to log, does not change return value. + * On errors, likely the trust point has been unchanged. + */ +int autr_process_prime(struct module_env* env, struct val_env* ve, + struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset); + +/** + * Debug printout of rfc5011 tracked anchors + * @param anchors: all the anchors. + */ +void autr_debug_print(struct val_anchors* anchors); + +/** callback for query answer to 5011 probe */ +void probe_answer_cb(void* arg, int rcode, ldns_buffer* buf, + enum sec_status sec, char* errinf); + +#endif /* VALIDATOR_AUTOTRUST_H */ diff --git a/validator/val_anchor.c b/validator/val_anchor.c new file mode 100644 index 0000000000000..200bf5d97be61 --- /dev/null +++ b/validator/val_anchor.c @@ -0,0 +1,1263 @@ +/* + * validator/val_anchor.c - validator trust anchor storage. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains storage for the trust anchors for the validator. + */ +#include "config.h" +#include <ctype.h> +#include <ldns/dname.h> +#include <ldns/host2wire.h> +#include "validator/val_anchor.h" +#include "validator/val_sigcrypt.h" +#include "validator/autotrust.h" +#include "util/data/packed_rrset.h" +#include "util/data/dname.h" +#include "util/log.h" +#include "util/net_help.h" +#include "util/config_file.h" +#ifdef HAVE_GLOB_H +#include <glob.h> +#endif + +int +anchor_cmp(const void* k1, const void* k2) +{ + int m; + struct trust_anchor* n1 = (struct trust_anchor*)k1; + struct trust_anchor* n2 = (struct trust_anchor*)k2; + /* no need to ntohs(class) because sort order is irrelevant */ + if(n1->dclass != n2->dclass) { + if(n1->dclass < n2->dclass) + return -1; + return 1; + } + return dname_lab_cmp(n1->name, n1->namelabs, n2->name, n2->namelabs, + &m); +} + +struct val_anchors* +anchors_create(void) +{ + struct val_anchors* a = (struct val_anchors*)calloc(1, sizeof(*a)); + if(!a) + return NULL; + a->tree = rbtree_create(anchor_cmp); + if(!a->tree) { + anchors_delete(a); + return NULL; + } + a->autr = autr_global_create(); + if(!a->autr) { + anchors_delete(a); + return NULL; + } + lock_basic_init(&a->lock); + lock_protect(&a->lock, a, sizeof(*a)); + lock_protect(&a->lock, a->autr, sizeof(*a->autr)); + return a; +} + +/** delete assembled rrset */ +static void +assembled_rrset_delete(struct ub_packed_rrset_key* pkey) +{ + if(!pkey) return; + if(pkey->entry.data) { + struct packed_rrset_data* pd = (struct packed_rrset_data*) + pkey->entry.data; + free(pd->rr_data); + free(pd->rr_ttl); + free(pd->rr_len); + free(pd); + } + free(pkey->rk.dname); + free(pkey); +} + +/** destroy locks in tree and delete autotrust anchors */ +static void +anchors_delfunc(rbnode_t* elem, void* ATTR_UNUSED(arg)) +{ + struct trust_anchor* ta = (struct trust_anchor*)elem; + if(!ta) return; + if(ta->autr) { + autr_point_delete(ta); + } else { + struct ta_key* p, *np; + lock_basic_destroy(&ta->lock); + free(ta->name); + p = ta->keylist; + while(p) { + np = p->next; + free(p->data); + free(p); + p = np; + } + assembled_rrset_delete(ta->ds_rrset); + assembled_rrset_delete(ta->dnskey_rrset); + free(ta); + } +} + +void +anchors_delete(struct val_anchors* anchors) +{ + if(!anchors) + return; + lock_unprotect(&anchors->lock, anchors->autr); + lock_unprotect(&anchors->lock, anchors); + lock_basic_destroy(&anchors->lock); + if(anchors->tree) + traverse_postorder(anchors->tree, anchors_delfunc, NULL); + free(anchors->tree); + autr_global_delete(anchors->autr); + free(anchors); +} + +void +anchors_init_parents_locked(struct val_anchors* anchors) +{ + struct trust_anchor* node, *prev = NULL, *p; + int m; + /* nobody else can grab locks because we hold the main lock. + * Thus the previous items, after unlocked, are not deleted */ + RBTREE_FOR(node, struct trust_anchor*, anchors->tree) { + lock_basic_lock(&node->lock); + node->parent = NULL; + if(!prev || prev->dclass != node->dclass) { + prev = node; + lock_basic_unlock(&node->lock); + continue; + } + (void)dname_lab_cmp(prev->name, prev->namelabs, node->name, + node->namelabs, &m); /* we know prev is smaller */ + /* sort order like: . com. bla.com. zwb.com. net. */ + /* find the previous, or parent-parent-parent */ + for(p = prev; p; p = p->parent) + /* looking for name with few labels, a parent */ + if(p->namelabs <= m) { + /* ==: since prev matched m, this is closest*/ + /* <: prev matches more, but is not a parent, + * this one is a (grand)parent */ + node->parent = p; + break; + } + lock_basic_unlock(&node->lock); + prev = node; + } +} + +/** initialise parent pointers in the tree */ +static void +init_parents(struct val_anchors* anchors) +{ + lock_basic_lock(&anchors->lock); + anchors_init_parents_locked(anchors); + lock_basic_unlock(&anchors->lock); +} + +struct trust_anchor* +anchor_find(struct val_anchors* anchors, uint8_t* name, int namelabs, + size_t namelen, uint16_t dclass) +{ + struct trust_anchor key; + rbnode_t* n; + if(!name) return NULL; + key.node.key = &key; + key.name = name; + key.namelabs = namelabs; + key.namelen = namelen; + key.dclass = dclass; + lock_basic_lock(&anchors->lock); + n = rbtree_search(anchors->tree, &key); + if(n) { + lock_basic_lock(&((struct trust_anchor*)n->key)->lock); + } + lock_basic_unlock(&anchors->lock); + if(!n) + return NULL; + return (struct trust_anchor*)n->key; +} + +/** create new trust anchor object */ +static struct trust_anchor* +anchor_new_ta(struct val_anchors* anchors, uint8_t* name, int namelabs, + size_t namelen, uint16_t dclass, int lockit) +{ +#ifdef UNBOUND_DEBUG + rbnode_t* r; +#endif + struct trust_anchor* ta = (struct trust_anchor*)malloc( + sizeof(struct trust_anchor)); + if(!ta) + return NULL; + memset(ta, 0, sizeof(*ta)); + ta->node.key = ta; + ta->name = memdup(name, namelen); + if(!ta->name) { + free(ta); + return NULL; + } + ta->namelabs = namelabs; + ta->namelen = namelen; + ta->dclass = dclass; + lock_basic_init(&ta->lock); + if(lockit) { + lock_basic_lock(&anchors->lock); + } +#ifdef UNBOUND_DEBUG + r = +#endif + rbtree_insert(anchors->tree, &ta->node); + if(lockit) { + lock_basic_unlock(&anchors->lock); + } + log_assert(r != NULL); + return ta; +} + +/** find trustanchor key by exact data match */ +static struct ta_key* +anchor_find_key(struct trust_anchor* ta, uint8_t* rdata, size_t rdata_len, + uint16_t type) +{ + struct ta_key* k; + for(k = ta->keylist; k; k = k->next) { + if(k->type == type && k->len == rdata_len && + memcmp(k->data, rdata, rdata_len) == 0) + return k; + } + return NULL; +} + +/** create new trustanchor key */ +static struct ta_key* +anchor_new_ta_key(uint8_t* rdata, size_t rdata_len, uint16_t type) +{ + struct ta_key* k = (struct ta_key*)malloc(sizeof(*k)); + if(!k) + return NULL; + memset(k, 0, sizeof(*k)); + k->data = memdup(rdata, rdata_len); + if(!k->data) { + free(k); + return NULL; + } + k->len = rdata_len; + k->type = type; + return k; +} + +/** + * This routine adds a new RR to a trust anchor. The trust anchor may not + * exist yet, and is created if not. The RR can be DS or DNSKEY. + * This routine will also remove duplicates; storing them only once. + * @param anchors: anchor storage. + * @param name: name of trust anchor (wireformat) + * @param type: type or RR + * @param dclass: class of RR + * @param rdata: rdata wireformat, starting with rdlength. + * If NULL, nothing is stored, but an entry is created. + * @param rdata_len: length of rdata including rdlength. + * @return: NULL on error, else the trust anchor. + */ +static struct trust_anchor* +anchor_store_new_key(struct val_anchors* anchors, uint8_t* name, uint16_t type, + uint16_t dclass, uint8_t* rdata, size_t rdata_len) +{ + struct ta_key* k; + struct trust_anchor* ta; + int namelabs; + size_t namelen; + namelabs = dname_count_size_labels(name, &namelen); + if(type != LDNS_RR_TYPE_DS && type != LDNS_RR_TYPE_DNSKEY) { + log_err("Bad type for trust anchor"); + return 0; + } + /* lookup or create trustanchor */ + ta = anchor_find(anchors, name, namelabs, namelen, dclass); + if(!ta) { + ta = anchor_new_ta(anchors, name, namelabs, namelen, dclass, 1); + if(!ta) + return NULL; + lock_basic_lock(&ta->lock); + } + if(!rdata) { + lock_basic_unlock(&ta->lock); + return ta; + } + /* look for duplicates */ + if(anchor_find_key(ta, rdata, rdata_len, type)) { + lock_basic_unlock(&ta->lock); + return ta; + } + k = anchor_new_ta_key(rdata, rdata_len, type); + if(!k) { + lock_basic_unlock(&ta->lock); + return NULL; + } + /* add new key */ + if(type == LDNS_RR_TYPE_DS) + ta->numDS++; + else ta->numDNSKEY++; + k->next = ta->keylist; + ta->keylist = k; + lock_basic_unlock(&ta->lock); + return ta; +} + +/** + * Add new RR. It converts ldns RR to wire format. + * @param anchors: anchor storage. + * @param buffer: parsing buffer. + * @param rr: the rr (allocated by caller). + * @return NULL on error, else the trust anchor. + */ +static struct trust_anchor* +anchor_store_new_rr(struct val_anchors* anchors, ldns_buffer* buffer, + ldns_rr* rr) +{ + struct trust_anchor* ta; + ldns_rdf* owner = ldns_rr_owner(rr); + ldns_status status; + ldns_buffer_clear(buffer); + ldns_buffer_skip(buffer, 2); /* skip rdatalen */ + status = ldns_rr_rdata2buffer_wire(buffer, rr); + if(status != LDNS_STATUS_OK) { + log_err("error converting trustanchor to wireformat: %s", + ldns_get_errorstr_by_id(status)); + return NULL; + } + ldns_buffer_flip(buffer); + ldns_buffer_write_u16_at(buffer, 0, ldns_buffer_limit(buffer) - 2); + + if(!(ta=anchor_store_new_key(anchors, ldns_rdf_data(owner), + ldns_rr_get_type(rr), ldns_rr_get_class(rr), + ldns_buffer_begin(buffer), ldns_buffer_limit(buffer)))) { + return NULL; + } + log_nametypeclass(VERB_QUERY, "adding trusted key", + ldns_rdf_data(owner), + ldns_rr_get_type(rr), ldns_rr_get_class(rr)); + return ta; +} + +/** + * Insert insecure anchor + * @param anchors: anchor storage. + * @param str: the domain name. + * @return NULL on error, Else last trust anchor point + */ +static struct trust_anchor* +anchor_insert_insecure(struct val_anchors* anchors, const char* str) +{ + struct trust_anchor* ta; + ldns_rdf* nm = ldns_dname_new_frm_str(str); + if(!nm) { + log_err("parse error in domain name '%s'", str); + return NULL; + } + ta = anchor_store_new_key(anchors, ldns_rdf_data(nm), LDNS_RR_TYPE_DS, + LDNS_RR_CLASS_IN, NULL, 0); + ldns_rdf_deep_free(nm); + return ta; +} + +struct trust_anchor* +anchor_store_str(struct val_anchors* anchors, ldns_buffer* buffer, + const char* str) +{ + struct trust_anchor* ta; + ldns_rr* rr = NULL; + ldns_status status = ldns_rr_new_frm_str(&rr, str, 0, NULL, NULL); + if(status != LDNS_STATUS_OK) { + log_err("error parsing trust anchor: %s", + ldns_get_errorstr_by_id(status)); + ldns_rr_free(rr); + return NULL; + } + if(!(ta=anchor_store_new_rr(anchors, buffer, rr))) { + log_err("out of memory"); + ldns_rr_free(rr); + return NULL; + } + ldns_rr_free(rr); + return ta; +} + +/** + * Read a file with trust anchors + * @param anchors: anchor storage. + * @param buffer: parsing buffer. + * @param fname: string. + * @param onlyone: only one trust anchor allowed in file. + * @return NULL on error. Else last trust-anchor point. + */ +static struct trust_anchor* +anchor_read_file(struct val_anchors* anchors, ldns_buffer* buffer, + const char* fname, int onlyone) +{ + struct trust_anchor* ta = NULL, *tanew; + uint32_t default_ttl = 3600; + ldns_rdf* origin = NULL, *prev = NULL; + int line_nr = 1; + ldns_status status; + ldns_rr* rr; + int ok = 1; + FILE* in = fopen(fname, "r"); + if(!in) { + log_err("error opening file %s: %s", fname, strerror(errno)); + return 0; + } + while(!feof(in)) { + rr = NULL; + status = ldns_rr_new_frm_fp_l(&rr, in, &default_ttl, &origin, + &prev, &line_nr); + if(status == LDNS_STATUS_SYNTAX_EMPTY /* empty line */ + || status == LDNS_STATUS_SYNTAX_TTL /* $TTL */ + || status == LDNS_STATUS_SYNTAX_ORIGIN /* $ORIGIN */) + continue; + if(status != LDNS_STATUS_OK) { + log_err("parse error in %s:%d : %s", fname, line_nr, + ldns_get_errorstr_by_id(status)); + ldns_rr_free(rr); + ok = 0; + break; + } + if(ldns_rr_get_type(rr) != LDNS_RR_TYPE_DS && + ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY) { + ldns_rr_free(rr); + continue; + } + if(!(tanew=anchor_store_new_rr(anchors, buffer, rr))) { + log_err("error at %s line %d", fname, line_nr); + ldns_rr_free(rr); + ok = 0; + break; + } + if(onlyone && ta && ta != tanew) { + log_err("error at %s line %d: no multiple anchor " + "domains allowed (you can have multiple " + "keys, but they must have the same name).", + fname, line_nr); + ldns_rr_free(rr); + ok = 0; + break; + } + ta = tanew; + ldns_rr_free(rr); + } + ldns_rdf_deep_free(origin); + ldns_rdf_deep_free(prev); + fclose(in); + if(!ok) return NULL; + /* empty file is OK when multiple anchors are allowed */ + if(!onlyone && !ta) return (struct trust_anchor*)1; + return ta; +} + +/** skip file to end of line */ +static void +skip_to_eol(FILE* in) +{ + int c; + while((c = getc(in)) != EOF ) { + if(c == '\n') + return; + } +} + +/** true for special characters in bind configs */ +static int +is_bind_special(int c) +{ + switch(c) { + case '{': + case '}': + case '"': + case ';': + return 1; + } + return 0; +} + +/** + * Read a keyword skipping bind comments; spaces, specials, restkeywords. + * The file is split into the following tokens: + * * special characters, on their own, rdlen=1, { } doublequote ; + * * whitespace becomes a single ' ' or tab. Newlines become spaces. + * * other words ('keywords') + * * comments are skipped if desired + * / / C++ style comment to end of line + * # to end of line + * / * C style comment * / + * @param in: file to read from. + * @param buf: buffer, what is read is stored after current buffer position. + * Space is left in the buffer to write a terminating 0. + * @param line: line number is increased per line, for error reports. + * @param comments: if 0, comments are not possible and become text. + * if 1, comments are skipped entirely. + * In BIND files, this is when reading quoted strings, for example + * " base 64 text with / / in there " + * @return the number of character written to the buffer. + * 0 on end of file. + */ +static int +readkeyword_bindfile(FILE* in, ldns_buffer* buf, int* line, int comments) +{ + int c; + int numdone = 0; + while((c = getc(in)) != EOF ) { + if(comments && c == '#') { /* # blabla */ + skip_to_eol(in); + (*line)++; + continue; + } else if(comments && c=='/' && numdone>0 && /* /_/ bla*/ + ldns_buffer_read_u8_at(buf, + ldns_buffer_position(buf)-1) == '/') { + ldns_buffer_skip(buf, -1); + numdone--; + skip_to_eol(in); + (*line)++; + continue; + } else if(comments && c=='*' && numdone>0 && /* /_* bla *_/ */ + ldns_buffer_read_u8_at(buf, + ldns_buffer_position(buf)-1) == '/') { + ldns_buffer_skip(buf, -1); + numdone--; + /* skip to end of comment */ + while(c != EOF && (c=getc(in)) != EOF ) { + if(c == '*') { + if((c=getc(in)) == '/') + break; + } + if(c == '\n') + (*line)++; + } + continue; + } + /* not a comment, complete the keyword */ + if(numdone > 0) { + /* check same type */ + if(isspace(c)) { + ungetc(c, in); + return numdone; + } + if(is_bind_special(c)) { + ungetc(c, in); + return numdone; + } + } + if(c == '\n') { + c = ' '; + (*line)++; + } + /* space for 1 char + 0 string terminator */ + if(ldns_buffer_remaining(buf) < 2) { + fatal_exit("trusted-keys, %d, string too long", *line); + } + ldns_buffer_write_u8(buf, (uint8_t)c); + numdone++; + if(isspace(c)) { + /* collate whitespace into ' ' */ + while((c = getc(in)) != EOF ) { + if(c == '\n') + (*line)++; + if(!isspace(c)) { + ungetc(c, in); + break; + } + } + return numdone; + } + if(is_bind_special(c)) + return numdone; + } + return numdone; +} + +/** skip through file to { or ; */ +static int +skip_to_special(FILE* in, ldns_buffer* buf, int* line, int spec) +{ + int rdlen; + ldns_buffer_clear(buf); + while((rdlen=readkeyword_bindfile(in, buf, line, 1))) { + if(rdlen == 1 && isspace((int)*ldns_buffer_begin(buf))) { + ldns_buffer_clear(buf); + continue; + } + if(rdlen != 1 || *ldns_buffer_begin(buf) != (uint8_t)spec) { + ldns_buffer_write_u8(buf, 0); + log_err("trusted-keys, line %d, expected %c", + *line, spec); + return 0; + } + return 1; + } + log_err("trusted-keys, line %d, expected %c got EOF", *line, spec); + return 0; +} + +/** + * read contents of trusted-keys{ ... ; clauses and insert keys into storage. + * @param anchors: where to store keys + * @param buf: buffer to use + * @param line: line number in file + * @param in: file to read from. + * @return 0 on error. + */ +static int +process_bind_contents(struct val_anchors* anchors, ldns_buffer* buf, + int* line, FILE* in) +{ + /* loop over contents, collate strings before ; */ + /* contents is (numbered): 0 1 2 3 4 5 6 7 8 */ + /* name. 257 3 5 base64 base64 */ + /* quoted value: 0 "111" 0 0 0 0 0 0 0 */ + /* comments value: 1 "000" 1 1 1 "0 0 0 0" 1 */ + int contnum = 0; + int quoted = 0; + int comments = 1; + int rdlen; + char* str = 0; + ldns_buffer_clear(buf); + while((rdlen=readkeyword_bindfile(in, buf, line, comments))) { + if(rdlen == 1 && ldns_buffer_position(buf) == 1 + && isspace((int)*ldns_buffer_begin(buf))) { + /* starting whitespace is removed */ + ldns_buffer_clear(buf); + continue; + } else if(rdlen == 1 && ldns_buffer_current(buf)[-1] == '"') { + /* remove " from the string */ + if(contnum == 0) { + quoted = 1; + comments = 0; + } + ldns_buffer_skip(buf, -1); + if(contnum > 0 && quoted) { + if(ldns_buffer_remaining(buf) < 8+1) { + log_err("line %d, too long", *line); + return 0; + } + ldns_buffer_write(buf, " DNSKEY ", 8); + quoted = 0; + comments = 1; + } else if(contnum > 0) + comments = !comments; + continue; + } else if(rdlen == 1 && ldns_buffer_current(buf)[-1] == ';') { + + if(contnum < 5) { + ldns_buffer_write_u8(buf, 0); + log_err("line %d, bad key", *line); + return 0; + } + ldns_buffer_skip(buf, -1); + ldns_buffer_write_u8(buf, 0); + str = strdup((char*)ldns_buffer_begin(buf)); + if(!str) { + log_err("line %d, allocation failure", *line); + return 0; + } + if(!anchor_store_str(anchors, buf, str)) { + log_err("line %d, bad key", *line); + free(str); + return 0; + } + free(str); + ldns_buffer_clear(buf); + contnum = 0; + quoted = 0; + comments = 1; + continue; + } else if(rdlen == 1 && ldns_buffer_current(buf)[-1] == '}') { + if(contnum > 0) { + ldns_buffer_write_u8(buf, 0); + log_err("line %d, bad key before }", *line); + return 0; + } + return 1; + } else if(rdlen == 1 && + isspace((int)ldns_buffer_current(buf)[-1])) { + /* leave whitespace here */ + } else { + /* not space or whatnot, so actual content */ + contnum ++; + if(contnum == 1 && !quoted) { + if(ldns_buffer_remaining(buf) < 8+1) { + log_err("line %d, too long", *line); + return 0; + } + ldns_buffer_write(buf, " DNSKEY ", 8); + } + } + } + + log_err("line %d, EOF before }", *line); + return 0; +} + +/** + * Read a BIND9 like file with trust anchors in named.conf format. + * @param anchors: anchor storage. + * @param buffer: parsing buffer. + * @param fname: string. + * @return false on error. + */ +static int +anchor_read_bind_file(struct val_anchors* anchors, ldns_buffer* buffer, + const char* fname) +{ + int line_nr = 1; + FILE* in = fopen(fname, "r"); + int rdlen = 0; + if(!in) { + log_err("error opening file %s: %s", fname, strerror(errno)); + return 0; + } + verbose(VERB_QUERY, "reading in bind-compat-mode: '%s'", fname); + /* scan for trusted-keys keyword, ignore everything else */ + ldns_buffer_clear(buffer); + while((rdlen=readkeyword_bindfile(in, buffer, &line_nr, 1)) != 0) { + if(rdlen != 12 || strncmp((char*)ldns_buffer_begin(buffer), + "trusted-keys", 12) != 0) { + ldns_buffer_clear(buffer); + /* ignore everything but trusted-keys */ + continue; + } + if(!skip_to_special(in, buffer, &line_nr, '{')) { + log_err("error in trusted key: \"%s\"", fname); + fclose(in); + return 0; + } + /* process contents */ + if(!process_bind_contents(anchors, buffer, &line_nr, in)) { + log_err("error in trusted key: \"%s\"", fname); + fclose(in); + return 0; + } + if(!skip_to_special(in, buffer, &line_nr, ';')) { + log_err("error in trusted key: \"%s\"", fname); + fclose(in); + return 0; + } + ldns_buffer_clear(buffer); + } + fclose(in); + return 1; +} + +/** + * Read a BIND9 like files with trust anchors in named.conf format. + * Performs wildcard processing of name. + * @param anchors: anchor storage. + * @param buffer: parsing buffer. + * @param pat: pattern string. (can be wildcarded) + * @return false on error. + */ +static int +anchor_read_bind_file_wild(struct val_anchors* anchors, ldns_buffer* buffer, + const char* pat) +{ +#ifdef HAVE_GLOB + glob_t g; + size_t i; + int r, flags; + if(!strchr(pat, '*') && !strchr(pat, '?') && !strchr(pat, '[') && + !strchr(pat, '{') && !strchr(pat, '~')) { + return anchor_read_bind_file(anchors, buffer, pat); + } + verbose(VERB_QUERY, "wildcard found, processing %s", pat); + flags = 0 +#ifdef GLOB_ERR + | GLOB_ERR +#endif +#ifdef GLOB_NOSORT + | GLOB_NOSORT +#endif +#ifdef GLOB_BRACE + | GLOB_BRACE +#endif +#ifdef GLOB_TILDE + | GLOB_TILDE +#endif + ; + memset(&g, 0, sizeof(g)); + r = glob(pat, flags, NULL, &g); + if(r) { + /* some error */ + if(r == GLOB_NOMATCH) { + verbose(VERB_QUERY, "trusted-keys-file: " + "no matches for %s", pat); + return 1; + } else if(r == GLOB_NOSPACE) { + log_err("wildcard trusted-keys-file %s: " + "pattern out of memory", pat); + } else if(r == GLOB_ABORTED) { + log_err("wildcard trusted-keys-file %s: expansion " + "aborted (%s)", pat, strerror(errno)); + } else { + log_err("wildcard trusted-keys-file %s: expansion " + "failed (%s)", pat, strerror(errno)); + } + return 0; + } + /* process files found, if any */ + for(i=0; i<(size_t)g.gl_pathc; i++) { + if(!anchor_read_bind_file(anchors, buffer, g.gl_pathv[i])) { + log_err("error reading wildcard " + "trusted-keys-file: %s", g.gl_pathv[i]); + globfree(&g); + return 0; + } + } + globfree(&g); + return 1; +#else /* not HAVE_GLOB */ + return anchor_read_bind_file(anchors, buffer, pat); +#endif /* HAVE_GLOB */ +} + +/** + * Assemble an rrset structure for the type + * @param ta: trust anchor. + * @param num: number of items to fetch from list. + * @param type: fetch only items of this type. + * @return rrset or NULL on error. + */ +static struct ub_packed_rrset_key* +assemble_it(struct trust_anchor* ta, size_t num, uint16_t type) +{ + struct ub_packed_rrset_key* pkey = (struct ub_packed_rrset_key*) + malloc(sizeof(*pkey)); + struct packed_rrset_data* pd; + struct ta_key* tk; + size_t i; + if(!pkey) + return NULL; + memset(pkey, 0, sizeof(*pkey)); + pkey->rk.dname = memdup(ta->name, ta->namelen); + if(!pkey->rk.dname) { + free(pkey); + return NULL; + } + + pkey->rk.dname_len = ta->namelen; + pkey->rk.type = htons(type); + pkey->rk.rrset_class = htons(ta->dclass); + /* The rrset is build in an uncompressed way. This means it + * cannot be copied in the normal way. */ + pd = (struct packed_rrset_data*)malloc(sizeof(*pd)); + if(!pd) { + free(pkey->rk.dname); + free(pkey); + return NULL; + } + memset(pd, 0, sizeof(*pd)); + pd->count = num; + pd->trust = rrset_trust_ultimate; + pd->rr_len = (size_t*)malloc(num*sizeof(size_t)); + if(!pd->rr_len) { + free(pd); + free(pkey->rk.dname); + free(pkey); + return NULL; + } + pd->rr_ttl = (uint32_t*)malloc(num*sizeof(uint32_t)); + if(!pd->rr_ttl) { + free(pd->rr_len); + free(pd); + free(pkey->rk.dname); + free(pkey); + return NULL; + } + pd->rr_data = (uint8_t**)malloc(num*sizeof(uint8_t*)); + if(!pd->rr_data) { + free(pd->rr_ttl); + free(pd->rr_len); + free(pd); + free(pkey->rk.dname); + free(pkey); + return NULL; + } + /* fill in rrs */ + i=0; + for(tk = ta->keylist; tk; tk = tk->next) { + if(tk->type != type) + continue; + pd->rr_len[i] = tk->len; + /* reuse data ptr to allocation in talist */ + pd->rr_data[i] = tk->data; + pd->rr_ttl[i] = 0; + i++; + } + pkey->entry.data = (void*)pd; + return pkey; +} + +/** + * Assemble structures for the trust DS and DNSKEY rrsets. + * @param ta: trust anchor + * @return: false on error. + */ +static int +anchors_assemble(struct trust_anchor* ta) +{ + if(ta->numDS > 0) { + ta->ds_rrset = assemble_it(ta, ta->numDS, LDNS_RR_TYPE_DS); + if(!ta->ds_rrset) + return 0; + } + if(ta->numDNSKEY > 0) { + ta->dnskey_rrset = assemble_it(ta, ta->numDNSKEY, + LDNS_RR_TYPE_DNSKEY); + if(!ta->dnskey_rrset) + return 0; + } + return 1; +} + +/** + * Check DS algos for support, warn if not. + * @param ta: trust anchor + * @return number of DS anchors with unsupported algorithms. + */ +static size_t +anchors_ds_unsupported(struct trust_anchor* ta) +{ + size_t i, num = 0; + for(i=0; i<ta->numDS; i++) { + if(!ds_digest_algo_is_supported(ta->ds_rrset, i) || + !ds_key_algo_is_supported(ta->ds_rrset, i)) + num++; + } + return num; +} + +/** + * Check DNSKEY algos for support, warn if not. + * @param ta: trust anchor + * @return number of DNSKEY anchors with unsupported algorithms. + */ +static size_t +anchors_dnskey_unsupported(struct trust_anchor* ta) +{ + size_t i, num = 0; + for(i=0; i<ta->numDNSKEY; i++) { + if(!dnskey_algo_is_supported(ta->dnskey_rrset, i)) + num++; + } + return num; +} + +/** + * Assemble the rrsets in the anchors, ready for use by validator. + * @param anchors: trust anchor storage. + * @return: false on error. + */ +static int +anchors_assemble_rrsets(struct val_anchors* anchors) +{ + struct trust_anchor* ta; + struct trust_anchor* next; + size_t nods, nokey; + lock_basic_lock(&anchors->lock); + ta=(struct trust_anchor*)rbtree_first(anchors->tree); + while((rbnode_t*)ta != RBTREE_NULL) { + next = (struct trust_anchor*)rbtree_next(&ta->node); + lock_basic_lock(&ta->lock); + if(ta->autr || (ta->numDS == 0 && ta->numDNSKEY == 0)) { + lock_basic_unlock(&ta->lock); + ta = next; /* skip */ + continue; + } + if(!anchors_assemble(ta)) { + log_err("out of memory"); + lock_basic_unlock(&ta->lock); + lock_basic_unlock(&anchors->lock); + return 0; + } + nods = anchors_ds_unsupported(ta); + nokey = anchors_dnskey_unsupported(ta); + if(nods) { + log_nametypeclass(0, "warning: unsupported " + "algorithm for trust anchor", + ta->name, LDNS_RR_TYPE_DS, ta->dclass); + } + if(nokey) { + log_nametypeclass(0, "warning: unsupported " + "algorithm for trust anchor", + ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); + } + if(nods == ta->numDS && nokey == ta->numDNSKEY) { + char b[257]; + dname_str(ta->name, b); + log_warn("trust anchor %s has no supported algorithms," + " the anchor is ignored (check if you need to" + " upgrade unbound and openssl)", b); + (void)rbtree_delete(anchors->tree, &ta->node); + lock_basic_unlock(&ta->lock); + anchors_delfunc(&ta->node, NULL); + ta = next; + continue; + } + lock_basic_unlock(&ta->lock); + ta = next; + } + lock_basic_unlock(&anchors->lock); + return 1; +} + +int +anchors_apply_cfg(struct val_anchors* anchors, struct config_file* cfg) +{ + struct config_strlist* f; + char* nm; + ldns_buffer* parsebuf = ldns_buffer_new(65535); + for(f = cfg->domain_insecure; f; f = f->next) { + if(!f->str || f->str[0] == 0) /* empty "" */ + continue; + if(!anchor_insert_insecure(anchors, f->str)) { + log_err("error in domain-insecure: %s", f->str); + ldns_buffer_free(parsebuf); + return 0; + } + } + for(f = cfg->trust_anchor_file_list; f; f = f->next) { + if(!f->str || f->str[0] == 0) /* empty "" */ + continue; + nm = f->str; + if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, + cfg->chrootdir, strlen(cfg->chrootdir)) == 0) + nm += strlen(cfg->chrootdir); + if(!anchor_read_file(anchors, parsebuf, nm, 0)) { + log_err("error reading trust-anchor-file: %s", f->str); + ldns_buffer_free(parsebuf); + return 0; + } + } + for(f = cfg->trusted_keys_file_list; f; f = f->next) { + if(!f->str || f->str[0] == 0) /* empty "" */ + continue; + nm = f->str; + if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, + cfg->chrootdir, strlen(cfg->chrootdir)) == 0) + nm += strlen(cfg->chrootdir); + if(!anchor_read_bind_file_wild(anchors, parsebuf, nm)) { + log_err("error reading trusted-keys-file: %s", f->str); + ldns_buffer_free(parsebuf); + return 0; + } + } + for(f = cfg->trust_anchor_list; f; f = f->next) { + if(!f->str || f->str[0] == 0) /* empty "" */ + continue; + if(!anchor_store_str(anchors, parsebuf, f->str)) { + log_err("error in trust-anchor: \"%s\"", f->str); + ldns_buffer_free(parsebuf); + return 0; + } + } + if(cfg->dlv_anchor_file && cfg->dlv_anchor_file[0] != 0) { + struct trust_anchor* dlva; + nm = cfg->dlv_anchor_file; + if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, + cfg->chrootdir, strlen(cfg->chrootdir)) == 0) + nm += strlen(cfg->chrootdir); + if(!(dlva = anchor_read_file(anchors, parsebuf, + nm, 1))) { + log_err("error reading dlv-anchor-file: %s", + cfg->dlv_anchor_file); + ldns_buffer_free(parsebuf); + return 0; + } + lock_basic_lock(&anchors->lock); + anchors->dlv_anchor = dlva; + lock_basic_unlock(&anchors->lock); + } + for(f = cfg->dlv_anchor_list; f; f = f->next) { + struct trust_anchor* dlva; + if(!f->str || f->str[0] == 0) /* empty "" */ + continue; + if(!(dlva = anchor_store_str( + anchors, parsebuf, f->str))) { + log_err("error in dlv-anchor: \"%s\"", f->str); + ldns_buffer_free(parsebuf); + return 0; + } + lock_basic_lock(&anchors->lock); + anchors->dlv_anchor = dlva; + lock_basic_unlock(&anchors->lock); + } + /* do autr last, so that it sees what anchors are filled by other + * means can can print errors about double config for the name */ + for(f = cfg->auto_trust_anchor_file_list; f; f = f->next) { + if(!f->str || f->str[0] == 0) /* empty "" */ + continue; + nm = f->str; + if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, + cfg->chrootdir, strlen(cfg->chrootdir)) == 0) + nm += strlen(cfg->chrootdir); + if(!autr_read_file(anchors, nm)) { + log_err("error reading auto-trust-anchor-file: %s", + f->str); + ldns_buffer_free(parsebuf); + return 0; + } + } + /* first assemble, since it may delete useless anchors */ + anchors_assemble_rrsets(anchors); + init_parents(anchors); + ldns_buffer_free(parsebuf); + if(verbosity >= VERB_ALGO) autr_debug_print(anchors); + return 1; +} + +struct trust_anchor* +anchors_lookup(struct val_anchors* anchors, + uint8_t* qname, size_t qname_len, uint16_t qclass) +{ + struct trust_anchor key; + struct trust_anchor* result; + rbnode_t* res = NULL; + key.node.key = &key; + key.name = qname; + key.namelabs = dname_count_labels(qname); + key.namelen = qname_len; + key.dclass = qclass; + lock_basic_lock(&anchors->lock); + if(rbtree_find_less_equal(anchors->tree, &key, &res)) { + /* exact */ + result = (struct trust_anchor*)res; + } else { + /* smaller element (or no element) */ + int m; + result = (struct trust_anchor*)res; + if(!result || result->dclass != qclass) { + lock_basic_unlock(&anchors->lock); + return NULL; + } + /* count number of labels matched */ + (void)dname_lab_cmp(result->name, result->namelabs, key.name, + key.namelabs, &m); + while(result) { /* go up until qname is subdomain of stub */ + if(result->namelabs <= m) + break; + result = result->parent; + } + } + if(result) { + lock_basic_lock(&result->lock); + } + lock_basic_unlock(&anchors->lock); + return result; +} + +size_t +anchors_get_mem(struct val_anchors* anchors) +{ + struct trust_anchor *ta; + size_t s = sizeof(*anchors); + RBTREE_FOR(ta, struct trust_anchor*, anchors->tree) { + s += sizeof(*ta) + ta->namelen; + /* keys and so on */ + } + return s; +} + +int +anchors_add_insecure(struct val_anchors* anchors, uint16_t c, uint8_t* nm) +{ + struct trust_anchor key; + key.node.key = &key; + key.name = nm; + key.namelabs = dname_count_size_labels(nm, &key.namelen); + key.dclass = c; + lock_basic_lock(&anchors->lock); + if(rbtree_search(anchors->tree, &key)) { + lock_basic_unlock(&anchors->lock); + /* nothing to do, already an anchor or insecure point */ + return 1; + } + if(!anchor_new_ta(anchors, nm, key.namelabs, key.namelen, c, 0)) { + log_err("out of memory"); + lock_basic_unlock(&anchors->lock); + return 0; + } + /* no other contents in new ta, because it is insecure point */ + anchors_init_parents_locked(anchors); + lock_basic_unlock(&anchors->lock); + return 1; +} + +void +anchors_delete_insecure(struct val_anchors* anchors, uint16_t c, + uint8_t* nm) +{ + struct trust_anchor key; + struct trust_anchor* ta; + key.node.key = &key; + key.name = nm; + key.namelabs = dname_count_size_labels(nm, &key.namelen); + key.dclass = c; + lock_basic_lock(&anchors->lock); + if(!(ta=(struct trust_anchor*)rbtree_search(anchors->tree, &key))) { + lock_basic_unlock(&anchors->lock); + /* nothing there */ + return; + } + /* lock it to drive away other threads that use it */ + lock_basic_lock(&ta->lock); + /* see if its really an insecure point */ + if(ta->keylist || ta->autr || ta->numDS || ta->numDNSKEY) { + lock_basic_unlock(&ta->lock); + /* its not an insecure point, do not remove it */ + return; + } + + /* remove from tree */ + (void)rbtree_delete(anchors->tree, &ta->node); + anchors_init_parents_locked(anchors); + lock_basic_unlock(&anchors->lock); + + /* actual free of data */ + lock_basic_unlock(&ta->lock); + anchors_delfunc(&ta->node, NULL); +} + diff --git a/validator/val_anchor.h b/validator/val_anchor.h new file mode 100644 index 0000000000000..5a3ad409a83e3 --- /dev/null +++ b/validator/val_anchor.h @@ -0,0 +1,218 @@ +/* + * validator/val_anchor.h - validator trust anchor storage. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains storage for the trust anchors for the validator. + */ + +#ifndef VALIDATOR_VAL_ANCHOR_H +#define VALIDATOR_VAL_ANCHOR_H +#include "util/rbtree.h" +#include "util/locks.h" +struct trust_anchor; +struct config_file; +struct ub_packed_rrset_key; +struct autr_point_data; +struct autr_global_data; + +/** + * Trust anchor store. + * The tree must be locked, while no other locks (from trustanchors) are held. + * And then an anchor searched for. Which can be locked or deleted. Then + * the tree can be unlocked again. This means you have to release the lock + * on a trust anchor and look it up again to delete it. + */ +struct val_anchors { + /** lock on trees */ + lock_basic_t lock; + /** + * Anchors are store in this tree. Sort order is chosen, so that + * dnames are in nsec-like order. A lookup on class, name will return + * an exact match of the closest match, with the ancestor needed. + * contents of type trust_anchor. + */ + rbtree_t* tree; + /** The DLV trust anchor (if one is configured, else NULL) */ + struct trust_anchor* dlv_anchor; + /** Autotrust global data, anchors sorted by next probe time */ + struct autr_global_data* autr; +}; + +/** + * Trust anchor key + */ +struct ta_key { + /** next in list */ + struct ta_key* next; + /** rdata, in wireformat of the key RR. starts with rdlength. */ + uint8_t* data; + /** length of the rdata (including rdlength). */ + size_t len; + /** DNS type (host format) of the key, DS or DNSKEY */ + uint16_t type; +}; + +/** + * A trust anchor in the trust anchor store. + * Unique by name, class. + */ +struct trust_anchor { + /** rbtree node, key is this structure */ + rbnode_t node; + /** lock on the entire anchor and its keys; for autotrust changes */ + lock_basic_t lock; + /** name of this trust anchor */ + uint8_t* name; + /** length of name */ + size_t namelen; + /** number of labels in name of rrset */ + int namelabs; + /** the ancestor in the trustanchor tree */ + struct trust_anchor* parent; + /** + * List of DS or DNSKEY rrs that form the trust anchor. + */ + struct ta_key* keylist; + /** Autotrust anchor point data, or NULL */ + struct autr_point_data* autr; + /** number of DSs in the keylist */ + size_t numDS; + /** number of DNSKEYs in the keylist */ + size_t numDNSKEY; + /** the DS RRset */ + struct ub_packed_rrset_key* ds_rrset; + /** The DNSKEY RRset */ + struct ub_packed_rrset_key* dnskey_rrset; + /** class of the trust anchor */ + uint16_t dclass; +}; + +/** + * Create trust anchor storage + * @return new storage or NULL on error. + */ +struct val_anchors* anchors_create(void); + +/** + * Delete trust anchor storage. + * @param anchors: to delete. + */ +void anchors_delete(struct val_anchors* anchors); + +/** + * Process trust anchor config. + * @param anchors: struct anchor storage + * @param cfg: config options. + * @return 0 on error. + */ +int anchors_apply_cfg(struct val_anchors* anchors, struct config_file* cfg); + +/** + * Recalculate parent pointers. The caller must hold the lock on the + * anchors structure (say after removing an item from the rbtree). + * Caller must not hold any locks on trust anchors. + * After the call is complete the parent pointers are updated and an item + * just removed is no longer referenced in parent pointers. + * @param anchors: the structure to update. + */ +void anchors_init_parents_locked(struct val_anchors* anchors); + +/** + * Given a qname/qclass combination, find the trust anchor closest above it. + * Or return NULL if none exists. + * + * @param anchors: struct anchor storage + * @param qname: query name, uncompressed wireformat. + * @param qname_len: length of qname. + * @param qclass: class to query for. + * @return the trust anchor or NULL if none is found. The anchor is locked. + */ +struct trust_anchor* anchors_lookup(struct val_anchors* anchors, + uint8_t* qname, size_t qname_len, uint16_t qclass); + +/** + * Find a trust anchor. Exact matching. + * @param anchors: anchor storage. + * @param name: name of trust anchor (wireformat) + * @param namelabs: labels in name + * @param namelen: length of name + * @param dclass: class of trust anchor + * @return NULL if not found. The anchor is locked. + */ +struct trust_anchor* anchor_find(struct val_anchors* anchors, + uint8_t* name, int namelabs, size_t namelen, uint16_t dclass); + +/** + * Store one string as trust anchor RR. + * @param anchors: anchor storage. + * @param buffer: parsing buffer, to generate the RR wireformat in. + * @param str: string. + * @return NULL on error. + */ +struct trust_anchor* anchor_store_str(struct val_anchors* anchors, + ldns_buffer* buffer, const char* str); + +/** + * Get memory in use by the trust anchor storage + * @param anchors: anchor storage. + * @return memory in use in bytes. + */ +size_t anchors_get_mem(struct val_anchors* anchors); + +/** compare two trust anchors */ +int anchor_cmp(const void* k1, const void* k2); + +/** + * Add insecure point trust anchor. For external use (locks and init_parents) + * @param anchors: anchor storage. + * @param c: class. + * @param nm: name of insecure trust point. + * @return false on alloc failure. + */ +int anchors_add_insecure(struct val_anchors* anchors, uint16_t c, uint8_t* nm); + +/** + * Delete insecure point trust anchor. Does not remove if no such point. + * For external use (locks and init_parents) + * @param anchors: anchor storage. + * @param c: class. + * @param nm: name of insecure trust point. + */ +void anchors_delete_insecure(struct val_anchors* anchors, uint16_t c, + uint8_t* nm); + +#endif /* VALIDATOR_VAL_ANCHOR_H */ diff --git a/validator/val_kcache.c b/validator/val_kcache.c new file mode 100644 index 0000000000000..68e8c3f619b66 --- /dev/null +++ b/validator/val_kcache.c @@ -0,0 +1,172 @@ +/* + * validator/val_kcache.c - validator key shared cache with validated keys + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions for dealing with the validator key cache. + */ +#include "config.h" +#include "validator/val_kcache.h" +#include "validator/val_kentry.h" +#include "util/log.h" +#include "util/config_file.h" +#include "util/data/dname.h" +#include "util/module.h" + +struct key_cache* +key_cache_create(struct config_file* cfg) +{ + struct key_cache* kcache = (struct key_cache*)calloc(1, + sizeof(*kcache)); + size_t numtables, start_size, maxmem; + if(!kcache) { + log_err("malloc failure"); + return NULL; + } + numtables = cfg->key_cache_slabs; + start_size = HASH_DEFAULT_STARTARRAY; + maxmem = cfg->key_cache_size; + kcache->slab = slabhash_create(numtables, start_size, maxmem, + &key_entry_sizefunc, &key_entry_compfunc, + &key_entry_delkeyfunc, &key_entry_deldatafunc, NULL); + if(!kcache->slab) { + log_err("malloc failure"); + free(kcache); + return NULL; + } + return kcache; +} + +void +key_cache_delete(struct key_cache* kcache) +{ + if(!kcache) + return; + slabhash_delete(kcache->slab); + free(kcache); +} + +void +key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey, + struct module_qstate* qstate) +{ + struct key_entry_key* k = key_entry_copy(kkey); + if(!k) + return; + if(key_entry_isbad(k) && qstate->errinf && + qstate->env->cfg->val_log_level >= 2) { + /* on malloc failure there is simply no reason string */ + key_entry_set_reason(k, errinf_to_str(qstate)); + } + key_entry_hash(k); + slabhash_insert(kcache->slab, k->entry.hash, &k->entry, + k->entry.data, NULL); +} + +/** + * Lookup exactly in the key cache. Returns pointer to locked entry. + * Caller must unlock it after use. + * @param kcache: the key cache. + * @param name: for what name to look; uncompressed wireformat + * @param namelen: length of the name. + * @param key_class: class of the key. + * @param wr: set true to get a writelock. + * @return key entry, locked, or NULL if not found. No TTL checking is + * performed. + */ +static struct key_entry_key* +key_cache_search(struct key_cache* kcache, uint8_t* name, size_t namelen, + uint16_t key_class, int wr) +{ + struct lruhash_entry* e; + struct key_entry_key lookfor; + lookfor.entry.key = &lookfor; + lookfor.name = name; + lookfor.namelen = namelen; + lookfor.key_class = key_class; + key_entry_hash(&lookfor); + e = slabhash_lookup(kcache->slab, lookfor.entry.hash, &lookfor, wr); + if(!e) + return NULL; + return (struct key_entry_key*)e->key; +} + +struct key_entry_key* +key_cache_obtain(struct key_cache* kcache, uint8_t* name, size_t namelen, + uint16_t key_class, struct regional* region, uint32_t now) +{ + /* keep looking until we find a nonexpired entry */ + while(1) { + struct key_entry_key* k = key_cache_search(kcache, name, + namelen, key_class, 0); + if(k) { + /* see if TTL is OK */ + struct key_entry_data* d = (struct key_entry_data*) + k->entry.data; + if(now <= d->ttl) { + /* copy and return it */ + struct key_entry_key* retkey = + key_entry_copy_toregion(k, region); + lock_rw_unlock(&k->entry.lock); + return retkey; + } + lock_rw_unlock(&k->entry.lock); + } + /* snip off first label to continue */ + if(dname_is_root(name)) + break; + dname_remove_label(&name, &namelen); + } + return NULL; +} + +size_t +key_cache_get_mem(struct key_cache* kcache) +{ + return sizeof(*kcache) + slabhash_get_mem(kcache->slab); +} + +void key_cache_remove(struct key_cache* kcache, + uint8_t* name, size_t namelen, uint16_t key_class) +{ + struct key_entry_key lookfor; + lookfor.entry.key = &lookfor; + lookfor.name = name; + lookfor.namelen = namelen; + lookfor.key_class = key_class; + key_entry_hash(&lookfor); + slabhash_remove(kcache->slab, lookfor.entry.hash, &lookfor); +} diff --git a/validator/val_kcache.h b/validator/val_kcache.h new file mode 100644 index 0000000000000..c37cf1ecbafc5 --- /dev/null +++ b/validator/val_kcache.h @@ -0,0 +1,118 @@ +/* + * validator/val_kcache.h - validator key shared cache with validated keys + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions for caching validated key entries. + */ + +#ifndef VALIDATOR_VAL_KCACHE_H +#define VALIDATOR_VAL_KCACHE_H +#include "util/storage/slabhash.h" +struct key_entry_key; +struct key_entry_data; +struct config_file; +struct regional; +struct module_qstate; + +/** + * Key cache + */ +struct key_cache { + /** uses slabhash for storage, type key_entry_key, key_entry_data */ + struct slabhash* slab; +}; + +/** + * Create the key cache + * @param cfg: config settings for the key cache. + * @return new key cache or NULL on malloc failure. + */ +struct key_cache* key_cache_create(struct config_file* cfg); + +/** + * Delete the key cache + * @param kcache: to delete + */ +void key_cache_delete(struct key_cache* kcache); + +/** + * Insert or update a key cache entry. Note that the insert may silently + * fail if there is not enough memory. + * + * @param kcache: the key cache. + * @param kkey: key entry key, assumed malloced in a region, is copied + * to perform update or insertion. Its data pointer is also copied. + * @param qstate: store errinf reason in case its bad. + */ +void key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey, + struct module_qstate* qstate); + +/** + * Remove an entry from the key cache. + * @param kcache: the key cache. + * @param name: for what name to look; uncompressed wireformat + * @param namelen: length of the name. + * @param key_class: class of the key. + */ +void key_cache_remove(struct key_cache* kcache, + uint8_t* name, size_t namelen, uint16_t key_class); + +/** + * Lookup key entry in the cache. Looks up the closest key entry above the + * given name. + * @param kcache: the key cache. + * @param name: for what name to look; uncompressed wireformat + * @param namelen: length of the name. + * @param key_class: class of the key. + * @param region: a copy of the key_entry is allocated in this region. + * @param now: current time. + * @return pointer to a newly allocated key_entry copy in the region, if + * a key entry could be found, and allocation succeeded and TTL was OK. + * Otherwise, NULL is returned. + */ +struct key_entry_key* key_cache_obtain(struct key_cache* kcache, + uint8_t* name, size_t namelen, uint16_t key_class, + struct regional* region, uint32_t now); + +/** + * Get memory in use by the key cache. + * @param kcache: the key cache. + * @return memory in use in bytes. + */ +size_t key_cache_get_mem(struct key_cache* kcache); + +#endif /* VALIDATOR_VAL_KCACHE_H */ diff --git a/validator/val_kentry.c b/validator/val_kentry.c new file mode 100644 index 0000000000000..ddac140d316f0 --- /dev/null +++ b/validator/val_kentry.c @@ -0,0 +1,412 @@ +/* + * validator/val_kentry.c - validator key entry definition. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions for dealing with validator key entries. + */ +#include "config.h" +#include <ldns/ldns.h> +#include "validator/val_kentry.h" +#include "util/data/packed_rrset.h" +#include "util/data/dname.h" +#include "util/storage/lookup3.h" +#include "util/regional.h" +#include "util/net_help.h" + +size_t +key_entry_sizefunc(void* key, void* data) +{ + struct key_entry_key* kk = (struct key_entry_key*)key; + struct key_entry_data* kd = (struct key_entry_data*)data; + size_t s = sizeof(*kk) + kk->namelen; + s += sizeof(*kd) + lock_get_mem(&kk->entry.lock); + if(kd->rrset_data) + s += packed_rrset_sizeof(kd->rrset_data); + if(kd->reason) + s += strlen(kd->reason)+1; + if(kd->algo) + s += strlen((char*)kd->algo)+1; + return s; +} + +int +key_entry_compfunc(void* k1, void* k2) +{ + struct key_entry_key* n1 = (struct key_entry_key*)k1; + struct key_entry_key* n2 = (struct key_entry_key*)k2; + if(n1->key_class != n2->key_class) { + if(n1->key_class < n2->key_class) + return -1; + return 1; + } + return query_dname_compare(n1->name, n2->name); +} + +void +key_entry_delkeyfunc(void* key, void* ATTR_UNUSED(userarg)) +{ + struct key_entry_key* kk = (struct key_entry_key*)key; + if(!key) + return; + lock_rw_destroy(&kk->entry.lock); + free(kk->name); + free(kk); +} + +void +key_entry_deldatafunc(void* data, void* ATTR_UNUSED(userarg)) +{ + struct key_entry_data* kd = (struct key_entry_data*)data; + free(kd->reason); + free(kd->rrset_data); + free(kd->algo); + free(kd); +} + +void +key_entry_hash(struct key_entry_key* kk) +{ + kk->entry.hash = 0x654; + kk->entry.hash = hashlittle(&kk->key_class, sizeof(kk->key_class), + kk->entry.hash); + kk->entry.hash = dname_query_hash(kk->name, kk->entry.hash); +} + +struct key_entry_key* +key_entry_copy_toregion(struct key_entry_key* kkey, struct regional* region) +{ + struct key_entry_key* newk; + newk = regional_alloc_init(region, kkey, sizeof(*kkey)); + if(!newk) + return NULL; + newk->name = regional_alloc_init(region, kkey->name, kkey->namelen); + if(!newk->name) + return NULL; + newk->entry.key = newk; + if(newk->entry.data) { + /* copy data element */ + struct key_entry_data *d = (struct key_entry_data*) + kkey->entry.data; + struct key_entry_data *newd; + newd = regional_alloc_init(region, d, sizeof(*d)); + if(!newd) + return NULL; + /* copy rrset */ + if(d->rrset_data) { + newd->rrset_data = regional_alloc_init(region, + d->rrset_data, + packed_rrset_sizeof(d->rrset_data)); + if(!newd->rrset_data) + return NULL; + packed_rrset_ptr_fixup(newd->rrset_data); + } + if(d->reason) { + newd->reason = regional_strdup(region, d->reason); + if(!newd->reason) + return NULL; + } + if(d->algo) { + newd->algo = (uint8_t*)regional_strdup(region, + (char*)d->algo); + if(!newd->algo) + return NULL; + } + newk->entry.data = newd; + } + return newk; +} + +struct key_entry_key* +key_entry_copy(struct key_entry_key* kkey) +{ + struct key_entry_key* newk; + if(!kkey) + return NULL; + newk = memdup(kkey, sizeof(*kkey)); + if(!newk) + return NULL; + newk->name = memdup(kkey->name, kkey->namelen); + if(!newk->name) { + free(newk); + return NULL; + } + lock_rw_init(&newk->entry.lock); + newk->entry.key = newk; + if(newk->entry.data) { + /* copy data element */ + struct key_entry_data *d = (struct key_entry_data*) + kkey->entry.data; + struct key_entry_data *newd; + newd = memdup(d, sizeof(*d)); + if(!newd) { + free(newk->name); + free(newk); + return NULL; + } + /* copy rrset */ + if(d->rrset_data) { + newd->rrset_data = memdup(d->rrset_data, + packed_rrset_sizeof(d->rrset_data)); + if(!newd->rrset_data) { + free(newd); + free(newk->name); + free(newk); + return NULL; + } + packed_rrset_ptr_fixup(newd->rrset_data); + } + if(d->reason) { + newd->reason = strdup(d->reason); + if(!newd->reason) { + free(newd->rrset_data); + free(newd); + free(newk->name); + free(newk); + return NULL; + } + } + if(d->algo) { + newd->algo = (uint8_t*)strdup((char*)d->algo); + if(!newd->algo) { + free(newd->rrset_data); + free(newd->reason); + free(newd); + free(newk->name); + free(newk); + return NULL; + } + } + newk->entry.data = newd; + } + return newk; +} + +int +key_entry_isnull(struct key_entry_key* kkey) +{ + struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; + return (!d->isbad && d->rrset_data == NULL); +} + +int +key_entry_isgood(struct key_entry_key* kkey) +{ + struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; + return (!d->isbad && d->rrset_data != NULL); +} + +int +key_entry_isbad(struct key_entry_key* kkey) +{ + struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; + return (int)(d->isbad); +} + +void +key_entry_set_reason(struct key_entry_key* kkey, char* reason) +{ + struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; + d->reason = reason; +} + +char* +key_entry_get_reason(struct key_entry_key* kkey) +{ + struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; + return d->reason; +} + +/** setup key entry in region */ +static int +key_entry_setup(struct regional* region, + uint8_t* name, size_t namelen, uint16_t dclass, + struct key_entry_key** k, struct key_entry_data** d) +{ + *k = regional_alloc(region, sizeof(**k)); + if(!*k) + return 0; + memset(*k, 0, sizeof(**k)); + (*k)->entry.key = *k; + (*k)->name = regional_alloc_init(region, name, namelen); + if(!(*k)->name) + return 0; + (*k)->namelen = namelen; + (*k)->key_class = dclass; + *d = regional_alloc(region, sizeof(**d)); + if(!*d) + return 0; + (*k)->entry.data = *d; + return 1; +} + +struct key_entry_key* +key_entry_create_null(struct regional* region, + uint8_t* name, size_t namelen, uint16_t dclass, uint32_t ttl, + uint32_t now) +{ + struct key_entry_key* k; + struct key_entry_data* d; + if(!key_entry_setup(region, name, namelen, dclass, &k, &d)) + return NULL; + d->ttl = now + ttl; + d->isbad = 0; + d->reason = NULL; + d->rrset_type = LDNS_RR_TYPE_DNSKEY; + d->rrset_data = NULL; + d->algo = NULL; + return k; +} + +struct key_entry_key* +key_entry_create_rrset(struct regional* region, + uint8_t* name, size_t namelen, uint16_t dclass, + struct ub_packed_rrset_key* rrset, uint8_t* sigalg, uint32_t now) +{ + struct key_entry_key* k; + struct key_entry_data* d; + struct packed_rrset_data* rd = (struct packed_rrset_data*) + rrset->entry.data; + if(!key_entry_setup(region, name, namelen, dclass, &k, &d)) + return NULL; + d->ttl = rd->ttl + now; + d->isbad = 0; + d->reason = NULL; + d->rrset_type = ntohs(rrset->rk.type); + d->rrset_data = (struct packed_rrset_data*)regional_alloc_init(region, + rd, packed_rrset_sizeof(rd)); + if(!d->rrset_data) + return NULL; + if(sigalg) { + d->algo = (uint8_t*)regional_strdup(region, (char*)sigalg); + if(!d->algo) + return NULL; + } else d->algo = NULL; + packed_rrset_ptr_fixup(d->rrset_data); + return k; +} + +struct key_entry_key* +key_entry_create_bad(struct regional* region, + uint8_t* name, size_t namelen, uint16_t dclass, uint32_t ttl, + uint32_t now) +{ + struct key_entry_key* k; + struct key_entry_data* d; + if(!key_entry_setup(region, name, namelen, dclass, &k, &d)) + return NULL; + d->ttl = now + ttl; + d->isbad = 1; + d->reason = NULL; + d->rrset_type = LDNS_RR_TYPE_DNSKEY; + d->rrset_data = NULL; + d->algo = NULL; + return k; +} + +struct ub_packed_rrset_key* +key_entry_get_rrset(struct key_entry_key* kkey, struct regional* region) +{ + struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; + struct ub_packed_rrset_key* rrk; + struct packed_rrset_data* rrd; + if(!d || !d->rrset_data) + return NULL; + rrk = regional_alloc(region, sizeof(*rrk)); + if(!rrk) + return NULL; + memset(rrk, 0, sizeof(*rrk)); + rrk->rk.dname = regional_alloc_init(region, kkey->name, kkey->namelen); + if(!rrk->rk.dname) + return NULL; + rrk->rk.dname_len = kkey->namelen; + rrk->rk.type = htons(d->rrset_type); + rrk->rk.rrset_class = htons(kkey->key_class); + rrk->entry.key = rrk; + rrd = regional_alloc_init(region, d->rrset_data, + packed_rrset_sizeof(d->rrset_data)); + if(!rrd) + return NULL; + rrk->entry.data = rrd; + packed_rrset_ptr_fixup(rrd); + return rrk; +} + +/** Get size of key in keyset */ +static size_t +dnskey_get_keysize(struct packed_rrset_data* data, size_t idx) +{ + unsigned char* pk; + unsigned int pklen = 0; + int algo; + if(data->rr_len[idx] < 2+5) + return 0; + algo = (int)data->rr_data[idx][2+3]; + pk = (unsigned char*)data->rr_data[idx]+2+4; + pklen = (unsigned)data->rr_len[idx]-2-4; + return ldns_rr_dnskey_key_size_raw(pk, pklen, algo); +} + +/** get dnskey flags from data */ +static uint16_t +kd_get_flags(struct packed_rrset_data* data, size_t idx) +{ + uint16_t f; + if(data->rr_len[idx] < 2+2) + return 0; + memmove(&f, data->rr_data[idx]+2, 2); + f = ntohs(f); + return f; +} + +size_t +key_entry_keysize(struct key_entry_key* kkey) +{ + struct packed_rrset_data* d; + /* compute size of smallest ZSK key in the rrset */ + size_t i; + size_t bits = 0; + if(!key_entry_isgood(kkey)) + return 0; + d = ((struct key_entry_data*)kkey->entry.data)->rrset_data; + for(i=0; i<d->count; i++) { + if(!(kd_get_flags(d, i) & DNSKEY_BIT_ZSK)) + continue; + if(i==0 || dnskey_get_keysize(d, i) < bits) + bits = dnskey_get_keysize(d, i); + } + return bits; +} diff --git a/validator/val_kentry.h b/validator/val_kentry.h new file mode 100644 index 0000000000000..d14ffe588016e --- /dev/null +++ b/validator/val_kentry.h @@ -0,0 +1,220 @@ +/* + * validator/val_kentry.h - validator key entry definition. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions for dealing with validator key entries. + */ + +#ifndef VALIDATOR_VAL_KENTRY_H +#define VALIDATOR_VAL_KENTRY_H +struct packed_rrset_data; +struct regional; +struct ub_packed_rrset_key; +#include "util/storage/lruhash.h" + +/** + * A key entry for the validator. + * This may or may not be a trusted key. + * This is what is stored in the key cache. + * This is the key part for the cache; the key entry key. + */ +struct key_entry_key { + /** lru hash entry */ + struct lruhash_entry entry; + /** name of the key */ + uint8_t* name; + /** length of name */ + size_t namelen; + /** class of the key, host byteorder */ + uint16_t key_class; +}; + +/** + * Key entry for the validator. + * Contains key status. + * This is the data part for the cache, the key entry data. + * + * Can be in three basic states: + * isbad=0: good key + * isbad=1: bad key + * isbad=0 && rrset=0: insecure space. + */ +struct key_entry_data { + /** the TTL of this entry (absolute time) */ + uint32_t ttl; + /** the key rrdata. can be NULL to signal keyless name. */ + struct packed_rrset_data* rrset_data; + /** not NULL sometimes to give reason why bogus */ + char* reason; + /** list of algorithms signalled, ends with 0, or NULL */ + uint8_t* algo; + /** DNS RR type of the rrset data (host order) */ + uint16_t rrset_type; + /** if the key is bad: Bogus or malformed */ + uint8_t isbad; +}; + +/** function for lruhash operation */ +size_t key_entry_sizefunc(void* key, void* data); + +/** function for lruhash operation */ +int key_entry_compfunc(void* k1, void* k2); + +/** function for lruhash operation */ +void key_entry_delkeyfunc(void* key, void* userarg); + +/** function for lruhash operation */ +void key_entry_deldatafunc(void* data, void* userarg); + +/** calculate hash for key entry + * @param kk: key entry. The lruhash entry.hash value is filled in. + */ +void key_entry_hash(struct key_entry_key* kk); + +/** + * Copy a key entry, to be region-allocated. + * @param kkey: the key entry key (and data pointer) to copy. + * @param region: where to allocate it + * @return newly region-allocated entry or NULL on a failure to allocate. + */ +struct key_entry_key* key_entry_copy_toregion(struct key_entry_key* kkey, + struct regional* region); + +/** + * Copy a key entry, malloced. + * @param kkey: the key entry key (and data pointer) to copy. + * @return newly allocated entry or NULL on a failure to allocate memory. + */ +struct key_entry_key* key_entry_copy(struct key_entry_key* kkey); + +/** + * See if this is a null entry. Does not do locking. + * @param kkey: must have data pointer set correctly + * @return true if it is a NULL rrset entry. + */ +int key_entry_isnull(struct key_entry_key* kkey); + +/** + * See if this entry is good. Does not do locking. + * @param kkey: must have data pointer set correctly + * @return true if it is good. + */ +int key_entry_isgood(struct key_entry_key* kkey); + +/** + * See if this entry is bad. Does not do locking. + * @param kkey: must have data pointer set correctly + * @return true if it is bad. + */ +int key_entry_isbad(struct key_entry_key* kkey); + +/** + * Set reason why a key is bad. + * @param kkey: bad key. + * @param reason: string to attach, you must allocate it. + * Not safe to call twice unless you deallocate it yourself. + */ +void key_entry_set_reason(struct key_entry_key* kkey, char* reason); + +/** + * Get reason why a key is bad. + * @param kkey: bad key + * @return pointer to string. + * String is part of key entry and is deleted with it. + */ +char* key_entry_get_reason(struct key_entry_key* kkey); + +/** + * Create a null entry, in the given region. + * @param region: where to allocate + * @param name: the key name + * @param namelen: length of name + * @param dclass: class of key entry. (host order); + * @param ttl: what ttl should the key have. relative. + * @param now: current time (added to ttl). + * @return new key entry or NULL on alloc failure + */ +struct key_entry_key* key_entry_create_null(struct regional* region, + uint8_t* name, size_t namelen, uint16_t dclass, uint32_t ttl, + uint32_t now); + +/** + * Create a key entry from an rrset, in the given region. + * @param region: where to allocate. + * @param name: the key name + * @param namelen: length of name + * @param dclass: class of key entry. (host order); + * @param rrset: data for key entry. This is copied to the region. + * @param sigalg: signalled algorithm list (or NULL). + * @param now: current time (added to ttl of rrset) + * @return new key entry or NULL on alloc failure + */ +struct key_entry_key* key_entry_create_rrset(struct regional* region, + uint8_t* name, size_t namelen, uint16_t dclass, + struct ub_packed_rrset_key* rrset, uint8_t* sigalg, uint32_t now); + +/** + * Create a bad entry, in the given region. + * @param region: where to allocate + * @param name: the key name + * @param namelen: length of name + * @param dclass: class of key entry. (host order); + * @param ttl: what ttl should the key have. relative. + * @param now: current time (added to ttl). + * @return new key entry or NULL on alloc failure + */ +struct key_entry_key* key_entry_create_bad(struct regional* region, + uint8_t* name, size_t namelen, uint16_t dclass, uint32_t ttl, + uint32_t now); + +/** + * Obtain rrset from a key entry, allocated in region. + * @param kkey: key entry to convert to a rrset. + * @param region: where to allocate rrset + * @return rrset copy; if no rrset or alloc error returns NULL. + */ +struct ub_packed_rrset_key* key_entry_get_rrset(struct key_entry_key* kkey, + struct regional* region); + +/** + * Get keysize of the keyentry. + * @param kkey: key, must be a good key, with contents. + * @return size in bits of the key. + */ +size_t key_entry_keysize(struct key_entry_key* kkey); + +#endif /* VALIDATOR_VAL_KENTRY_H */ diff --git a/validator/val_neg.c b/validator/val_neg.c new file mode 100644 index 0000000000000..60434db033858 --- /dev/null +++ b/validator/val_neg.c @@ -0,0 +1,1455 @@ +/* + * validator/val_neg.c - validator aggressive negative caching functions. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with aggressive negative caching. + * This creates new denials of existance, and proofs for absence of types + * from cached NSEC records. + */ +#include "config.h" +#ifdef HAVE_OPENSSL_SSL_H +#include "openssl/ssl.h" +#endif +#include "validator/val_neg.h" +#include "validator/val_nsec.h" +#include "validator/val_nsec3.h" +#include "validator/val_utils.h" +#include "util/data/dname.h" +#include "util/data/msgreply.h" +#include "util/log.h" +#include "util/net_help.h" +#include "util/config_file.h" +#include "services/cache/rrset.h" +#include "services/cache/dns.h" + +int val_neg_data_compare(const void* a, const void* b) +{ + struct val_neg_data* x = (struct val_neg_data*)a; + struct val_neg_data* y = (struct val_neg_data*)b; + int m; + return dname_canon_lab_cmp(x->name, x->labs, y->name, y->labs, &m); +} + +int val_neg_zone_compare(const void* a, const void* b) +{ + struct val_neg_zone* x = (struct val_neg_zone*)a; + struct val_neg_zone* y = (struct val_neg_zone*)b; + int m; + if(x->dclass != y->dclass) { + if(x->dclass < y->dclass) + return -1; + return 1; + } + return dname_canon_lab_cmp(x->name, x->labs, y->name, y->labs, &m); +} + +struct val_neg_cache* val_neg_create(struct config_file* cfg, size_t maxiter) +{ + struct val_neg_cache* neg = (struct val_neg_cache*)calloc(1, + sizeof(*neg)); + if(!neg) { + log_err("Could not create neg cache: out of memory"); + return NULL; + } + neg->nsec3_max_iter = maxiter; + neg->max = 1024*1024; /* 1 M is thousands of entries */ + if(cfg) neg->max = cfg->neg_cache_size; + rbtree_init(&neg->tree, &val_neg_zone_compare); + lock_basic_init(&neg->lock); + lock_protect(&neg->lock, neg, sizeof(*neg)); + return neg; +} + +size_t val_neg_get_mem(struct val_neg_cache* neg) +{ + size_t result; + lock_basic_lock(&neg->lock); + result = sizeof(*neg) + neg->use; + lock_basic_unlock(&neg->lock); + return result; +} + +/** clear datas on cache deletion */ +static void +neg_clear_datas(rbnode_t* n, void* ATTR_UNUSED(arg)) +{ + struct val_neg_data* d = (struct val_neg_data*)n; + free(d->name); + free(d); +} + +/** clear zones on cache deletion */ +static void +neg_clear_zones(rbnode_t* n, void* ATTR_UNUSED(arg)) +{ + struct val_neg_zone* z = (struct val_neg_zone*)n; + /* delete all the rrset entries in the tree */ + traverse_postorder(&z->tree, &neg_clear_datas, NULL); + free(z->nsec3_salt); + free(z->name); + free(z); +} + +void neg_cache_delete(struct val_neg_cache* neg) +{ + if(!neg) return; + lock_basic_destroy(&neg->lock); + /* delete all the zones in the tree */ + traverse_postorder(&neg->tree, &neg_clear_zones, NULL); + free(neg); +} + +/** + * Put data element at the front of the LRU list. + * @param neg: negative cache with LRU start and end. + * @param data: this data is fronted. + */ +static void neg_lru_front(struct val_neg_cache* neg, + struct val_neg_data* data) +{ + data->prev = NULL; + data->next = neg->first; + if(!neg->first) + neg->last = data; + else neg->first->prev = data; + neg->first = data; +} + +/** + * Remove data element from LRU list. + * @param neg: negative cache with LRU start and end. + * @param data: this data is removed from the list. + */ +static void neg_lru_remove(struct val_neg_cache* neg, + struct val_neg_data* data) +{ + if(data->prev) + data->prev->next = data->next; + else neg->first = data->next; + if(data->next) + data->next->prev = data->prev; + else neg->last = data->prev; +} + +/** + * Touch LRU for data element, put it at the start of the LRU list. + * @param neg: negative cache with LRU start and end. + * @param data: this data is used. + */ +static void neg_lru_touch(struct val_neg_cache* neg, + struct val_neg_data* data) +{ + if(data == neg->first) + return; /* nothing to do */ + /* remove from current lru position */ + neg_lru_remove(neg, data); + /* add at front */ + neg_lru_front(neg, data); +} + +/** + * Delete a zone element from the negative cache. + * May delete other zone elements to keep tree coherent, or + * only mark the element as 'not in use'. + * @param neg: negative cache. + * @param z: zone element to delete. + */ +static void neg_delete_zone(struct val_neg_cache* neg, struct val_neg_zone* z) +{ + struct val_neg_zone* p, *np; + if(!z) return; + log_assert(z->in_use); + log_assert(z->count > 0); + z->in_use = 0; + + /* go up the tree and reduce counts */ + p = z; + while(p) { + log_assert(p->count > 0); + p->count --; + p = p->parent; + } + + /* remove zones with zero count */ + p = z; + while(p && p->count == 0) { + np = p->parent; + (void)rbtree_delete(&neg->tree, &p->node); + neg->use -= p->len + sizeof(*p); + free(p->nsec3_salt); + free(p->name); + free(p); + p = np; + } +} + +void neg_delete_data(struct val_neg_cache* neg, struct val_neg_data* el) +{ + struct val_neg_zone* z; + struct val_neg_data* p, *np; + if(!el) return; + z = el->zone; + log_assert(el->in_use); + log_assert(el->count > 0); + el->in_use = 0; + + /* remove it from the lru list */ + neg_lru_remove(neg, el); + + /* go up the tree and reduce counts */ + p = el; + while(p) { + log_assert(p->count > 0); + p->count --; + p = p->parent; + } + + /* delete 0 count items from tree */ + p = el; + while(p && p->count == 0) { + np = p->parent; + (void)rbtree_delete(&z->tree, &p->node); + neg->use -= p->len + sizeof(*p); + free(p->name); + free(p); + p = np; + } + + /* check if the zone is now unused */ + if(z->tree.count == 0) { + neg_delete_zone(neg, z); + } +} + +/** + * Create more space in negative cache + * The oldest elements are deleted until enough space is present. + * Empty zones are deleted. + * @param neg: negative cache. + * @param need: how many bytes are needed. + */ +static void neg_make_space(struct val_neg_cache* neg, size_t need) +{ + /* delete elements until enough space or its empty */ + while(neg->last && neg->max < neg->use + need) { + neg_delete_data(neg, neg->last); + } +} + +struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg, + uint8_t* nm, size_t len, uint16_t dclass) +{ + struct val_neg_zone lookfor; + struct val_neg_zone* result; + lookfor.node.key = &lookfor; + lookfor.name = nm; + lookfor.len = len; + lookfor.labs = dname_count_labels(lookfor.name); + lookfor.dclass = dclass; + + result = (struct val_neg_zone*) + rbtree_search(&neg->tree, lookfor.node.key); + return result; +} + +/** + * Find the given data + * @param zone: negative zone + * @param nm: what to look for. + * @param len: length of nm + * @param labs: labels in nm + * @return data or NULL if not found. + */ +static struct val_neg_data* neg_find_data(struct val_neg_zone* zone, + uint8_t* nm, size_t len, int labs) +{ + struct val_neg_data lookfor; + struct val_neg_data* result; + lookfor.node.key = &lookfor; + lookfor.name = nm; + lookfor.len = len; + lookfor.labs = labs; + + result = (struct val_neg_data*) + rbtree_search(&zone->tree, lookfor.node.key); + return result; +} + +/** + * Calculate space needed for the data and all its parents + * @param rep: NSEC entries. + * @return size. + */ +static size_t calc_data_need(struct reply_info* rep) +{ + uint8_t* d; + size_t i, len, res = 0; + + for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) { + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) { + d = rep->rrsets[i]->rk.dname; + len = rep->rrsets[i]->rk.dname_len; + res = sizeof(struct val_neg_data) + len; + while(!dname_is_root(d)) { + log_assert(len > 1); /* not root label */ + dname_remove_label(&d, &len); + res += sizeof(struct val_neg_data) + len; + } + } + } + return res; +} + +/** + * Calculate space needed for zone and all its parents + * @param d: name of zone + * @param len: length of name + * @return size. + */ +static size_t calc_zone_need(uint8_t* d, size_t len) +{ + size_t res = sizeof(struct val_neg_zone) + len; + while(!dname_is_root(d)) { + log_assert(len > 1); /* not root label */ + dname_remove_label(&d, &len); + res += sizeof(struct val_neg_zone) + len; + } + return res; +} + +/** + * Find closest existing parent zone of the given name. + * @param neg: negative cache. + * @param nm: name to look for + * @param nm_len: length of nm + * @param labs: labelcount of nm. + * @param qclass: class. + * @return the zone or NULL if none found. + */ +static struct val_neg_zone* neg_closest_zone_parent(struct val_neg_cache* neg, + uint8_t* nm, size_t nm_len, int labs, uint16_t qclass) +{ + struct val_neg_zone key; + struct val_neg_zone* result; + rbnode_t* res = NULL; + key.node.key = &key; + key.name = nm; + key.len = nm_len; + key.labs = labs; + key.dclass = qclass; + if(rbtree_find_less_equal(&neg->tree, &key, &res)) { + /* exact match */ + result = (struct val_neg_zone*)res; + } else { + /* smaller element (or no element) */ + int m; + result = (struct val_neg_zone*)res; + if(!result || result->dclass != qclass) + return NULL; + /* count number of labels matched */ + (void)dname_lab_cmp(result->name, result->labs, key.name, + key.labs, &m); + while(result) { /* go up until qname is subdomain of stub */ + if(result->labs <= m) + break; + result = result->parent; + } + } + return result; +} + +/** + * Find closest existing parent data for the given name. + * @param zone: to look in. + * @param nm: name to look for + * @param nm_len: length of nm + * @param labs: labelcount of nm. + * @return the data or NULL if none found. + */ +static struct val_neg_data* neg_closest_data_parent( + struct val_neg_zone* zone, uint8_t* nm, size_t nm_len, int labs) +{ + struct val_neg_data key; + struct val_neg_data* result; + rbnode_t* res = NULL; + key.node.key = &key; + key.name = nm; + key.len = nm_len; + key.labs = labs; + if(rbtree_find_less_equal(&zone->tree, &key, &res)) { + /* exact match */ + result = (struct val_neg_data*)res; + } else { + /* smaller element (or no element) */ + int m; + result = (struct val_neg_data*)res; + if(!result) + return NULL; + /* count number of labels matched */ + (void)dname_lab_cmp(result->name, result->labs, key.name, + key.labs, &m); + while(result) { /* go up until qname is subdomain of stub */ + if(result->labs <= m) + break; + result = result->parent; + } + } + return result; +} + +/** + * Create a single zone node + * @param nm: name for zone (copied) + * @param nm_len: length of name + * @param labs: labels in name. + * @param dclass: class of zone, host order. + * @return new zone or NULL on failure + */ +static struct val_neg_zone* neg_setup_zone_node( + uint8_t* nm, size_t nm_len, int labs, uint16_t dclass) +{ + struct val_neg_zone* zone = + (struct val_neg_zone*)calloc(1, sizeof(*zone)); + if(!zone) { + return NULL; + } + zone->node.key = zone; + zone->name = memdup(nm, nm_len); + if(!zone->name) { + free(zone); + return NULL; + } + zone->len = nm_len; + zone->labs = labs; + zone->dclass = dclass; + + rbtree_init(&zone->tree, &val_neg_data_compare); + return zone; +} + +/** + * Create a linked list of parent zones, starting at longname ending on + * the parent (can be NULL, creates to the root). + * @param nm: name for lowest in chain + * @param nm_len: length of name + * @param labs: labels in name. + * @param dclass: class of zone. + * @param parent: NULL for to root, else so it fits under here. + * @return zone; a chain of zones and their parents up to the parent. + * or NULL on malloc failure + */ +static struct val_neg_zone* neg_zone_chain( + uint8_t* nm, size_t nm_len, int labs, uint16_t dclass, + struct val_neg_zone* parent) +{ + int i; + int tolabs = parent?parent->labs:0; + struct val_neg_zone* zone, *prev = NULL, *first = NULL; + + /* create the new subtree, i is labelcount of current creation */ + /* this creates a 'first' to z->parent=NULL list of zones */ + for(i=labs; i!=tolabs; i--) { + /* create new item */ + zone = neg_setup_zone_node(nm, nm_len, i, dclass); + if(!zone) { + /* need to delete other allocations in this routine!*/ + struct val_neg_zone* p=first, *np; + while(p) { + np = p->parent; + free(p); + free(p->name); + p = np; + } + return NULL; + } + if(i == labs) { + first = zone; + } else { + prev->parent = zone; + } + /* prepare for next name */ + prev = zone; + dname_remove_label(&nm, &nm_len); + } + return first; +} + +void val_neg_zone_take_inuse(struct val_neg_zone* zone) +{ + if(!zone->in_use) { + struct val_neg_zone* p; + zone->in_use = 1; + /* increase usage count of all parents */ + for(p=zone; p; p = p->parent) { + p->count++; + } + } +} + +struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg, + uint8_t* nm, size_t nm_len, uint16_t dclass) +{ + struct val_neg_zone* zone; + struct val_neg_zone* parent; + struct val_neg_zone* p, *np; + int labs = dname_count_labels(nm); + + /* find closest enclosing parent zone that (still) exists */ + parent = neg_closest_zone_parent(neg, nm, nm_len, labs, dclass); + if(parent && query_dname_compare(parent->name, nm) == 0) + return parent; /* already exists, weird */ + /* if parent exists, it is in use */ + log_assert(!parent || parent->count > 0); + zone = neg_zone_chain(nm, nm_len, labs, dclass, parent); + if(!zone) { + return NULL; + } + + /* insert the list of zones into the tree */ + p = zone; + while(p) { + np = p->parent; + /* mem use */ + neg->use += sizeof(struct val_neg_zone) + p->len; + /* insert in tree */ + (void)rbtree_insert(&neg->tree, &p->node); + /* last one needs proper parent pointer */ + if(np == NULL) + p->parent = parent; + p = np; + } + return zone; +} + +/** find zone name of message, returns the SOA record */ +static struct ub_packed_rrset_key* reply_find_soa(struct reply_info* rep) +{ + size_t i; + for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_SOA) + return rep->rrsets[i]; + } + return NULL; +} + +/** see if the reply has NSEC records worthy of caching */ +static int reply_has_nsec(struct reply_info* rep) +{ + size_t i; + struct packed_rrset_data* d; + if(rep->security != sec_status_secure) + return 0; + for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) { + d = (struct packed_rrset_data*)rep->rrsets[i]-> + entry.data; + if(d->security == sec_status_secure) + return 1; + } + } + return 0; +} + + +/** + * Create single node of data element. + * @param nm: name (copied) + * @param nm_len: length of name + * @param labs: labels in name. + * @return element with name nm, or NULL malloc failure. + */ +static struct val_neg_data* neg_setup_data_node( + uint8_t* nm, size_t nm_len, int labs) +{ + struct val_neg_data* el; + el = (struct val_neg_data*)calloc(1, sizeof(*el)); + if(!el) { + return NULL; + } + el->node.key = el; + el->name = memdup(nm, nm_len); + if(!el->name) { + free(el); + return NULL; + } + el->len = nm_len; + el->labs = labs; + return el; +} + +/** + * Create chain of data element and parents + * @param nm: name + * @param nm_len: length of name + * @param labs: labels in name. + * @param parent: up to where to make, if NULL up to root label. + * @return lowest element with name nm, or NULL malloc failure. + */ +static struct val_neg_data* neg_data_chain( + uint8_t* nm, size_t nm_len, int labs, struct val_neg_data* parent) +{ + int i; + int tolabs = parent?parent->labs:0; + struct val_neg_data* el, *first = NULL, *prev = NULL; + + /* create the new subtree, i is labelcount of current creation */ + /* this creates a 'first' to z->parent=NULL list of zones */ + for(i=labs; i!=tolabs; i--) { + /* create new item */ + el = neg_setup_data_node(nm, nm_len, i); + if(!el) { + /* need to delete other allocations in this routine!*/ + struct val_neg_data* p = first, *np; + while(p) { + np = p->parent; + free(p); + free(p->name); + p = np; + } + return NULL; + } + if(i == labs) { + first = el; + } else { + prev->parent = el; + } + + /* prepare for next name */ + prev = el; + dname_remove_label(&nm, &nm_len); + } + return first; +} + +/** + * Remove NSEC records between start and end points. + * By walking the tree, the tree is sorted canonically. + * @param neg: negative cache. + * @param zone: the zone + * @param el: element to start walking at. + * @param nsec: the nsec record with the end point + */ +static void wipeout(struct val_neg_cache* neg, struct val_neg_zone* zone, + struct val_neg_data* el, struct ub_packed_rrset_key* nsec) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> + entry.data; + uint8_t* end; + size_t end_len; + int end_labs, m; + rbnode_t* walk, *next; + struct val_neg_data* cur; + uint8_t buf[257]; + /* get endpoint */ + if(!d || d->count == 0 || d->rr_len[0] < 2+1) + return; + if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC) { + end = d->rr_data[0]+2; + end_len = dname_valid(end, d->rr_len[0]-2); + end_labs = dname_count_labels(end); + } else { + /* NSEC3 */ + if(!nsec3_get_nextowner_b32(nsec, 0, buf, sizeof(buf))) + return; + end = buf; + end_labs = dname_count_size_labels(end, &end_len); + } + + /* sanity check, both owner and end must be below the zone apex */ + if(!dname_subdomain_c(el->name, zone->name) || + !dname_subdomain_c(end, zone->name)) + return; + + /* detect end of zone NSEC ; wipe until the end of zone */ + if(query_dname_compare(end, zone->name) == 0) { + end = NULL; + } + + walk = rbtree_next(&el->node); + while(walk && walk != RBTREE_NULL) { + cur = (struct val_neg_data*)walk; + /* sanity check: must be larger than start */ + if(dname_canon_lab_cmp(cur->name, cur->labs, + el->name, el->labs, &m) <= 0) { + /* r == 0 skip original record. */ + /* r < 0 too small! */ + walk = rbtree_next(walk); + continue; + } + /* stop at endpoint, also data at empty nonterminals must be + * removed (no NSECs there) so everything between + * start and end */ + if(end && dname_canon_lab_cmp(cur->name, cur->labs, + end, end_labs, &m) >= 0) { + break; + } + /* this element has to be deleted, but we cannot do it + * now, because we are walking the tree still ... */ + /* get the next element: */ + next = rbtree_next(walk); + /* now delete the original element, this may trigger + * rbtree rebalances, but really, the next element is + * the one we need. + * But it may trigger delete of other data and the + * entire zone. However, if that happens, this is done + * by deleting the *parents* of the element for deletion, + * and maybe also the entire zone if it is empty. + * But parents are smaller in canonical compare, thus, + * if a larger element exists, then it is not a parent, + * it cannot get deleted, the zone cannot get empty. + * If the next==NULL, then zone can be empty. */ + if(cur->in_use) + neg_delete_data(neg, cur); + walk = next; + } +} + +void neg_insert_data(struct val_neg_cache* neg, + struct val_neg_zone* zone, struct ub_packed_rrset_key* nsec) +{ + struct packed_rrset_data* d; + struct val_neg_data* parent; + struct val_neg_data* el; + uint8_t* nm = nsec->rk.dname; + size_t nm_len = nsec->rk.dname_len; + int labs = dname_count_labels(nsec->rk.dname); + + d = (struct packed_rrset_data*)nsec->entry.data; + if( !(d->security == sec_status_secure || + (d->security == sec_status_unchecked && d->rrsig_count > 0))) + return; + log_nametypeclass(VERB_ALGO, "negcache rr", + nsec->rk.dname, ntohs(nsec->rk.type), + ntohs(nsec->rk.rrset_class)); + + /* find closest enclosing parent data that (still) exists */ + parent = neg_closest_data_parent(zone, nm, nm_len, labs); + if(parent && query_dname_compare(parent->name, nm) == 0) { + /* perfect match already exists */ + log_assert(parent->count > 0); + el = parent; + } else { + struct val_neg_data* p, *np; + + /* create subtree for perfect match */ + /* if parent exists, it is in use */ + log_assert(!parent || parent->count > 0); + + el = neg_data_chain(nm, nm_len, labs, parent); + if(!el) { + log_err("out of memory inserting NSEC negative cache"); + return; + } + el->in_use = 0; /* set on below */ + + /* insert the list of zones into the tree */ + p = el; + while(p) { + np = p->parent; + /* mem use */ + neg->use += sizeof(struct val_neg_data) + p->len; + /* insert in tree */ + p->zone = zone; + (void)rbtree_insert(&zone->tree, &p->node); + /* last one needs proper parent pointer */ + if(np == NULL) + p->parent = parent; + p = np; + } + } + + if(!el->in_use) { + struct val_neg_data* p; + + el->in_use = 1; + /* increase usage count of all parents */ + for(p=el; p; p = p->parent) { + p->count++; + } + + neg_lru_front(neg, el); + } else { + /* in use, bring to front, lru */ + neg_lru_touch(neg, el); + } + + /* if nsec3 store last used parameters */ + if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC3) { + int h; + uint8_t* s; + size_t slen, it; + if(nsec3_get_params(nsec, 0, &h, &it, &s, &slen) && + it <= neg->nsec3_max_iter && + (h != zone->nsec3_hash || it != zone->nsec3_iter || + slen != zone->nsec3_saltlen || + memcmp(zone->nsec3_salt, s, slen) != 0)) { + uint8_t* sa = memdup(s, slen); + if(sa) { + free(zone->nsec3_salt); + zone->nsec3_salt = sa; + zone->nsec3_saltlen = slen; + zone->nsec3_hash = h; + zone->nsec3_iter = it; + } + } + } + + /* wipe out the cache items between NSEC start and end */ + wipeout(neg, zone, el, nsec); +} + +void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep) +{ + size_t i, need; + struct ub_packed_rrset_key* soa; + struct val_neg_zone* zone; + /* see if secure nsecs inside */ + if(!reply_has_nsec(rep)) + return; + /* find the zone name in message */ + soa = reply_find_soa(rep); + if(!soa) + return; + + log_nametypeclass(VERB_ALGO, "negcache insert for zone", + soa->rk.dname, LDNS_RR_TYPE_SOA, ntohs(soa->rk.rrset_class)); + + /* ask for enough space to store all of it */ + need = calc_data_need(rep) + + calc_zone_need(soa->rk.dname, soa->rk.dname_len); + lock_basic_lock(&neg->lock); + neg_make_space(neg, need); + + /* find or create the zone entry */ + zone = neg_find_zone(neg, soa->rk.dname, soa->rk.dname_len, + ntohs(soa->rk.rrset_class)); + if(!zone) { + if(!(zone = neg_create_zone(neg, soa->rk.dname, + soa->rk.dname_len, ntohs(soa->rk.rrset_class)))) { + lock_basic_unlock(&neg->lock); + log_err("out of memory adding negative zone"); + return; + } + } + val_neg_zone_take_inuse(zone); + + /* insert the NSECs */ + for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ + if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC) + continue; + if(!dname_subdomain_c(rep->rrsets[i]->rk.dname, + zone->name)) continue; + /* insert NSEC into this zone's tree */ + neg_insert_data(neg, zone, rep->rrsets[i]); + } + if(zone->tree.count == 0) { + /* remove empty zone if inserts failed */ + neg_delete_zone(neg, zone); + } + lock_basic_unlock(&neg->lock); +} + +/** + * Lookup closest data record. For NSEC denial. + * @param zone: zone to look in + * @param qname: name to look for. + * @param len: length of name + * @param labs: labels in name + * @param data: data element, exact or smaller or NULL + * @return true if exact match. + */ +static int neg_closest_data(struct val_neg_zone* zone, + uint8_t* qname, size_t len, int labs, struct val_neg_data** data) +{ + struct val_neg_data key; + rbnode_t* r; + key.node.key = &key; + key.name = qname; + key.len = len; + key.labs = labs; + if(rbtree_find_less_equal(&zone->tree, &key, &r)) { + /* exact match */ + *data = (struct val_neg_data*)r; + return 1; + } else { + /* smaller match */ + *data = (struct val_neg_data*)r; + return 0; + } +} + +int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len, + uint16_t qclass, struct rrset_cache* rrset_cache, uint32_t now) +{ + /* lookup closest zone */ + struct val_neg_zone* zone; + struct val_neg_data* data; + int labs; + struct ub_packed_rrset_key* nsec; + struct packed_rrset_data* d; + uint32_t flags; + uint8_t* wc; + struct query_info qinfo; + if(!neg) return 0; + + log_nametypeclass(VERB_ALGO, "negcache dlvlookup", qname, + LDNS_RR_TYPE_DLV, qclass); + + labs = dname_count_labels(qname); + lock_basic_lock(&neg->lock); + zone = neg_closest_zone_parent(neg, qname, len, labs, qclass); + while(zone && !zone->in_use) + zone = zone->parent; + if(!zone) { + lock_basic_unlock(&neg->lock); + return 0; + } + log_nametypeclass(VERB_ALGO, "negcache zone", zone->name, 0, + zone->dclass); + + /* DLV is defined to use NSEC only */ + if(zone->nsec3_hash) { + lock_basic_unlock(&neg->lock); + return 0; + } + + /* lookup closest data record */ + (void)neg_closest_data(zone, qname, len, labs, &data); + while(data && !data->in_use) + data = data->parent; + if(!data) { + lock_basic_unlock(&neg->lock); + return 0; + } + log_nametypeclass(VERB_ALGO, "negcache rr", data->name, + LDNS_RR_TYPE_NSEC, zone->dclass); + + /* lookup rrset in rrset cache */ + flags = 0; + if(query_dname_compare(data->name, zone->name) == 0) + flags = PACKED_RRSET_NSEC_AT_APEX; + nsec = rrset_cache_lookup(rrset_cache, data->name, data->len, + LDNS_RR_TYPE_NSEC, zone->dclass, flags, now, 0); + + /* check if secure and TTL ok */ + if(!nsec) { + lock_basic_unlock(&neg->lock); + return 0; + } + d = (struct packed_rrset_data*)nsec->entry.data; + if(!d || now > d->ttl) { + lock_rw_unlock(&nsec->entry.lock); + /* delete data record if expired */ + neg_delete_data(neg, data); + lock_basic_unlock(&neg->lock); + return 0; + } + if(d->security != sec_status_secure) { + lock_rw_unlock(&nsec->entry.lock); + neg_delete_data(neg, data); + lock_basic_unlock(&neg->lock); + return 0; + } + verbose(VERB_ALGO, "negcache got secure rrset"); + + /* check NSEC security */ + /* check if NSEC proves no DLV type exists */ + /* check if NSEC proves NXDOMAIN for qname */ + qinfo.qname = qname; + qinfo.qtype = LDNS_RR_TYPE_DLV; + qinfo.qclass = qclass; + if(!nsec_proves_nodata(nsec, &qinfo, &wc) && + !val_nsec_proves_name_error(nsec, qname)) { + /* the NSEC is not a denial for the DLV */ + lock_rw_unlock(&nsec->entry.lock); + lock_basic_unlock(&neg->lock); + verbose(VERB_ALGO, "negcache not proven"); + return 0; + } + /* so the NSEC was a NODATA proof, or NXDOMAIN proof. */ + + /* no need to check for wildcard NSEC; no wildcards in DLV repos */ + /* no need to lookup SOA record for client; no response message */ + + lock_rw_unlock(&nsec->entry.lock); + /* if OK touch the LRU for neg_data element */ + neg_lru_touch(neg, data); + lock_basic_unlock(&neg->lock); + verbose(VERB_ALGO, "negcache DLV denial proven"); + return 1; +} + +/** see if the reply has signed NSEC records and return the signer */ +static uint8_t* reply_nsec_signer(struct reply_info* rep, size_t* signer_len, + uint16_t* dclass) +{ + size_t i; + struct packed_rrset_data* d; + uint8_t* s; + for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC || + ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC3) { + d = (struct packed_rrset_data*)rep->rrsets[i]-> + entry.data; + /* return first signer name of first NSEC */ + if(d->rrsig_count != 0) { + val_find_rrset_signer(rep->rrsets[i], + &s, signer_len); + if(s && *signer_len) { + *dclass = ntohs(rep->rrsets[i]-> + rk.rrset_class); + return s; + } + } + } + } + return 0; +} + +void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep, + uint8_t* zone_name) +{ + size_t i, need; + uint8_t* signer; + size_t signer_len; + uint16_t dclass; + struct val_neg_zone* zone; + /* no SOA in this message, find RRSIG over NSEC's signer name. + * note the NSEC records are maybe not validated yet */ + signer = reply_nsec_signer(rep, &signer_len, &dclass); + if(!signer) + return; + if(!dname_subdomain_c(signer, zone_name)) { + /* the signer is not in the bailiwick, throw it out */ + return; + } + + log_nametypeclass(VERB_ALGO, "negcache insert referral ", + signer, LDNS_RR_TYPE_NS, dclass); + + /* ask for enough space to store all of it */ + need = calc_data_need(rep) + calc_zone_need(signer, signer_len); + lock_basic_lock(&neg->lock); + neg_make_space(neg, need); + + /* find or create the zone entry */ + zone = neg_find_zone(neg, signer, signer_len, dclass); + if(!zone) { + if(!(zone = neg_create_zone(neg, signer, signer_len, + dclass))) { + lock_basic_unlock(&neg->lock); + log_err("out of memory adding negative zone"); + return; + } + } + val_neg_zone_take_inuse(zone); + + /* insert the NSECs */ + for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ + if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC && + ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC3) + continue; + if(!dname_subdomain_c(rep->rrsets[i]->rk.dname, + zone->name)) continue; + /* insert NSEC into this zone's tree */ + neg_insert_data(neg, zone, rep->rrsets[i]); + } + if(zone->tree.count == 0) { + /* remove empty zone if inserts failed */ + neg_delete_zone(neg, zone); + } + lock_basic_unlock(&neg->lock); +} + +/** + * Check that an NSEC3 rrset does not have a type set. + * None of the nsec3s in a hash-collision are allowed to have the type. + * (since we do not know which one is the nsec3 looked at, flags, ..., we + * ignore the cached item and let it bypass negative caching). + * @param k: the nsec3 rrset to check. + * @param t: type to check + * @return true if no RRs have the type. + */ +static int nsec3_no_type(struct ub_packed_rrset_key* k, uint16_t t) +{ + int count = (int)((struct packed_rrset_data*)k->entry.data)->count; + int i; + for(i=0; i<count; i++) + if(nsec3_has_type(k, i, t)) + return 0; + return 1; +} + +/** + * See if rrset exists in rrset cache. + * If it does, the bit is checked, and if not expired, it is returned + * allocated in region. + * @param rrset_cache: rrset cache + * @param qname: to lookup rrset name + * @param qname_len: length of qname. + * @param qtype: type of rrset to lookup, host order + * @param qclass: class of rrset to lookup, host order + * @param flags: flags for rrset to lookup + * @param region: where to alloc result + * @param checkbit: if true, a bit in the nsec typemap is checked for absence. + * @param checktype: which bit to check + * @param now: to check ttl against + * @return rrset or NULL + */ +static struct ub_packed_rrset_key* +grab_nsec(struct rrset_cache* rrset_cache, uint8_t* qname, size_t qname_len, + uint16_t qtype, uint16_t qclass, uint32_t flags, + struct regional* region, int checkbit, uint16_t checktype, + uint32_t now) +{ + struct ub_packed_rrset_key* r, *k = rrset_cache_lookup(rrset_cache, + qname, qname_len, qtype, qclass, flags, now, 0); + struct packed_rrset_data* d; + if(!k) return NULL; + d = (struct packed_rrset_data*)k->entry.data; + if(d->ttl < now) { + lock_rw_unlock(&k->entry.lock); + return NULL; + } + /* only secure or unchecked records that have signatures. */ + if( ! ( d->security == sec_status_secure || + (d->security == sec_status_unchecked && + d->rrsig_count > 0) ) ) { + lock_rw_unlock(&k->entry.lock); + return NULL; + } + /* check if checktype is absent */ + if(checkbit && ( + (qtype == LDNS_RR_TYPE_NSEC && nsec_has_type(k, checktype)) || + (qtype == LDNS_RR_TYPE_NSEC3 && !nsec3_no_type(k, checktype)) + )) { + lock_rw_unlock(&k->entry.lock); + return NULL; + } + /* looks OK! copy to region and return it */ + r = packed_rrset_copy_region(k, region, now); + /* if it failed, we return the NULL */ + lock_rw_unlock(&k->entry.lock); + return r; +} + +/** find nsec3 closest encloser in neg cache */ +static struct val_neg_data* +neg_find_nsec3_ce(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, + int qlabs, ldns_buffer* buf, uint8_t* hashnc, size_t* nclen) +{ + struct val_neg_data* data; + uint8_t hashce[SHA_DIGEST_LENGTH]; + uint8_t b32[257]; + size_t celen, b32len; + + *nclen = 0; + while(qlabs > 0) { + /* hash */ + if(!(celen=nsec3_get_hashed(buf, qname, qname_len, + zone->nsec3_hash, zone->nsec3_iter, zone->nsec3_salt, + zone->nsec3_saltlen, hashce, sizeof(hashce)))) + return NULL; + if(!(b32len=nsec3_hash_to_b32(hashce, celen, zone->name, + zone->len, b32, sizeof(b32)))) + return NULL; + + /* lookup (exact match only) */ + data = neg_find_data(zone, b32, b32len, zone->labs+1); + if(data && data->in_use) { + /* found ce match! */ + return data; + } + + *nclen = celen; + memmove(hashnc, hashce, celen); + dname_remove_label(&qname, &qname_len); + qlabs --; + } + return NULL; +} + +/** check nsec3 parameters on nsec3 rrset with current zone values */ +static int +neg_params_ok(struct val_neg_zone* zone, struct ub_packed_rrset_key* rrset) +{ + int h; + uint8_t* s; + size_t slen, it; + if(!nsec3_get_params(rrset, 0, &h, &it, &s, &slen)) + return 0; + return (h == zone->nsec3_hash && it == zone->nsec3_iter && + slen == zone->nsec3_saltlen && + memcmp(zone->nsec3_salt, s, slen) == 0); +} + +/** get next closer for nsec3 proof */ +static struct ub_packed_rrset_key* +neg_nsec3_getnc(struct val_neg_zone* zone, uint8_t* hashnc, size_t nclen, + struct rrset_cache* rrset_cache, struct regional* region, + uint32_t now, uint8_t* b32, size_t maxb32) +{ + struct ub_packed_rrset_key* nc_rrset; + struct val_neg_data* data; + size_t b32len; + + if(!(b32len=nsec3_hash_to_b32(hashnc, nclen, zone->name, + zone->len, b32, maxb32))) + return NULL; + (void)neg_closest_data(zone, b32, b32len, zone->labs+1, &data); + if(!data && zone->tree.count != 0) { + /* could be before the first entry ; return the last + * entry (possibly the rollover nsec3 at end) */ + data = (struct val_neg_data*)rbtree_last(&zone->tree); + } + while(data && !data->in_use) + data = data->parent; + if(!data) + return NULL; + /* got a data element in tree, grab it */ + nc_rrset = grab_nsec(rrset_cache, data->name, data->len, + LDNS_RR_TYPE_NSEC3, zone->dclass, 0, region, 0, 0, now); + if(!nc_rrset) + return NULL; + if(!neg_params_ok(zone, nc_rrset)) + return NULL; + return nc_rrset; +} + +/** neg cache nsec3 proof procedure*/ +static struct dns_msg* +neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, + int qlabs, ldns_buffer* buf, struct rrset_cache* rrset_cache, + struct regional* region, uint32_t now, uint8_t* topname) +{ + struct dns_msg* msg; + struct val_neg_data* data; + uint8_t hashnc[SHA_DIGEST_LENGTH]; + size_t nclen; + struct ub_packed_rrset_key* ce_rrset, *nc_rrset; + struct nsec3_cached_hash c; + uint8_t nc_b32[257]; + + /* for NSEC3 ; determine the closest encloser for which we + * can find an exact match. Remember the hashed lower name, + * since that is the one we need a closest match for. + * If we find a match straight away, then it becomes NODATA. + * Otherwise, NXDOMAIN or if OPTOUT, an insecure delegation. + * Also check that parameters are the same on closest encloser + * and on closest match. + */ + if(!zone->nsec3_hash) + return NULL; /* not nsec3 zone */ + + if(!(data=neg_find_nsec3_ce(zone, qname, qname_len, qlabs, buf, + hashnc, &nclen))) { + return NULL; + } + + /* grab the ce rrset */ + ce_rrset = grab_nsec(rrset_cache, data->name, data->len, + LDNS_RR_TYPE_NSEC3, zone->dclass, 0, region, 1, + LDNS_RR_TYPE_DS, now); + if(!ce_rrset) + return NULL; + if(!neg_params_ok(zone, ce_rrset)) + return NULL; + + if(nclen == 0) { + /* exact match, just check the type bits */ + /* need: -SOA, -DS, +NS */ + if(nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_SOA) || + nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_DS) || + !nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_NS)) + return NULL; + if(!(msg = dns_msg_create(qname, qname_len, + LDNS_RR_TYPE_DS, zone->dclass, region, 1))) + return NULL; + /* TTL reduced in grab_nsec */ + if(!dns_msg_authadd(msg, region, ce_rrset, 0)) + return NULL; + return msg; + } + + /* optout is not allowed without knowing the trust-anchor in use, + * otherwise the optout could spoof away that anchor */ + if(!topname) + return NULL; + + /* if there is no exact match, it must be in an optout span + * (an existing DS implies an NSEC3 must exist) */ + nc_rrset = neg_nsec3_getnc(zone, hashnc, nclen, rrset_cache, + region, now, nc_b32, sizeof(nc_b32)); + if(!nc_rrset) + return NULL; + if(!neg_params_ok(zone, nc_rrset)) + return NULL; + if(!nsec3_has_optout(nc_rrset, 0)) + return NULL; + c.hash = hashnc; + c.hash_len = nclen; + c.b32 = nc_b32+1; + c.b32_len = (size_t)nc_b32[0]; + if(nsec3_covers(zone->name, &c, nc_rrset, 0, buf)) { + /* nc_rrset covers the next closer name. + * ce_rrset equals a closer encloser. + * nc_rrset is optout. + * No need to check wildcard for type DS */ + /* capacity=3: ce + nc + soa(if needed) */ + if(!(msg = dns_msg_create(qname, qname_len, + LDNS_RR_TYPE_DS, zone->dclass, region, 3))) + return NULL; + /* now=0 because TTL was reduced in grab_nsec */ + if(!dns_msg_authadd(msg, region, ce_rrset, 0)) + return NULL; + if(!dns_msg_authadd(msg, region, nc_rrset, 0)) + return NULL; + return msg; + } + return NULL; +} + +/** + * Add SOA record for external responses. + * @param rrset_cache: to look into. + * @param now: current time. + * @param region: where to perform the allocation + * @param msg: current msg with NSEC. + * @param zone: val_neg_zone if we have one. + * @return false on lookup or alloc failure. + */ +static int add_soa(struct rrset_cache* rrset_cache, uint32_t now, + struct regional* region, struct dns_msg* msg, struct val_neg_zone* zone) +{ + struct ub_packed_rrset_key* soa; + uint8_t* nm; + size_t nmlen; + uint16_t dclass; + if(zone) { + nm = zone->name; + nmlen = zone->len; + dclass = zone->dclass; + } else { + /* Assumes the signer is the zone SOA to add */ + nm = reply_nsec_signer(msg->rep, &nmlen, &dclass); + if(!nm) + return 0; + } + soa = rrset_cache_lookup(rrset_cache, nm, nmlen, LDNS_RR_TYPE_SOA, + dclass, PACKED_RRSET_SOA_NEG, now, 0); + if(!soa) + return 0; + if(!dns_msg_authadd(msg, region, soa, now)) { + lock_rw_unlock(&soa->entry.lock); + return 0; + } + lock_rw_unlock(&soa->entry.lock); + return 1; +} + +struct dns_msg* +val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, + struct regional* region, struct rrset_cache* rrset_cache, + ldns_buffer* buf, uint32_t now, int addsoa, uint8_t* topname) +{ + struct dns_msg* msg; + struct ub_packed_rrset_key* rrset; + uint8_t* zname; + size_t zname_len; + int zname_labs; + struct val_neg_zone* zone; + + /* only for DS queries */ + if(qinfo->qtype != LDNS_RR_TYPE_DS) + return NULL; + log_assert(!topname || dname_subdomain_c(qinfo->qname, topname)); + + /* see if info from neg cache is available + * For NSECs, because there is no optout; a DS next to a delegation + * always has exactly an NSEC for it itself; check its DS bit. + * flags=0 (not the zone apex). + */ + rrset = grab_nsec(rrset_cache, qinfo->qname, qinfo->qname_len, + LDNS_RR_TYPE_NSEC, qinfo->qclass, 0, region, 1, + qinfo->qtype, now); + if(rrset) { + /* return msg with that rrset */ + if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len, + qinfo->qtype, qinfo->qclass, region, 2))) + return NULL; + /* TTL already subtracted in grab_nsec */ + if(!dns_msg_authadd(msg, region, rrset, 0)) + return NULL; + if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL)) + return NULL; + return msg; + } + + /* check NSEC3 neg cache for type DS */ + /* need to look one zone higher for DS type */ + zname = qinfo->qname; + zname_len = qinfo->qname_len; + dname_remove_label(&zname, &zname_len); + zname_labs = dname_count_labels(zname); + + /* lookup closest zone */ + lock_basic_lock(&neg->lock); + zone = neg_closest_zone_parent(neg, zname, zname_len, zname_labs, + qinfo->qclass); + while(zone && !zone->in_use) + zone = zone->parent; + /* check that the zone is not too high up so that we do not pick data + * out of a zone that is above the last-seen key (or trust-anchor). */ + if(zone && topname) { + if(!dname_subdomain_c(zone->name, topname)) + zone = NULL; + } + if(!zone) { + lock_basic_unlock(&neg->lock); + return NULL; + } + + msg = neg_nsec3_proof_ds(zone, qinfo->qname, qinfo->qname_len, + zname_labs+1, buf, rrset_cache, region, now, topname); + if(msg && addsoa && !add_soa(rrset_cache, now, region, msg, zone)) { + lock_basic_unlock(&neg->lock); + return NULL; + } + lock_basic_unlock(&neg->lock); + return msg; +} diff --git a/validator/val_neg.h b/validator/val_neg.h new file mode 100644 index 0000000000000..01b423e1afb39 --- /dev/null +++ b/validator/val_neg.h @@ -0,0 +1,314 @@ +/* + * validator/val_neg.h - validator aggressive negative caching functions. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with aggressive negative caching. + * This creates new denials of existance, and proofs for absence of types + * from cached NSEC records. + */ + +#ifndef VALIDATOR_VAL_NEG_H +#define VALIDATOR_VAL_NEG_H +#include "util/locks.h" +#include "util/rbtree.h" +struct val_neg_data; +struct config_file; +struct reply_info; +struct rrset_cache; +struct regional; +struct query_info; +struct dns_msg; +struct ub_packed_rrset_key; + +/** + * The negative cache. It is shared between the threads, so locked. + * Kept as validator-environ-state. It refers back to the rrset cache for + * data elements. It can be out of date and contain conflicting data + * from zone content changes. + * It contains a tree of zones, every zone has a tree of data elements. + * The data elements are part of one big LRU list, with one memory counter. + */ +struct val_neg_cache { + /** the big lock on the negative cache. Because we use a rbtree + * for the data (quick lookup), we need a big lock */ + lock_basic_t lock; + /** The zone rbtree. contents sorted canonical, type val_neg_zone */ + rbtree_t tree; + /** the first in linked list of LRU of val_neg_data */ + struct val_neg_data* first; + /** last in lru (least recently used element) */ + struct val_neg_data* last; + /** current memory in use (bytes) */ + size_t use; + /** max memory to use (bytes) */ + size_t max; + /** max nsec3 iterations allowed */ + size_t nsec3_max_iter; +}; + +/** + * Per Zone aggressive negative caching data. + */ +struct val_neg_zone { + /** rbtree node element, key is this struct: the name, class */ + rbnode_t node; + /** name; the key */ + uint8_t* name; + /** length of name */ + size_t len; + /** labels in name */ + int labs; + + /** pointer to parent zone in the negative cache */ + struct val_neg_zone* parent; + + /** the number of elements, including this one and the ones whose + * parents (-parents) include this one, that are in_use + * No elements have a count of zero, those are removed. */ + int count; + + /** if 0: NSEC zone, else NSEC3 hash algorithm in use */ + int nsec3_hash; + /** nsec3 iteration count in use */ + size_t nsec3_iter; + /** nsec3 salt in use */ + uint8_t* nsec3_salt; + /** length of salt in bytes */ + size_t nsec3_saltlen; + + /** tree of NSEC data for this zone, sorted canonical + * by NSEC owner name */ + rbtree_t tree; + + /** class of node; host order */ + uint16_t dclass; + /** if this element is in use, boolean */ + uint8_t in_use; +}; + +/** + * Data element for aggressive negative caching. + * The tree of these elements acts as an index onto the rrset cache. + * It shows the NSEC records that (may) exist and are (possibly) secure. + * The rbtree allows for logN search for a covering NSEC record. + * To make tree insertion and deletion logN too, all the parent (one label + * less than the name) data elements are also in the rbtree, with a usage + * count for every data element. + * There is no actual data stored in this data element, if it is in_use, + * then the data can (possibly) be found in the rrset cache. + */ +struct val_neg_data { + /** rbtree node element, key is this struct: the name */ + rbnode_t node; + /** name; the key */ + uint8_t* name; + /** length of name */ + size_t len; + /** labels in name */ + int labs; + + /** pointer to parent node in the negative cache */ + struct val_neg_data* parent; + + /** the number of elements, including this one and the ones whose + * parents (-parents) include this one, that are in use + * No elements have a count of zero, those are removed. */ + int count; + + /** the zone that this denial is part of */ + struct val_neg_zone* zone; + + /** previous in LRU */ + struct val_neg_data* prev; + /** next in LRU (next element was less recently used) */ + struct val_neg_data* next; + + /** if this element is in use, boolean */ + uint8_t in_use; +}; + +/** + * Create negative cache + * @param cfg: config options. + * @param maxiter: max nsec3 iterations allowed. + * @return neg cache, empty or NULL on failure. + */ +struct val_neg_cache* val_neg_create(struct config_file* cfg, size_t maxiter); + +/** + * see how much memory is in use by the negative cache. + * @param neg: negative cache + * @return number of bytes in use. + */ +size_t val_neg_get_mem(struct val_neg_cache* neg); + +/** + * Destroy negative cache. There must no longer be any other threads. + * @param neg: negative cache. + */ +void neg_cache_delete(struct val_neg_cache* neg); + +/** + * Comparison function for rbtree val neg data elements + */ +int val_neg_data_compare(const void* a, const void* b); + +/** + * Comparison function for rbtree val neg zone elements + */ +int val_neg_zone_compare(const void* a, const void* b); + +/** + * Insert NSECs from this message into the negative cache for reference. + * @param neg: negative cache + * @param rep: reply with NSECs. + * Errors are ignored, means that storage is omitted. + */ +void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep); + +/** + * Insert NSECs from this referral into the negative cache for reference. + * @param neg: negative cache + * @param rep: referral reply with NS, NSECs. + * @param zone: bailiwick for the referral. + * Errors are ignored, means that storage is omitted. + */ +void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep, + uint8_t* zone); + +/** + * Perform a DLV style lookup + * During the lookup, we could find out that data has expired. In that + * case the neg_cache entries are removed, and lookup fails. + * + * @param neg: negative cache. + * @param qname: name to look for + * @param len: length of qname. + * @param qclass: class to look in. + * @param rrset_cache: the rrset cache, for NSEC lookups. + * @param now: current time for ttl checks. + * @return + * 0 on error + * 0 if no proof of negative + * 1 if indeed negative was proven + * thus, qname DLV qclass does not exist. + */ +int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len, + uint16_t qclass, struct rrset_cache* rrset_cache, uint32_t now); + +/** + * For the given query, try to get a reply out of the negative cache. + * The reply still needs to be validated. + * @param neg: negative cache. + * @param qinfo: query + * @param region: where to allocate reply. + * @param rrset_cache: rrset cache. + * @param buf: temporary buffer. + * @param now: to check TTLs against. + * @param addsoa: if true, produce result for external consumption. + * if false, do not add SOA - for unbound-internal consumption. + * @param topname: do not look higher than this name, + * so that the result cannot be taken from a zone above the current + * trust anchor. Which could happen with multiple islands of trust. + * if NULL, then no trust anchor is used, but also the algorithm becomes + * more conservative, especially for opt-out zones, since the receiver + * may have a trust-anchor below the optout and thus the optout cannot + * be used to create a proof from the negative cache. + * @return a reply message if something was found. + * This reply may still need validation. + * NULL if nothing found (or out of memory). + */ +struct dns_msg* val_neg_getmsg(struct val_neg_cache* neg, + struct query_info* qinfo, struct regional* region, + struct rrset_cache* rrset_cache, ldns_buffer* buf, uint32_t now, + int addsoa, uint8_t* topname); + + +/**** functions exposed for unit test ****/ +/** + * Insert data into the data tree of a zone + * Does not do locking. + * @param neg: negative cache + * @param zone: zone to insert into + * @param nsec: record to insert. + */ +void neg_insert_data(struct val_neg_cache* neg, + struct val_neg_zone* zone, struct ub_packed_rrset_key* nsec); + +/** + * Delete a data element from the negative cache. + * May delete other data elements to keep tree coherent, or + * only mark the element as 'not in use'. + * Does not do locking. + * @param neg: negative cache. + * @param el: data element to delete. + */ +void neg_delete_data(struct val_neg_cache* neg, struct val_neg_data* el); + +/** + * Find the given zone, from the SOA owner name and class + * Does not do locking. + * @param neg: negative cache + * @param nm: what to look for. + * @param len: length of nm + * @param dclass: class to look for. + * @return zone or NULL if not found. + */ +struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg, + uint8_t* nm, size_t len, uint16_t dclass); + +/** + * Create a new zone. + * Does not do locking. + * @param neg: negative cache + * @param nm: what to look for. + * @param nm_len: length of name. + * @param dclass: class of zone, host order. + * @return zone or NULL if out of memory. + */ +struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg, + uint8_t* nm, size_t nm_len, uint16_t dclass); + +/** + * take a zone into use. increases counts of parents. + * Does not do locking. + * @param zone: zone to take into use. + */ +void val_neg_zone_take_inuse(struct val_neg_zone* zone); + +#endif /* VALIDATOR_VAL_NEG_H */ diff --git a/validator/val_nsec.c b/validator/val_nsec.c new file mode 100644 index 0000000000000..8bda8dabc9376 --- /dev/null +++ b/validator/val_nsec.c @@ -0,0 +1,603 @@ +/* + * validator/val_nsec.c - validator NSEC denial of existance functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with NSEC checking, the different NSEC proofs + * for denial of existance, and proofs for presence of types. + */ +#include "config.h" +#include <ldns/packet.h> +#include "validator/val_nsec.h" +#include "validator/val_utils.h" +#include "util/data/msgreply.h" +#include "util/data/dname.h" +#include "util/net_help.h" +#include "util/module.h" +#include "services/cache/rrset.h" + +/** get ttl of rrset */ +static uint32_t +rrset_get_ttl(struct ub_packed_rrset_key* k) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + return d->ttl; +} + +int +nsecbitmap_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type) +{ + /* Check type present in NSEC typemap with bitmap arg */ + /* bitmasks for determining type-lowerbits presence */ + uint8_t masks[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; + uint8_t type_window = type>>8; + uint8_t type_low = type&0xff; + uint8_t win, winlen; + /* read each of the type bitmap windows and see if the searched + * type is amongst it */ + while(len > 0) { + if(len < 3) /* bad window, at least window# winlen bitmap */ + return 0; + win = *bitmap++; + winlen = *bitmap++; + len -= 2; + if(len < winlen || winlen < 1 || winlen > 32) + return 0; /* bad window length */ + if(win == type_window) { + /* search window bitmap for the correct byte */ + /* mybyte is 0 if we need the first byte */ + size_t mybyte = type_low>>3; + if(winlen <= mybyte) + return 0; /* window too short */ + return (int)(bitmap[mybyte] & masks[type_low&0x7]); + } else { + /* not the window we are looking for */ + bitmap += winlen; + len -= winlen; + } + } + /* end of bitmap reached, no type found */ + return 0; +} + +int +nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> + entry.data; + size_t len; + if(!d || d->count == 0 || d->rr_len[0] < 2+1) + return 0; + len = dname_valid(d->rr_data[0]+2, d->rr_len[0]-2); + if(!len) + return 0; + return nsecbitmap_has_type_rdata(d->rr_data[0]+2+len, + d->rr_len[0]-2-len, type); +} + +/** + * Get next owner name from nsec record + * @param nsec: the nsec RRset. + * If there are multiple RRs, then this will only return one of them. + * @param nm: the next name is returned. + * @param ln: length of nm is returned. + * @return false on a bad NSEC RR (too short, malformed dname). + */ +static int +nsec_get_next(struct ub_packed_rrset_key* nsec, uint8_t** nm, size_t* ln) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> + entry.data; + if(!d || d->count == 0 || d->rr_len[0] < 2+1) { + *nm = 0; + *ln = 0; + return 0; + } + *nm = d->rr_data[0]+2; + *ln = dname_valid(*nm, d->rr_len[0]-2); + if(!*ln) { + *nm = 0; + *ln = 0; + return 0; + } + return 1; +} + +/** + * For an NSEC that matches the DS queried for, check absence of DS type. + * + * @param nsec: NSEC for proof, must be trusted. + * @param qinfo: what is queried for. + * @return if secure the nsec proves that no DS is present, or + * insecure if it proves it is not a delegation point. + * or bogus if something was wrong. + */ +static enum sec_status +val_nsec_proves_no_ds(struct ub_packed_rrset_key* nsec, + struct query_info* qinfo) +{ + log_assert(qinfo->qtype == LDNS_RR_TYPE_DS); + log_assert(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC); + + if(nsec_has_type(nsec, LDNS_RR_TYPE_SOA) && qinfo->qname_len != 1) { + /* SOA present means that this is the NSEC from the child, + * not the parent (so it is the wrong one). */ + return sec_status_bogus; + } + if(nsec_has_type(nsec, LDNS_RR_TYPE_DS)) { + /* DS present means that there should have been a positive + * response to the DS query, so there is something wrong. */ + return sec_status_bogus; + } + + if(!nsec_has_type(nsec, LDNS_RR_TYPE_NS)) { + /* If there is no NS at this point at all, then this + * doesn't prove anything one way or the other. */ + return sec_status_insecure; + } + /* Otherwise, this proves no DS. */ + return sec_status_secure; +} + +/** check security status from cache or verify rrset, returns true if secure */ +static int +nsec_verify_rrset(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey, + char** reason) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + nsec->entry.data; + if(d->security == sec_status_secure) + return 1; + rrset_check_sec_status(env->rrset_cache, nsec, *env->now); + if(d->security == sec_status_secure) + return 1; + d->security = val_verify_rrset_entry(env, ve, nsec, kkey, reason); + if(d->security == sec_status_secure) { + rrset_update_sec_status(env->rrset_cache, nsec, *env->now); + return 1; + } + return 0; +} + +enum sec_status +val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, + struct query_info* qinfo, struct reply_info* rep, + struct key_entry_key* kkey, uint32_t* proof_ttl, char** reason) +{ + struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns( + rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC, + qinfo->qclass); + enum sec_status sec; + size_t i; + uint8_t* wc = NULL, *ce = NULL; + int valid_nsec = 0; + struct ub_packed_rrset_key* wc_nsec = NULL; + + /* If we have a NSEC at the same name, it must prove one + * of two things + * -- + * 1) this is a delegation point and there is no DS + * 2) this is not a delegation point */ + if(nsec) { + if(!nsec_verify_rrset(env, ve, nsec, kkey, reason)) { + verbose(VERB_ALGO, "NSEC RRset for the " + "referral did not verify."); + return sec_status_bogus; + } + sec = val_nsec_proves_no_ds(nsec, qinfo); + if(sec == sec_status_bogus) { + /* something was wrong. */ + *reason = "NSEC does not prove absence of DS"; + return sec; + } else if(sec == sec_status_insecure) { + /* this wasn't a delegation point. */ + return sec; + } else if(sec == sec_status_secure) { + /* this proved no DS. */ + *proof_ttl = ub_packed_rrset_ttl(nsec); + return sec; + } + /* if unchecked, fall through to next proof */ + } + + /* Otherwise, there is no NSEC at qname. This could be an ENT. + * (ENT=empty non terminal). If not, this is broken. */ + + /* verify NSEC rrsets in auth section */ + for(i=rep->an_numrrsets; i < rep->an_numrrsets+rep->ns_numrrsets; + i++) { + if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC)) + continue; + if(!nsec_verify_rrset(env, ve, rep->rrsets[i], kkey, reason)) { + verbose(VERB_ALGO, "NSEC for empty non-terminal " + "did not verify."); + return sec_status_bogus; + } + if(nsec_proves_nodata(rep->rrsets[i], qinfo, &wc)) { + verbose(VERB_ALGO, "NSEC for empty non-terminal " + "proved no DS."); + *proof_ttl = rrset_get_ttl(rep->rrsets[i]); + if(wc && dname_is_wild(rep->rrsets[i]->rk.dname)) + wc_nsec = rep->rrsets[i]; + valid_nsec = 1; + } + if(val_nsec_proves_name_error(rep->rrsets[i], qinfo->qname)) { + ce = nsec_closest_encloser(qinfo->qname, + rep->rrsets[i]); + } + } + if(wc && !ce) + valid_nsec = 0; + else if(wc && ce) { + /* ce and wc must match */ + if(query_dname_compare(wc, ce) != 0) + valid_nsec = 0; + else if(!wc_nsec) + valid_nsec = 0; + } + if(valid_nsec) { + if(wc) { + /* check if this is a delegation */ + *reason = "NSEC for wildcard does not prove absence of DS"; + return val_nsec_proves_no_ds(wc_nsec, qinfo); + } + /* valid nsec proves empty nonterminal */ + return sec_status_insecure; + } + + /* NSEC proof did not conlusively point to DS or no DS */ + return sec_status_unchecked; +} + +int nsec_proves_nodata(struct ub_packed_rrset_key* nsec, + struct query_info* qinfo, uint8_t** wc) +{ + log_assert(wc); + if(query_dname_compare(nsec->rk.dname, qinfo->qname) != 0) { + uint8_t* nm; + size_t ln; + + /* empty-non-terminal checking. + * Done before wildcard, because this is an exact match, + * and would prevent a wildcard from matching. */ + + /* If the nsec is proving that qname is an ENT, the nsec owner + * will be less than qname, and the next name will be a child + * domain of the qname. */ + if(!nsec_get_next(nsec, &nm, &ln)) + return 0; /* bad nsec */ + if(dname_strict_subdomain_c(nm, qinfo->qname) && + dname_canonical_compare(nsec->rk.dname, + qinfo->qname) < 0) { + return 1; /* proves ENT */ + } + + /* wildcard checking. */ + + /* If this is a wildcard NSEC, make sure that a) it was + * possible to have generated qname from the wildcard and + * b) the type map does not contain qtype. Note that this + * does NOT prove that this wildcard was the applicable + * wildcard. */ + if(dname_is_wild(nsec->rk.dname)) { + /* the purported closest encloser. */ + uint8_t* ce = nsec->rk.dname; + size_t ce_len = nsec->rk.dname_len; + dname_remove_label(&ce, &ce_len); + + /* The qname must be a strict subdomain of the + * closest encloser, for the wildcard to apply + */ + if(dname_strict_subdomain_c(qinfo->qname, ce)) { + /* here we have a matching NSEC for the qname, + * perform matching NSEC checks */ + if(nsec_has_type(nsec, LDNS_RR_TYPE_CNAME)) { + /* should have gotten the wildcard CNAME */ + return 0; + } + if(nsec_has_type(nsec, LDNS_RR_TYPE_NS) && + !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { + /* wrong parentside (wildcard) NSEC used */ + return 0; + } + if(nsec_has_type(nsec, qinfo->qtype)) { + return 0; + } + *wc = ce; + return 1; + } + } + + /* Otherwise, this NSEC does not prove ENT and is not a + * wildcard, so it does not prove NODATA. */ + return 0; + } + + /* If the qtype exists, then we should have gotten it. */ + if(nsec_has_type(nsec, qinfo->qtype)) { + return 0; + } + + /* if the name is a CNAME node, then we should have gotten the CNAME*/ + if(nsec_has_type(nsec, LDNS_RR_TYPE_CNAME)) { + return 0; + } + + /* If an NS set exists at this name, and NOT a SOA (so this is a + * zone cut, not a zone apex), then we should have gotten a + * referral (or we just got the wrong NSEC). + * The reverse of this check is used when qtype is DS, since that + * must use the NSEC from above the zone cut. */ + if(qinfo->qtype != LDNS_RR_TYPE_DS && + nsec_has_type(nsec, LDNS_RR_TYPE_NS) && + !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { + return 0; + } else if(qinfo->qtype == LDNS_RR_TYPE_DS && + nsec_has_type(nsec, LDNS_RR_TYPE_SOA) && + !dname_is_root(qinfo->qname)) { + return 0; + } + + return 1; +} + +int +val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec, uint8_t* qname) +{ + uint8_t* owner = nsec->rk.dname; + uint8_t* next; + size_t nlen; + if(!nsec_get_next(nsec, &next, &nlen)) + return 0; + + /* If NSEC owner == qname, then this NSEC proves that qname exists. */ + if(query_dname_compare(qname, owner) == 0) { + return 0; + } + + /* If NSEC is a parent of qname, we need to check the type map + * If the parent name has a DNAME or is a delegation point, then + * this NSEC is being misused. */ + if(dname_subdomain_c(qname, owner) && + (nsec_has_type(nsec, LDNS_RR_TYPE_DNAME) || + (nsec_has_type(nsec, LDNS_RR_TYPE_NS) + && !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) + )) { + return 0; + } + + if(query_dname_compare(owner, next) == 0) { + /* this nsec is the only nsec */ + /* zone.name NSEC zone.name, disproves everything else */ + /* but only for subdomains of that zone */ + if(dname_strict_subdomain_c(qname, next)) + return 1; + } + else if(dname_canonical_compare(owner, next) > 0) { + /* this is the last nsec, ....(bigger) NSEC zonename(smaller) */ + /* the names after the last (owner) name do not exist + * there are no names before the zone name in the zone + * but the qname must be a subdomain of the zone name(next). */ + if(dname_canonical_compare(owner, qname) < 0 && + dname_strict_subdomain_c(qname, next)) + return 1; + } else { + /* regular NSEC, (smaller) NSEC (larger) */ + if(dname_canonical_compare(owner, qname) < 0 && + dname_canonical_compare(qname, next) < 0) { + return 1; + } + } + return 0; +} + +int val_nsec_proves_insecuredelegation(struct ub_packed_rrset_key* nsec, + struct query_info* qinfo) +{ + if(nsec_has_type(nsec, LDNS_RR_TYPE_NS) && + !nsec_has_type(nsec, LDNS_RR_TYPE_DS) && + !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { + /* see if nsec signals an insecure delegation */ + if(qinfo->qtype == LDNS_RR_TYPE_DS) { + /* if type is DS and qname is equal to nsec, then it + * is an exact match nsec, result not insecure */ + if(dname_strict_subdomain_c(qinfo->qname, + nsec->rk.dname)) + return 1; + } else { + if(dname_subdomain_c(qinfo->qname, nsec->rk.dname)) + return 1; + } + } + return 0; +} + +uint8_t* +nsec_closest_encloser(uint8_t* qname, struct ub_packed_rrset_key* nsec) +{ + uint8_t* next; + size_t nlen; + uint8_t* common1, *common2; + if(!nsec_get_next(nsec, &next, &nlen)) + return NULL; + /* longest common with owner or next name */ + common1 = dname_get_shared_topdomain(nsec->rk.dname, qname); + common2 = dname_get_shared_topdomain(next, qname); + if(dname_count_labels(common1) > dname_count_labels(common2)) + return common1; + return common2; +} + +int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec, + struct query_info* qinf, uint8_t* wc) +{ + uint8_t* ce; + /* 1) prove that qname doesn't exist and + * 2) that the correct wildcard was used + * nsec has been verified already. */ + if(!val_nsec_proves_name_error(nsec, qinf->qname)) + return 0; + /* check wildcard name */ + ce = nsec_closest_encloser(qinf->qname, nsec); + if(!ce) + return 0; + if(query_dname_compare(wc, ce) != 0) { + return 0; + } + return 1; +} + +int +val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, + size_t qnamelen) +{ + /* Determine if a NSEC record proves the non-existence of a + * wildcard that could have produced qname. */ + int labs; + int i; + uint8_t* ce = nsec_closest_encloser(qname, nsec); + uint8_t* strip; + size_t striplen; + uint8_t buf[LDNS_MAX_DOMAINLEN+3]; + if(!ce) + return 0; + /* we can subtract the closest encloser count - since that is the + * largest shared topdomain with owner and next NSEC name, + * because the NSEC is no proof for names shorter than the owner + * and next names. */ + labs = dname_count_labels(qname) - dname_count_labels(ce); + + for(i=labs; i>0; i--) { + /* i is number of labels to strip off qname, prepend * wild */ + strip = qname; + striplen = qnamelen; + dname_remove_labels(&strip, &striplen, i); + if(striplen > LDNS_MAX_DOMAINLEN-2) + continue; /* too long to prepend wildcard */ + buf[0] = 1; + buf[1] = (uint8_t)'*'; + memmove(buf+2, strip, striplen); + if(val_nsec_proves_name_error(nsec, buf)) { + return 1; + } + } + return 0; +} + +/** + * Find shared topdomain that exists + */ +static void +dlv_topdomain(struct ub_packed_rrset_key* nsec, uint8_t* qname, + uint8_t** nm, size_t* nm_len) +{ + /* make sure reply is part of nm */ + /* take shared topdomain with left of NSEC. */ + + /* because, if empty nonterminal, then right is subdomain of qname. + * and any shared topdomain would be empty nonterminals. + * + * If nxdomain, then the right is bigger, and could have an + * interesting shared topdomain, but if it does have one, it is + * an empty nonterminal. An empty nonterminal shared with the left + * one. */ + int n; + uint8_t* common = dname_get_shared_topdomain(qname, nsec->rk.dname); + n = dname_count_labels(*nm) - dname_count_labels(common); + dname_remove_labels(nm, nm_len, n); +} + +int val_nsec_check_dlv(struct query_info* qinfo, + struct reply_info* rep, uint8_t** nm, size_t* nm_len) +{ + uint8_t* next; + size_t i, nlen; + int c; + /* we should now have a NOERROR/NODATA or NXDOMAIN message */ + if(rep->an_numrrsets != 0) { + return 0; + } + /* is this NOERROR ? */ + if(FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { + /* it can be a plain NSEC match - go up one more level. */ + /* or its an empty nonterminal - go up to nonempty level */ + for(i=0; i<rep->ns_numrrsets; i++) { + if(htons(rep->rrsets[i]->rk.type)!=LDNS_RR_TYPE_NSEC || + !nsec_get_next(rep->rrsets[i], &next, &nlen)) + continue; + c = dname_canonical_compare( + rep->rrsets[i]->rk.dname, qinfo->qname); + if(c == 0) { + /* plain match */ + if(nsec_has_type(rep->rrsets[i], + LDNS_RR_TYPE_DLV)) + return 0; + dname_remove_label(nm, nm_len); + return 1; + } else if(c < 0 && + dname_strict_subdomain_c(next, qinfo->qname)) { + /* ENT */ + dlv_topdomain(rep->rrsets[i], qinfo->qname, + nm, nm_len); + return 1; + } + } + return 0; + } + + /* is this NXDOMAIN ? */ + if(FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN) { + /* find the qname denial NSEC record. It can tell us + * a closest encloser name; or that we not need bother */ + for(i=0; i<rep->ns_numrrsets; i++) { + if(htons(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC) + continue; + if(val_nsec_proves_name_error(rep->rrsets[i], + qinfo->qname)) { + log_nametypeclass(VERB_ALGO, "topdomain on", + rep->rrsets[i]->rk.dname, + ntohs(rep->rrsets[i]->rk.type), 0); + dlv_topdomain(rep->rrsets[i], qinfo->qname, + nm, nm_len); + return 1; + } + } + return 0; + } + return 0; +} diff --git a/validator/val_nsec.h b/validator/val_nsec.h new file mode 100644 index 0000000000000..34f7f63b40efe --- /dev/null +++ b/validator/val_nsec.h @@ -0,0 +1,182 @@ +/* + * validator/val_nsec.h - validator NSEC denial of existance functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with NSEC checking, the different NSEC proofs + * for denial of existance, and proofs for presence of types. + */ + +#ifndef VALIDATOR_VAL_NSEC_H +#define VALIDATOR_VAL_NSEC_H +#include "util/data/packed_rrset.h" +struct val_env; +struct module_env; +struct ub_packed_rrset_key; +struct reply_info; +struct query_info; +struct key_entry_key; + +/** + * Check DS absence. + * There is a NODATA reply to a DS that needs checking. + * NSECs can prove this is not a delegation point, or sucessfully prove + * that there is no DS. Or this fails. + * + * @param env: module env for rrsig verification routines. + * @param ve: validator env for rrsig verification routines. + * @param qinfo: the DS queried for. + * @param rep: reply received. + * @param kkey: key entry to use for verification of signatures. + * @param proof_ttl: if secure, the TTL of how long this proof lasts. + * @param reason: string explaining why bogus. + * @return security status. + * SECURE: proved absence of DS. + * INSECURE: proved that this was not a delegation point. + * BOGUS: crypto bad, or no absence of DS proven. + * UNCHECKED: there was no way to prove anything (no NSECs, unknown algo). + */ +enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env, + struct val_env* ve, struct query_info* qinfo, + struct reply_info* rep, struct key_entry_key* kkey, + uint32_t* proof_ttl, char** reason); + +/** + * nsec typemap check, takes an NSEC-type bitmap as argument, checks for type. + * @param bitmap: pointer to the bitmap part of wireformat rdata. + * @param len: length of the bitmap, in bytes. + * @param type: the type (in host order) to check for. + * @return true if the type bit was set in the bitmap. false if not, or + * if the bitmap was malformed in some way. + */ +int nsecbitmap_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type); + +/** + * Check if type is present in the NSEC typemap + * @param nsec: the nsec RRset. + * If there are multiple RRs, then each must have the same typemap, + * since the typemap represents the types at this domain node. + * @param type: type to check for, host order. + * @return true if present + */ +int nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type); + +/** + * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also + * handle the empty non-terminal (ENT) case and partially handle the + * wildcard case. If the ownername of 'nsec' is a wildcard, the validator + * must still be provided proof that qname did not directly exist and that + * the wildcard is, in fact, *.closest_encloser. + * + * @param nsec: the nsec record to check against. + * @param qinfo: the query info. + * @param wc: if the nodata is proven for a wildcard match, the wildcard + * closest encloser is returned, else NULL (wc is unchanged). + * This closest encloser must then match the nameerror given for the + * nextcloser of qname. + * @return true if NSEC proves this. + */ +int nsec_proves_nodata(struct ub_packed_rrset_key* nsec, + struct query_info* qinfo, uint8_t** wc); + +/** + * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given + * qname. + * + * @param nsec: the nsec to check + * @param qname: what was queried. + * @return true if proven. + */ +int val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec, + uint8_t* qname); + +/** + * Determine if the given NSEC proves a positive wildcard response. + * @param nsec: the nsec to check + * @param qinf: what was queried. + * @param wc: wildcard (without *. label) + * @return true if proven. + */ +int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec, + struct query_info* qinf, uint8_t* wc); + +/** + * Determine closest encloser of a query name and the NSEC that covers it + * (and thus disproved it). + * A name error must have been proven already, otherwise this will be invalid. + * @param qname: the name queried for. + * @param nsec: the nsec RRset. + * @return closest encloser dname or NULL on error (bad nsec RRset). + */ +uint8_t* nsec_closest_encloser(uint8_t* qname, + struct ub_packed_rrset_key* nsec); + +/** + * Determine if the given NSEC proves that a wildcard match does not exist. + * + * @param nsec: the nsec RRset. + * @param qname: the name queried for. + * @param qnamelen: length of qname. + * @return true if proven. + */ +int val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, + size_t qnamelen); + +/** + * Determine the DLV result, what to do with NSEC DLV reply. + * @param qinfo: what was queried for. + * @param rep: the nonpositive reply. + * @param nm: dlv lookup name, to adjust for new lookup name (if needed). + * @param nm_len: length of lookup name. + * @return 0 on error, 1 if a higher point is found. + * If the higher point is above the dlv repo anchor, the qname does + * not exist. + */ +int val_nsec_check_dlv(struct query_info* qinfo, + struct reply_info* rep, uint8_t** nm, size_t* nm_len); + +/** + * Determine if an nsec proves an insecure delegation towards the qname. + * @param nsec: nsec rrset. + * @param qinfo: what was queries for. + * @return 0 if not, 1 if an NSEC that signals an insecure delegation to + * the qname. + */ +int val_nsec_proves_insecuredelegation(struct ub_packed_rrset_key* nsec, + struct query_info* qinfo); + +#endif /* VALIDATOR_VAL_NSEC_H */ diff --git a/validator/val_nsec3.c b/validator/val_nsec3.c new file mode 100644 index 0000000000000..a18e3ab31d068 --- /dev/null +++ b/validator/val_nsec3.c @@ -0,0 +1,1446 @@ +/* + * validator/val_nsec3.c - validator NSEC3 denial of existance functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with NSEC3 checking, the different NSEC3 proofs + * for denial of existance, and proofs for presence of types. + */ +#include "config.h" +#include <ctype.h> +#ifdef HAVE_OPENSSL_SSL_H +#include "openssl/ssl.h" +#endif +#include "validator/val_nsec3.h" +#include "validator/validator.h" +#include "validator/val_kentry.h" +#include "services/cache/rrset.h" +#include "util/regional.h" +#include "util/rbtree.h" +#include "util/module.h" +#include "util/net_help.h" +#include "util/data/packed_rrset.h" +#include "util/data/dname.h" +#include "util/data/msgreply.h" +/* we include nsec.h for the bitmap_has_type function */ +#include "validator/val_nsec.h" + +/** + * This function we get from ldns-compat or from base system + * it returns the number of data bytes stored at the target, or <0 on error. + */ +int ldns_b32_ntop_extended_hex(uint8_t const *src, size_t srclength, + char *target, size_t targsize); +/** + * This function we get from ldns-compat or from base system + * it returns the number of data bytes stored at the target, or <0 on error. + */ +int ldns_b32_pton_extended_hex(char const *src, size_t hashed_owner_str_len, + uint8_t *target, size_t targsize); + +/** + * Closest encloser (ce) proof results + * Contains the ce and the next-closer (nc) proof. + */ +struct ce_response { + /** the closest encloser name */ + uint8_t* ce; + /** length of ce */ + size_t ce_len; + /** NSEC3 record that proved ce. rrset */ + struct ub_packed_rrset_key* ce_rrset; + /** NSEC3 record that proved ce. rr number */ + int ce_rr; + /** NSEC3 record that proved nc. rrset */ + struct ub_packed_rrset_key* nc_rrset; + /** NSEC3 record that proved nc. rr*/ + int nc_rr; +}; + +/** + * Filter conditions for NSEC3 proof + * Used to iterate over the applicable NSEC3 RRs. + */ +struct nsec3_filter { + /** Zone name, only NSEC3 records for this zone are considered */ + uint8_t* zone; + /** length of the zonename */ + size_t zone_len; + /** the list of NSEC3s to filter; array */ + struct ub_packed_rrset_key** list; + /** number of rrsets in list */ + size_t num; + /** class of records for the NSEC3, only this class applies */ + uint16_t fclass; +}; + +/** return number of rrs in an rrset */ +static size_t +rrset_get_count(struct ub_packed_rrset_key* rrset) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + if(!d) return 0; + return d->count; +} + +/** return if nsec3 RR has unknown flags */ +static int +nsec3_unknown_flags(struct ub_packed_rrset_key* rrset, int r) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+2) + return 0; /* malformed */ + return (int)(d->rr_data[r][2+1] & NSEC3_UNKNOWN_FLAGS); +} + +int +nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+2) + return 0; /* malformed */ + return (int)(d->rr_data[r][2+1] & NSEC3_OPTOUT); +} + +/** return nsec3 RR algorithm */ +static int +nsec3_get_algo(struct ub_packed_rrset_key* rrset, int r) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+1) + return 0; /* malformed */ + return (int)(d->rr_data[r][2+0]); +} + +/** return if nsec3 RR has known algorithm */ +static int +nsec3_known_algo(struct ub_packed_rrset_key* rrset, int r) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+1) + return 0; /* malformed */ + switch(d->rr_data[r][2+0]) { + case NSEC3_HASH_SHA1: + return 1; + } + return 0; +} + +/** return nsec3 RR iteration count */ +static size_t +nsec3_get_iter(struct ub_packed_rrset_key* rrset, int r) +{ + uint16_t i; + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+4) + return 0; /* malformed */ + memmove(&i, d->rr_data[r]+2+2, sizeof(i)); + i = ntohs(i); + return (size_t)i; +} + +/** return nsec3 RR salt */ +static int +nsec3_get_salt(struct ub_packed_rrset_key* rrset, int r, + uint8_t** salt, size_t* saltlen) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+5) { + *salt = 0; + *saltlen = 0; + return 0; /* malformed */ + } + *saltlen = (size_t)d->rr_data[r][2+4]; + if(d->rr_len[r] < 2+5+(size_t)*saltlen) { + *salt = 0; + *saltlen = 0; + return 0; /* malformed */ + } + *salt = d->rr_data[r]+2+5; + return 1; +} + +int nsec3_get_params(struct ub_packed_rrset_key* rrset, int r, + int* algo, size_t* iter, uint8_t** salt, size_t* saltlen) +{ + if(!nsec3_known_algo(rrset, r) || nsec3_unknown_flags(rrset, r)) + return 0; + if(!nsec3_get_salt(rrset, r, salt, saltlen)) + return 0; + *algo = nsec3_get_algo(rrset, r); + *iter = nsec3_get_iter(rrset, r); + return 1; +} + +int +nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r, + uint8_t** next, size_t* nextlen) +{ + size_t saltlen; + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + if(d->rr_len[r] < 2+5) { + *next = 0; + *nextlen = 0; + return 0; /* malformed */ + } + saltlen = (size_t)d->rr_data[r][2+4]; + if(d->rr_len[r] < 2+5+saltlen+1) { + *next = 0; + *nextlen = 0; + return 0; /* malformed */ + } + *nextlen = (size_t)d->rr_data[r][2+5+saltlen]; + if(d->rr_len[r] < 2+5+saltlen+1+*nextlen) { + *next = 0; + *nextlen = 0; + return 0; /* malformed */ + } + *next = d->rr_data[r]+2+5+saltlen+1; + return 1; +} + +size_t nsec3_hash_to_b32(uint8_t* hash, size_t hashlen, uint8_t* zone, + size_t zonelen, uint8_t* buf, size_t max) +{ + /* write b32 of name, leave one for length */ + int ret; + if(max < hashlen*2+1) /* quick approx of b32, as if hexb16 */ + return 0; + ret = ldns_b32_ntop_extended_hex(hash, hashlen, (char*)buf+1, max-1); + if(ret < 1) + return 0; + buf[0] = (uint8_t)ret; /* length of b32 label */ + ret++; + if(max - ret < zonelen) + return 0; + memmove(buf+ret, zone, zonelen); + return zonelen+(size_t)ret; +} + +size_t nsec3_get_nextowner_b32(struct ub_packed_rrset_key* rrset, int r, + uint8_t* buf, size_t max) +{ + uint8_t* nm, *zone; + size_t nmlen, zonelen; + if(!nsec3_get_nextowner(rrset, r, &nm, &nmlen)) + return 0; + /* append zone name; the owner name must be <b32>.zone */ + zone = rrset->rk.dname; + zonelen = rrset->rk.dname_len; + dname_remove_label(&zone, &zonelen); + return nsec3_hash_to_b32(nm, nmlen, zone, zonelen, buf, max); +} + +int +nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type) +{ + uint8_t* bitmap; + size_t bitlen, skiplen; + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + log_assert(d && r < (int)d->count); + skiplen = 2+4; + /* skip salt */ + if(d->rr_len[r] < skiplen+1) + return 0; /* malformed, too short */ + skiplen += 1+(size_t)d->rr_data[r][skiplen]; + /* skip next hashed owner */ + if(d->rr_len[r] < skiplen+1) + return 0; /* malformed, too short */ + skiplen += 1+(size_t)d->rr_data[r][skiplen]; + if(d->rr_len[r] < skiplen) + return 0; /* malformed, too short */ + bitlen = d->rr_len[r] - skiplen; + bitmap = d->rr_data[r]+skiplen; + return nsecbitmap_has_type_rdata(bitmap, bitlen, type); +} + +/** + * Iterate through NSEC3 list, per RR + * This routine gives the next RR in the list (or sets rrset null). + * Usage: + * + * size_t rrsetnum; + * int rrnum; + * struct ub_packed_rrset_key* rrset; + * for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset; + * rrset=filter_next(filter, &rrsetnum, &rrnum)) + * do_stuff; + * + * Also filters out + * o unknown flag NSEC3s + * o unknown algorithm NSEC3s. + * @param filter: nsec3 filter structure. + * @param rrsetnum: in/out rrset number to look at. + * @param rrnum: in/out rr number in rrset to look at. + * @returns ptr to the next rrset (or NULL at end). + */ +static struct ub_packed_rrset_key* +filter_next(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum) +{ + size_t i; + int r; + uint8_t* nm; + size_t nmlen; + if(!filter->zone) /* empty list */ + return NULL; + for(i=*rrsetnum; i<filter->num; i++) { + /* see if RRset qualifies */ + if(ntohs(filter->list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 || + ntohs(filter->list[i]->rk.rrset_class) != + filter->fclass) + continue; + /* check RRset zone */ + nm = filter->list[i]->rk.dname; + nmlen = filter->list[i]->rk.dname_len; + dname_remove_label(&nm, &nmlen); + if(query_dname_compare(nm, filter->zone) != 0) + continue; + if(i == *rrsetnum) + r = (*rrnum) + 1; /* continue at next RR */ + else r = 0; /* new RRset start at first RR */ + for(; r < (int)rrset_get_count(filter->list[i]); r++) { + /* skip unknown flags, algo */ + if(nsec3_unknown_flags(filter->list[i], r) || + !nsec3_known_algo(filter->list[i], r)) + continue; + /* this one is a good target */ + *rrsetnum = i; + *rrnum = r; + return filter->list[i]; + } + } + return NULL; +} + +/** + * Start iterating over NSEC3 records. + * @param filter: the filter structure, must have been filter_init-ed. + * @param rrsetnum: can be undefined on call, inited. + * @param rrnum: can be undefined on call, inited. + * @return first rrset of an NSEC3, together with rrnum this points to + * the first RR to examine. Is NULL on empty list. + */ +static struct ub_packed_rrset_key* +filter_first(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum) +{ + *rrsetnum = 0; + *rrnum = -1; + return filter_next(filter, rrsetnum, rrnum); +} + +/** see if at least one RR is known (flags, algo) */ +static int +nsec3_rrset_has_known(struct ub_packed_rrset_key* s) +{ + int r; + for(r=0; r < (int)rrset_get_count(s); r++) { + if(!nsec3_unknown_flags(s, r) && nsec3_known_algo(s, r)) + return 1; + } + return 0; +} + +/** + * Initialize the filter structure. + * Finds the zone by looking at available NSEC3 records and best match. + * (skips the unknown flag and unknown algo NSEC3s). + * + * @param filter: nsec3 filter structure. + * @param list: list of rrsets, an array of them. + * @param num: number of rrsets in list. + * @param qinfo: + * query name to match a zone for. + * query type (if DS a higher zone must be chosen) + * qclass, to filter NSEC3s with. + */ +static void +filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list, + size_t num, struct query_info* qinfo) +{ + size_t i; + uint8_t* nm; + size_t nmlen; + filter->zone = NULL; + filter->zone_len = 0; + filter->list = list; + filter->num = num; + filter->fclass = qinfo->qclass; + for(i=0; i<num; i++) { + /* ignore other stuff in the list */ + if(ntohs(list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 || + ntohs(list[i]->rk.rrset_class) != qinfo->qclass) + continue; + /* skip unknown flags, algo */ + if(!nsec3_rrset_has_known(list[i])) + continue; + + /* since NSEC3s are base32.zonename, we can find the zone + * name by stripping off the first label of the record */ + nm = list[i]->rk.dname; + nmlen = list[i]->rk.dname_len; + dname_remove_label(&nm, &nmlen); + /* if we find a domain that can prove about the qname, + * and if this domain is closer to the qname */ + if(dname_subdomain_c(qinfo->qname, nm) && (!filter->zone || + dname_subdomain_c(nm, filter->zone))) { + /* for a type DS do not accept a zone equal to qname*/ + if(qinfo->qtype == LDNS_RR_TYPE_DS && + query_dname_compare(qinfo->qname, nm) == 0 && + !dname_is_root(qinfo->qname)) + continue; + filter->zone = nm; + filter->zone_len = nmlen; + } + } +} + +/** + * Find max iteration count using config settings and key size + * @param ve: validator environment with iteration count config settings. + * @param bits: key size + * @return max iteration count + */ +static size_t +get_max_iter(struct val_env* ve, size_t bits) +{ + int i; + log_assert(ve->nsec3_keyiter_count > 0); + /* round up to nearest config keysize, linear search, keep it small */ + for(i=0; i<ve->nsec3_keyiter_count; i++) { + if(bits <= ve->nsec3_keysize[i]) + return ve->nsec3_maxiter[i]; + } + /* else, use value for biggest key */ + return ve->nsec3_maxiter[ve->nsec3_keyiter_count-1]; +} + +/** + * Determine if any of the NSEC3 rrs iteration count is too high, from key. + * @param ve: validator environment with iteration count config settings. + * @param filter: what NSEC3s to loop over. + * @param kkey: key entry used for verification; used for iteration counts. + * @return 1 if some nsec3s are above the max iteration count. + */ +static int +nsec3_iteration_count_high(struct val_env* ve, struct nsec3_filter* filter, + struct key_entry_key* kkey) +{ + size_t rrsetnum; + int rrnum; + struct ub_packed_rrset_key* rrset; + /* first determine the max number of iterations */ + size_t bits = key_entry_keysize(kkey); + size_t max_iter = get_max_iter(ve, bits); + verbose(VERB_ALGO, "nsec3: keysize %d bits, max iterations %d", + (int)bits, (int)max_iter); + + for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset; + rrset=filter_next(filter, &rrsetnum, &rrnum)) { + if(nsec3_get_iter(rrset, rrnum) > max_iter) + return 1; + } + return 0; +} + +/* nsec3_cache_compare for rbtree */ +int +nsec3_hash_cmp(const void* c1, const void* c2) +{ + struct nsec3_cached_hash* h1 = (struct nsec3_cached_hash*)c1; + struct nsec3_cached_hash* h2 = (struct nsec3_cached_hash*)c2; + uint8_t* s1, *s2; + size_t s1len, s2len; + int c = query_dname_compare(h1->dname, h2->dname); + if(c != 0) + return c; + /* compare parameters */ + /* if both malformed, its equal, robustness */ + if(nsec3_get_algo(h1->nsec3, h1->rr) != + nsec3_get_algo(h2->nsec3, h2->rr)) { + if(nsec3_get_algo(h1->nsec3, h1->rr) < + nsec3_get_algo(h2->nsec3, h2->rr)) + return -1; + return 1; + } + if(nsec3_get_iter(h1->nsec3, h1->rr) != + nsec3_get_iter(h2->nsec3, h2->rr)) { + if(nsec3_get_iter(h1->nsec3, h1->rr) < + nsec3_get_iter(h2->nsec3, h2->rr)) + return -1; + return 1; + } + (void)nsec3_get_salt(h1->nsec3, h1->rr, &s1, &s1len); + (void)nsec3_get_salt(h2->nsec3, h2->rr, &s2, &s2len); + if(s1len != s2len) { + if(s1len < s2len) + return -1; + return 1; + } + return memcmp(s1, s2, s1len); +} + +size_t +nsec3_get_hashed(ldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo, + size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max) +{ + size_t i, hash_len; + /* prepare buffer for first iteration */ + ldns_buffer_clear(buf); + ldns_buffer_write(buf, nm, nmlen); + query_dname_tolower(ldns_buffer_begin(buf)); + ldns_buffer_write(buf, salt, saltlen); + ldns_buffer_flip(buf); + switch(algo) { +#ifdef HAVE_EVP_SHA1 + case NSEC3_HASH_SHA1: + hash_len = SHA_DIGEST_LENGTH; + if(hash_len > max) + return 0; + (void)SHA1((unsigned char*)ldns_buffer_begin(buf), + (unsigned long)ldns_buffer_limit(buf), + (unsigned char*)res); + for(i=0; i<iter; i++) { + ldns_buffer_clear(buf); + ldns_buffer_write(buf, res, hash_len); + ldns_buffer_write(buf, salt, saltlen); + ldns_buffer_flip(buf); + (void)SHA1( + (unsigned char*)ldns_buffer_begin(buf), + (unsigned long)ldns_buffer_limit(buf), + (unsigned char*)res); + } + break; +#endif /* HAVE_EVP_SHA1 */ + default: + log_err("nsec3 hash of unknown algo %d", algo); + return 0; + } + return hash_len; +} + +/** perform hash of name */ +static int +nsec3_calc_hash(struct regional* region, ldns_buffer* buf, + struct nsec3_cached_hash* c) +{ + int algo = nsec3_get_algo(c->nsec3, c->rr); + size_t iter = nsec3_get_iter(c->nsec3, c->rr); + uint8_t* salt; + size_t saltlen, i; + if(!nsec3_get_salt(c->nsec3, c->rr, &salt, &saltlen)) + return -1; + /* prepare buffer for first iteration */ + ldns_buffer_clear(buf); + ldns_buffer_write(buf, c->dname, c->dname_len); + query_dname_tolower(ldns_buffer_begin(buf)); + ldns_buffer_write(buf, salt, saltlen); + ldns_buffer_flip(buf); + switch(algo) { +#ifdef HAVE_EVP_SHA1 + case NSEC3_HASH_SHA1: + c->hash_len = SHA_DIGEST_LENGTH; + c->hash = (uint8_t*)regional_alloc(region, + c->hash_len); + if(!c->hash) + return 0; + (void)SHA1((unsigned char*)ldns_buffer_begin(buf), + (unsigned long)ldns_buffer_limit(buf), + (unsigned char*)c->hash); + for(i=0; i<iter; i++) { + ldns_buffer_clear(buf); + ldns_buffer_write(buf, c->hash, c->hash_len); + ldns_buffer_write(buf, salt, saltlen); + ldns_buffer_flip(buf); + (void)SHA1( + (unsigned char*)ldns_buffer_begin(buf), + (unsigned long)ldns_buffer_limit(buf), + (unsigned char*)c->hash); + } + break; +#endif /* HAVE_EVP_SHA1 */ + default: + log_err("nsec3 hash of unknown algo %d", algo); + return -1; + } + return 1; +} + +/** perform b32 encoding of hash */ +static int +nsec3_calc_b32(struct regional* region, ldns_buffer* buf, + struct nsec3_cached_hash* c) +{ + int r; + ldns_buffer_clear(buf); + r = ldns_b32_ntop_extended_hex(c->hash, c->hash_len, + (char*)ldns_buffer_begin(buf), ldns_buffer_limit(buf)); + if(r < 1) { + log_err("b32_ntop_extended_hex: error in encoding: %d", r); + return 0; + } + c->b32_len = (size_t)r; + c->b32 = regional_alloc_init(region, ldns_buffer_begin(buf), + c->b32_len); + if(!c->b32) + return 0; + return 1; +} + +int +nsec3_hash_name(rbtree_t* table, struct regional* region, ldns_buffer* buf, + struct ub_packed_rrset_key* nsec3, int rr, uint8_t* dname, + size_t dname_len, struct nsec3_cached_hash** hash) +{ + struct nsec3_cached_hash* c; + struct nsec3_cached_hash looki; +#ifdef UNBOUND_DEBUG + rbnode_t* n; +#endif + int r; + looki.node.key = &looki; + looki.nsec3 = nsec3; + looki.rr = rr; + looki.dname = dname; + looki.dname_len = dname_len; + /* lookup first in cache */ + c = (struct nsec3_cached_hash*)rbtree_search(table, &looki); + if(c) { + *hash = c; + return 1; + } + /* create a new entry */ + c = (struct nsec3_cached_hash*)regional_alloc(region, sizeof(*c)); + if(!c) return 0; + c->node.key = c; + c->nsec3 = nsec3; + c->rr = rr; + c->dname = dname; + c->dname_len = dname_len; + r = nsec3_calc_hash(region, buf, c); + if(r != 1) + return r; + r = nsec3_calc_b32(region, buf, c); + if(r != 1) + return r; +#ifdef UNBOUND_DEBUG + n = +#endif + rbtree_insert(table, &c->node); + log_assert(n); /* cannot be duplicate, just did lookup */ + *hash = c; + return 1; +} + +/** + * compare a label lowercased + */ +static int +label_compare_lower(uint8_t* lab1, uint8_t* lab2, size_t lablen) +{ + size_t i; + for(i=0; i<lablen; i++) { + if(tolower((int)*lab1) != tolower((int)*lab2)) { + if(tolower((int)*lab1) < tolower((int)*lab2)) + return -1; + return 1; + } + lab1++; + lab2++; + } + return 0; +} + +/** + * Compare a hashed name with the owner name of an NSEC3 RRset. + * @param flt: filter with zone name. + * @param hash: the hashed name. + * @param s: rrset with owner name. + * @return true if matches exactly, false if not. + */ +static int +nsec3_hash_matches_owner(struct nsec3_filter* flt, + struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* s) +{ + uint8_t* nm = s->rk.dname; + /* compare, does hash of name based on params in this NSEC3 + * match the owner name of this NSEC3? + * name must be: <hashlength>base32 . zone name + * so; first label must not be root label (not zero length), + * and match the b32 encoded hash length, + * and the label content match the b32 encoded hash + * and the rest must be the zone name. + */ + if(hash->b32_len != 0 && (size_t)nm[0] == hash->b32_len && + label_compare_lower(nm+1, hash->b32, hash->b32_len) == 0 && + query_dname_compare(nm+(size_t)nm[0]+1, flt->zone) == 0) { + return 1; + } + return 0; +} + +/** + * Find matching NSEC3 + * Find the NSEC3Record that matches a hash of a name. + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param nm: name to look for. + * @param nmlen: length of name. + * @param rrset: nsec3 that matches is returned here. + * @param rr: rr number in nsec3 rrset that matches. + * @return true if a matching NSEC3 is found, false if not. + */ +static int +find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, uint8_t* nm, size_t nmlen, + struct ub_packed_rrset_key** rrset, int* rr) +{ + size_t i_rs; + int i_rr; + struct ub_packed_rrset_key* s; + struct nsec3_cached_hash* hash; + int r; + + /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ + for(s=filter_first(flt, &i_rs, &i_rr); s; + s=filter_next(flt, &i_rs, &i_rr)) { + /* get name hashed for this NSEC3 RR */ + r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer, + s, i_rr, nm, nmlen, &hash); + if(r == 0) { + log_err("nsec3: malloc failure"); + break; /* alloc failure */ + } else if(r < 0) + continue; /* malformed NSEC3 */ + else if(nsec3_hash_matches_owner(flt, hash, s)) { + *rrset = s; /* rrset with this name */ + *rr = i_rr; /* matches hash with these parameters */ + return 1; + } + } + *rrset = NULL; + *rr = 0; + return 0; +} + +int +nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash, + struct ub_packed_rrset_key* rrset, int rr, ldns_buffer* buf) +{ + uint8_t* next, *owner; + size_t nextlen; + int len; + if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen)) + return 0; /* malformed RR proves nothing */ + + /* check the owner name is a hashed value . apex + * base32 encoded values must have equal length. + * hash_value and next hash value must have equal length. */ + if(nextlen != hash->hash_len || hash->hash_len==0||hash->b32_len==0|| + (size_t)*rrset->rk.dname != hash->b32_len || + query_dname_compare(rrset->rk.dname+1+ + (size_t)*rrset->rk.dname, zone) != 0) + return 0; /* bad lengths or owner name */ + + /* This is the "normal case: owner < next and owner < hash < next */ + if(label_compare_lower(rrset->rk.dname+1, hash->b32, + hash->b32_len) < 0 && + memcmp(hash->hash, next, nextlen) < 0) + return 1; + + /* convert owner name from text to binary */ + ldns_buffer_clear(buf); + owner = ldns_buffer_begin(buf); + len = ldns_b32_pton_extended_hex((char*)rrset->rk.dname+1, + hash->b32_len, owner, ldns_buffer_limit(buf)); + if(len<1) + return 0; /* bad owner name in some way */ + if((size_t)len != hash->hash_len || (size_t)len != nextlen) + return 0; /* wrong length */ + + /* this is the end of zone case: next <= owner && + * (hash > owner || hash < next) + * this also covers the only-apex case of next==owner. + */ + if(memcmp(next, owner, nextlen) <= 0 && + ( memcmp(hash->hash, owner, nextlen) > 0 || + memcmp(hash->hash, next, nextlen) < 0)) { + return 1; + } + return 0; +} + +/** + * findCoveringNSEC3 + * Given a name, find a covering NSEC3 from among a list of NSEC3s. + * + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param nm: name to check if covered. + * @param nmlen: length of name. + * @param rrset: covering NSEC3 rrset is returned here. + * @param rr: rr of cover is returned here. + * @return true if a covering NSEC3 is found, false if not. + */ +static int +find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, uint8_t* nm, size_t nmlen, + struct ub_packed_rrset_key** rrset, int* rr) +{ + size_t i_rs; + int i_rr; + struct ub_packed_rrset_key* s; + struct nsec3_cached_hash* hash; + int r; + + /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ + for(s=filter_first(flt, &i_rs, &i_rr); s; + s=filter_next(flt, &i_rs, &i_rr)) { + /* get name hashed for this NSEC3 RR */ + r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer, + s, i_rr, nm, nmlen, &hash); + if(r == 0) { + log_err("nsec3: malloc failure"); + break; /* alloc failure */ + } else if(r < 0) + continue; /* malformed NSEC3 */ + else if(nsec3_covers(flt->zone, hash, s, i_rr, + env->scratch_buffer)) { + *rrset = s; /* rrset with this name */ + *rr = i_rr; /* covers hash with these parameters */ + return 1; + } + } + *rrset = NULL; + *rr = 0; + return 0; +} + +/** + * findClosestEncloser + * Given a name and a list of NSEC3s, find the candidate closest encloser. + * This will be the first ancestor of 'name' (including itself) to have a + * matching NSEC3 RR. + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param qinfo: query that is verified for. + * @param ce: closest encloser information is returned in here. + * @return true if a closest encloser candidate is found, false if not. + */ +static int +nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo, struct ce_response* ce) +{ + uint8_t* nm = qinfo->qname; + size_t nmlen = qinfo->qname_len; + + /* This scans from longest name to shortest, so the first match + * we find is the only viable candidate. */ + + /* (David:) FIXME: modify so that the NSEC3 matching the zone apex need + * not be present. (Mark Andrews idea). + * (Wouter:) But make sure you check for DNAME bit in zone apex, + * if the NSEC3 you find is the only NSEC3 in the zone, then this + * may be the case. */ + + while(dname_subdomain_c(nm, flt->zone)) { + if(find_matching_nsec3(env, flt, ct, nm, nmlen, + &ce->ce_rrset, &ce->ce_rr)) { + ce->ce = nm; + ce->ce_len = nmlen; + return 1; + } + dname_remove_label(&nm, &nmlen); + } + return 0; +} + +/** + * Given a qname and its proven closest encloser, calculate the "next + * closest" name. Basically, this is the name that is one label longer than + * the closest encloser that is still a subdomain of qname. + * + * @param qname: query name. + * @param qnamelen: length of qname. + * @param ce: closest encloser + * @param nm: result name. + * @param nmlen: length of nm. + */ +static void +next_closer(uint8_t* qname, size_t qnamelen, uint8_t* ce, + uint8_t** nm, size_t* nmlen) +{ + int strip = dname_count_labels(qname) - dname_count_labels(ce) -1; + *nm = qname; + *nmlen = qnamelen; + if(strip>0) + dname_remove_labels(nm, nmlen, strip); +} + +/** + * proveClosestEncloser + * Given a List of nsec3 RRs, find and prove the closest encloser to qname. + * @param env: module environment with temporary region and buffer. + * @param flt: the NSEC3 RR filter, contains zone name and RRs. + * @param ct: cached hashes table. + * @param qinfo: query that is verified for. + * @param prove_does_not_exist: If true, then if the closest encloser + * turns out to be qname, then null is returned. + * If set true, and the return value is true, then you can be + * certain that the ce.nc_rrset and ce.nc_rr are set properly. + * @param ce: closest encloser information is returned in here. + * @return bogus if no closest encloser could be proven. + * secure if a closest encloser could be proven, ce is set. + * insecure if the closest-encloser candidate turns out to prove + * that an insecure delegation exists above the qname. + */ +static enum sec_status +nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo, int prove_does_not_exist, + struct ce_response* ce) +{ + uint8_t* nc; + size_t nc_len; + /* robust: clean out ce, in case it gets abused later */ + memset(ce, 0, sizeof(*ce)); + + if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " + "not find a candidate for the closest encloser."); + return sec_status_bogus; + } + log_nametypeclass(VERB_ALGO, "ce candidate", ce->ce, 0, 0); + + if(query_dname_compare(ce->ce, qinfo->qname) == 0) { + if(prove_does_not_exist) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: " + "proved that qname existed, bad"); + return sec_status_bogus; + } + /* otherwise, we need to nothing else to prove that qname + * is its own closest encloser. */ + return sec_status_secure; + } + + /* If the closest encloser is actually a delegation, then the + * response should have been a referral. If it is a DNAME, then + * it should have been a DNAME response. */ + if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_NS) && + !nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_SOA)) { + if(!nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DS)) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: " + "closest encloser is insecure delegation"); + return sec_status_insecure; + } + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " + "encloser was a delegation, bad"); + return sec_status_bogus; + } + if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DNAME)) { + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " + "encloser was a DNAME, bad"); + return sec_status_bogus; + } + + /* Otherwise, we need to show that the next closer name is covered. */ + next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len); + if(!find_covering_nsec3(env, flt, ct, nc, nc_len, + &ce->nc_rrset, &ce->nc_rr)) { + verbose(VERB_ALGO, "nsec3: Could not find proof that the " + "candidate encloser was the closest encloser"); + return sec_status_bogus; + } + return sec_status_secure; +} + +/** allocate a wildcard for the closest encloser */ +static uint8_t* +nsec3_ce_wildcard(struct regional* region, uint8_t* ce, size_t celen, + size_t* len) +{ + uint8_t* nm; + if(celen > LDNS_MAX_DOMAINLEN - 2) + return 0; /* too long */ + nm = (uint8_t*)regional_alloc(region, celen+2); + if(!nm) { + log_err("nsec3 wildcard: out of memory"); + return 0; /* alloc failure */ + } + nm[0] = 1; + nm[1] = (uint8_t)'*'; /* wildcard label */ + memmove(nm+2, ce, celen); + *len = celen+2; + return nm; +} + +/** Do the name error proof */ +static enum sec_status +nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo) +{ + struct ce_response ce; + uint8_t* wc; + size_t wclen; + struct ub_packed_rrset_key* wc_rrset; + int wc_rr; + enum sec_status sec; + + /* First locate and prove the closest encloser to qname. We will + * use the variant that fails if the closest encloser turns out + * to be qname. */ + sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); + if(sec != sec_status_secure) { + if(sec == sec_status_bogus) + verbose(VERB_ALGO, "nsec3 nameerror proof: failed " + "to prove a closest encloser"); + else verbose(VERB_ALGO, "nsec3 nameerror proof: closest " + "nsec3 is an insecure delegation"); + return sec; + } + log_nametypeclass(VERB_ALGO, "nsec3 namerror: proven ce=", ce.ce,0,0); + + /* At this point, we know that qname does not exist. Now we need + * to prove that the wildcard does not exist. */ + log_assert(ce.ce); + wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); + if(!wc || !find_covering_nsec3(env, flt, ct, wc, wclen, + &wc_rrset, &wc_rr)) { + verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " + "that the applicable wildcard did not exist."); + return sec_status_bogus; + } + + if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { + verbose(VERB_ALGO, "nsec3 nameerror proof: nc has optout"); + return sec_status_insecure; + } + return sec_status_secure; +} + +enum sec_status +nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey) +{ + rbtree_t ct; + struct nsec3_filter flt; + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ + rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", + flt.zone, 0, 0); + return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo); +} + +/* + * No code to handle qtype=NSEC3 specially. + * This existed in early drafts, but was later (-05) removed. + */ + +/** Do the nodata proof */ +static enum sec_status +nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, + rbtree_t* ct, struct query_info* qinfo) +{ + struct ce_response ce; + uint8_t* wc; + size_t wclen; + struct ub_packed_rrset_key* rrset; + int rr; + enum sec_status sec; + + if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len, + &rrset, &rr)) { + /* cases 1 and 2 */ + if(nsec3_has_type(rrset, rr, qinfo->qtype)) { + verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " + "proved that type existed, bogus"); + return sec_status_bogus; + } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { + verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " + "proved that a CNAME existed, bogus"); + return sec_status_bogus; + } + + /* + * If type DS: filter_init zone find already found a parent + * zone, so this nsec3 is from a parent zone. + * o can be not a delegation (unusual query for normal name, + * no DS anyway, but we can verify that). + * o can be a delegation (which is the usual DS check). + * o may not have the SOA bit set (only the top of the + * zone, which must have been above the name, has that). + * Except for the root; which is checked by itself. + * + * If not type DS: matching nsec3 must not be a delegation. + */ + if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 + && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA && + !dname_is_root(qinfo->qname))) { + verbose(VERB_ALGO, "proveNodata: apex NSEC3 " + "abused for no DS proof, bogus"); + return sec_status_bogus; + } else if(qinfo->qtype != LDNS_RR_TYPE_DS && + nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && + !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { + if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) { + verbose(VERB_ALGO, "proveNodata: matching " + "NSEC3 is insecure delegation"); + return sec_status_insecure; + } + verbose(VERB_ALGO, "proveNodata: matching " + "NSEC3 is a delegation, bogus"); + return sec_status_bogus; + } + return sec_status_secure; + } + + /* For cases 3 - 5, we need the proven closest encloser, and it + * can't match qname. Although, at this point, we know that it + * won't since we just checked that. */ + sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); + if(sec == sec_status_bogus) { + verbose(VERB_ALGO, "proveNodata: did not match qname, " + "nor found a proven closest encloser."); + return sec_status_bogus; + } else if(sec==sec_status_insecure && qinfo->qtype!=LDNS_RR_TYPE_DS){ + verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure " + "delegation."); + return sec_status_insecure; + } + + /* Case 3: removed */ + + /* Case 4: */ + log_assert(ce.ce); + wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); + if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) { + /* found wildcard */ + if(nsec3_has_type(rrset, rr, qinfo->qtype)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wildcard had qtype, bogus"); + return sec_status_bogus; + } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wildcard had a CNAME, bogus"); + return sec_status_bogus; + } + if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 + && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wildcard for no DS proof has a SOA, bogus"); + return sec_status_bogus; + } else if(qinfo->qtype != LDNS_RR_TYPE_DS && + nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && + !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wilcard is a delegation, bogus"); + return sec_status_bogus; + } + /* everything is peachy keen, except for optout spans */ + if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " + "wildcard is in optout range, insecure"); + return sec_status_insecure; + } + return sec_status_secure; + } + + /* Case 5: */ + /* Due to forwarders, cnames, and other collating effects, we + * can see the ordinary unsigned data from a zone beneath an + * insecure delegation under an optout here */ + if(!ce.nc_rrset) { + verbose(VERB_ALGO, "nsec3 nodata proof: no next closer nsec3"); + return sec_status_bogus; + } + + /* We need to make sure that the covering NSEC3 is opt-out. */ + log_assert(ce.nc_rrset); + if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { + if(qinfo->qtype == LDNS_RR_TYPE_DS) + verbose(VERB_ALGO, "proveNodata: covering NSEC3 was not " + "opt-out in an opt-out DS NOERROR/NODATA case."); + else verbose(VERB_ALGO, "proveNodata: could not find matching " + "NSEC3, nor matching wildcard, nor optout NSEC3 " + "-- no more options, bogus."); + return sec_status_bogus; + } + /* RFC5155 section 9.2: if nc has optout then no AD flag set */ + return sec_status_insecure; +} + +enum sec_status +nsec3_prove_nodata(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey) +{ + rbtree_t ct; + struct nsec3_filter flt; + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ + rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + return nsec3_do_prove_nodata(env, &flt, &ct, qinfo); +} + +enum sec_status +nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc) +{ + rbtree_t ct; + struct nsec3_filter flt; + struct ce_response ce; + uint8_t* nc; + size_t nc_len; + size_t wclen; + (void)dname_count_size_labels(wc, &wclen); + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ + rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + + /* We know what the (purported) closest encloser is by just + * looking at the supposed generating wildcard. + * The *. has already been removed from the wc name. + */ + memset(&ce, 0, sizeof(ce)); + ce.ce = wc; + ce.ce_len = wclen; + + /* Now we still need to prove that the original data did not exist. + * Otherwise, we need to show that the next closer name is covered. */ + next_closer(qinfo->qname, qinfo->qname_len, ce.ce, &nc, &nc_len); + if(!find_covering_nsec3(env, &flt, &ct, nc, nc_len, + &ce.nc_rrset, &ce.nc_rr)) { + verbose(VERB_ALGO, "proveWildcard: did not find a covering " + "NSEC3 that covered the next closer name."); + return sec_status_bogus; + } + if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { + verbose(VERB_ALGO, "proveWildcard: NSEC3 optout"); + return sec_status_insecure; + } + return sec_status_secure; +} + +/** test if list is all secure */ +static int +list_is_secure(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct key_entry_key* kkey, char** reason) +{ + struct packed_rrset_data* d; + size_t i; + for(i=0; i<num; i++) { + d = (struct packed_rrset_data*)list[i]->entry.data; + if(list[i]->rk.type != htons(LDNS_RR_TYPE_NSEC3)) + continue; + if(d->security == sec_status_secure) + continue; + rrset_check_sec_status(env->rrset_cache, list[i], *env->now); + if(d->security == sec_status_secure) + continue; + d->security = val_verify_rrset_entry(env, ve, list[i], kkey, + reason); + if(d->security != sec_status_secure) { + verbose(VERB_ALGO, "NSEC3 did not verify"); + return 0; + } + rrset_update_sec_status(env->rrset_cache, list[i], *env->now); + } + return 1; +} + +enum sec_status +nsec3_prove_nods(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, char** reason) +{ + rbtree_t ct; + struct nsec3_filter flt; + struct ce_response ce; + struct ub_packed_rrset_key* rrset; + int rr; + log_assert(qinfo->qtype == LDNS_RR_TYPE_DS); + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) { + *reason = "no valid NSEC3s"; + return sec_status_bogus; /* no valid NSEC3s, bogus */ + } + if(!list_is_secure(env, ve, list, num, kkey, reason)) + return sec_status_bogus; /* not all NSEC3 records secure */ + rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) { + *reason = "no NSEC3 records"; + return sec_status_bogus; /* no RRs */ + } + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + + /* Look for a matching NSEC3 to qname -- this is the normal + * NODATA case. */ + if(find_matching_nsec3(env, &flt, &ct, qinfo->qname, qinfo->qname_len, + &rrset, &rr)) { + /* If the matching NSEC3 has the SOA bit set, it is from + * the wrong zone (the child instead of the parent). If + * it has the DS bit set, then we were lied to. */ + if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA) && + qinfo->qname_len != 1) { + verbose(VERB_ALGO, "nsec3 provenods: NSEC3 is from" + " child zone, bogus"); + *reason = "NSEC3 from child zone"; + return sec_status_bogus; + } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) { + verbose(VERB_ALGO, "nsec3 provenods: NSEC3 has qtype" + " DS, bogus"); + *reason = "NSEC3 has DS in bitmap"; + return sec_status_bogus; + } + /* If the NSEC3 RR doesn't have the NS bit set, then + * this wasn't a delegation point. */ + if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS)) + return sec_status_indeterminate; + /* Otherwise, this proves no DS. */ + return sec_status_secure; + } + + /* Otherwise, we are probably in the opt-out case. */ + if(nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce) + != sec_status_secure) { + /* an insecure delegation *above* the qname does not prove + * anything about this qname exactly, and bogus is bogus */ + verbose(VERB_ALGO, "nsec3 provenods: did not match qname, " + "nor found a proven closest encloser."); + *reason = "no NSEC3 closest encloser"; + return sec_status_bogus; + } + + /* robust extra check */ + if(!ce.nc_rrset) { + verbose(VERB_ALGO, "nsec3 nods proof: no next closer nsec3"); + *reason = "no NSEC3 next closer"; + return sec_status_bogus; + } + + /* we had the closest encloser proof, then we need to check that the + * covering NSEC3 was opt-out -- the proveClosestEncloser step already + * checked to see if the closest encloser was a delegation or DNAME. + */ + log_assert(ce.nc_rrset); + if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { + verbose(VERB_ALGO, "nsec3 provenods: covering NSEC3 was not " + "opt-out in an opt-out DS NOERROR/NODATA case."); + *reason = "covering NSEC3 was not opt-out in an opt-out " + "DS NOERROR/NODATA case"; + return sec_status_bogus; + } + /* RFC5155 section 9.2: if nc has optout then no AD flag set */ + return sec_status_insecure; +} + +enum sec_status +nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, int* nodata) +{ + enum sec_status sec, secnx; + rbtree_t ct; + struct nsec3_filter flt; + *nodata = 0; + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ + rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ + + /* try nxdomain and nodata after another, while keeping the + * hash cache intact */ + + secnx = nsec3_do_prove_nameerror(env, &flt, &ct, qinfo); + if(secnx==sec_status_secure) + return sec_status_secure; + sec = nsec3_do_prove_nodata(env, &flt, &ct, qinfo); + if(sec==sec_status_secure) { + *nodata = 1; + } else if(sec == sec_status_insecure) { + *nodata = 1; + } else if(secnx == sec_status_insecure) { + sec = sec_status_insecure; + } + return sec; +} diff --git a/validator/val_nsec3.h b/validator/val_nsec3.h new file mode 100644 index 0000000000000..ae4326daffb62 --- /dev/null +++ b/validator/val_nsec3.h @@ -0,0 +1,378 @@ +/* + * validator/val_nsec3.h - validator NSEC3 denial of existance functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with NSEC3 checking, the different NSEC3 proofs + * for denial of existance, and proofs for presence of types. + * + * NSEC3 + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Hash Alg. | Flags | Iterations | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Salt Length | Salt / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Hash Length | Next Hashed Owner Name / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * / Type Bit Maps / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * NSEC3PARAM + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Hash Alg. | Flags | Iterations | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Salt Length | Salt / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +#ifndef VALIDATOR_VAL_NSEC3_H +#define VALIDATOR_VAL_NSEC3_H +#include "util/rbtree.h" +#include "util/data/packed_rrset.h" +struct val_env; +struct regional; +struct module_env; +struct ub_packed_rrset_key; +struct reply_info; +struct query_info; +struct key_entry_key; + +/** + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * | |O| + * +-+-+-+-+-+-+-+-+ + * The OPT-OUT bit in the NSEC3 flags field. + * If enabled, there can be zero or more unsigned delegations in the span. + * If disabled, there are zero unsigned delegations in the span. + */ +#define NSEC3_OPTOUT 0x01 +/** + * The unknown flags in the NSEC3 flags field. + * They must be zero, or the NSEC3 is ignored. + */ +#define NSEC3_UNKNOWN_FLAGS 0xFE + +/** The SHA1 hash algorithm for NSEC3 */ +#define NSEC3_HASH_SHA1 0x01 + +/** + * Determine if the set of NSEC3 records provided with a response prove NAME + * ERROR. This means that the NSEC3s prove a) the closest encloser exists, + * b) the direct child of the closest encloser towards qname doesn't exist, + * and c) *.closest encloser does not exist. + * + * @param env: module environment with temporary region and buffer. + * @param ve: validator environment, with iteration count settings. + * @param list: array of RRsets, some of which are NSEC3s. + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. + * @return: + * sec_status SECURE of the Name Error is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey); + +/** + * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA + * status. There are a number of different variants to this: + * + * 1) Normal NODATA -- qname is matched to an NSEC3 record, type is not + * present. + * + * 2) ENT NODATA -- because there must be NSEC3 record for + * empty-non-terminals, this is the same as #1. + * + * 3) NSEC3 ownername NODATA -- qname matched an existing, lone NSEC3 + * ownername, but qtype was not NSEC3. NOTE: as of nsec-05, this case no + * longer exists. + * + * 4) Wildcard NODATA -- A wildcard matched the name, but not the type. + * + * 5) Opt-In DS NODATA -- the qname is covered by an opt-in span and qtype == + * DS. (or maybe some future record with the same parent-side-only property) + * + * @param env: module environment with temporary region and buffer. + * @param ve: validator environment, with iteration count settings. + * @param list: array of RRsets, some of which are NSEC3s. + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_nodata(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey); + + +/** + * Prove that a positive wildcard match was appropriate (no direct match + * RRset). + * + * @param env: module environment with temporary region and buffer. + * @param ve: validator environment, with iteration count settings. + * @param list: array of RRsets, some of which are NSEC3s. + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. + * @param wc: The purported wildcard that matched. This is the wildcard name + * as *.wildcard.name., with the *. label already removed. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc); + +/** + * Prove that a DS response either had no DS, or wasn't a delegation point. + * + * Fundamentally there are two cases here: normal NODATA and Opt-In NODATA. + * + * @param env: module environment with temporary region and buffer. + * @param ve: validator environment, with iteration count settings. + * @param list: array of RRsets, some of which are NSEC3s. + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. + * @param reason: string for bogus result. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + * or if there was no DS in an insecure (i.e., opt-in) way, + * INDETERMINATE if it was clear that this wasn't a delegation point. + */ +enum sec_status +nsec3_prove_nods(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, char** reason); + +/** + * Prove NXDOMAIN or NODATA. + * + * @param env: module environment with temporary region and buffer. + * @param ve: validator environment, with iteration count settings. + * @param list: array of RRsets, some of which are NSEC3s. + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. + * @param nodata: if return value is secure, this indicates if nodata or + * nxdomain was proven. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + */ +enum sec_status +nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, int* nodata); + +/** + * The NSEC3 hash result storage. + * Consists of an rbtree, with these nodes in it. + * The nodes detail how a set of parameters (from nsec3 rr) plus + * a dname result in a hash. + */ +struct nsec3_cached_hash { + /** rbtree node, key is this structure */ + rbnode_t node; + /** where are the parameters for conversion, in this rrset data */ + struct ub_packed_rrset_key* nsec3; + /** where are the parameters for conversion, this RR number in data */ + int rr; + /** the name to convert */ + uint8_t* dname; + /** length of the dname */ + size_t dname_len; + /** the hash result (not base32 encoded) */ + uint8_t* hash; + /** length of hash in bytes */ + size_t hash_len; + /** the hash result in base32 encoding */ + uint8_t* b32; + /** length of base32 encoding (as a label) */ + size_t b32_len; +}; + +/** + * Rbtree for hash cache comparison function. + * @param c1: key 1. + * @param c2: key 2. + * @return: comparison code, -1, 0, 1, of the keys. + */ +int nsec3_hash_cmp(const void* c1, const void* c2); + +/** + * Obtain the hash of an owner name. + * Used internally by the nsec3 proof functions in this file. + * published to enable unit testing of hash algorithms and cache. + * + * @param table: the cache table. Must be inited at start. + * @param region: scratch region to use for allocation. + * This region holds the tree, if you wipe the region, reinit the tree. + * @param buf: temporary buffer. + * @param nsec3: the rrset with parameters + * @param rr: rr number from d that has the NSEC3 parameters to hash to. + * @param dname: name to hash + * This pointer is used inside the tree, assumed region-alloced. + * @param dname_len: the length of the name. + * @param hash: the hash node is returned on success. + * @return: + * 1 on success, either from cache or newly hashed hash is returned. + * 0 on a malloc failure. + * -1 if the NSEC3 rr was badly formatted (i.e. formerr). + */ +int nsec3_hash_name(rbtree_t* table, struct regional* region, ldns_buffer* buf, + struct ub_packed_rrset_key* nsec3, int rr, uint8_t* dname, + size_t dname_len, struct nsec3_cached_hash** hash); + +/** + * Get next owner name, converted to base32 encoding and with the + * zone name (taken from the nsec3 owner name) appended. + * @param rrset: the NSEC3 rrset. + * @param r: the rr num of the nsec3 in the rrset. + * @param buf: buffer to store name in + * @param max: size of buffer. + * @return length of name on success. 0 on failure (buffer too short or + * bad format nsec3 record). + */ +size_t nsec3_get_nextowner_b32(struct ub_packed_rrset_key* rrset, int r, + uint8_t* buf, size_t max); + +/** + * Convert hash into base32 encoding and with the + * zone name appended. + * @param hash: hashed buffer + * @param hashlen: length of hash + * @param zone: name of zone + * @param zonelen: length of zonename. + * @param buf: buffer to store name in + * @param max: size of buffer. + * @return length of name on success. 0 on failure (buffer too short or + * bad format nsec3 record). + */ +size_t nsec3_hash_to_b32(uint8_t* hash, size_t hashlen, uint8_t* zone, + size_t zonelen, uint8_t* buf, size_t max); + +/** + * Get NSEC3 parameters out of rr. + * @param rrset: the NSEC3 rrset. + * @param r: the rr num of the nsec3 in the rrset. + * @param algo: nsec3 hash algo. + * @param iter: iteration count. + * @param salt: ptr to salt inside rdata. + * @param saltlen: length of salt. + * @return 0 if bad formatted, unknown nsec3 hash algo, or unknown flags set. + */ +int nsec3_get_params(struct ub_packed_rrset_key* rrset, int r, + int* algo, size_t* iter, uint8_t** salt, size_t* saltlen); + +/** + * Get NSEC3 hashed in a buffer + * @param buf: buffer for temp use. + * @param nm: name to hash + * @param nmlen: length of nm. + * @param algo: algo to use, must be known. + * @param iter: iterations + * @param salt: salt for nsec3 + * @param saltlen: length of salt. + * @param res: result of hash stored here. + * @param max: maximum space for result. + * @return 0 on failure, otherwise bytelength stored. + */ +size_t nsec3_get_hashed(ldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo, + size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max); + +/** + * see if NSEC3 RR contains given type + * @param rrset: NSEC3 rrset + * @param r: RR in rrset + * @param type: in host order to check bit for. + * @return true if bit set, false if not or error. + */ +int nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type); + +/** + * return if nsec3 RR has the optout flag + * @param rrset: NSEC3 rrset + * @param r: RR in rrset + * @return true if optout, false on error or not optout + */ +int nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r); + +/** + * Return nsec3 RR next hashed owner name + * @param rrset: NSEC3 rrset + * @param r: RR in rrset + * @param next: ptr into rdata to next owner hash + * @param nextlen: length of hash. + * @return false on malformed + */ +int nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r, + uint8_t** next, size_t* nextlen); + +/** + * nsec3Covers + * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record + * covers the hash. Covers specifically means that the hash is in between + * the owner and next hashes and does not equal either. + * + * @param zone: the zone name. + * @param hash: the hash of the name + * @param rrset: the rrset of the NSEC3. + * @param rr: which rr in the rrset. + * @param buf: temporary buffer. + * @return true if covers, false if not. + */ +int nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash, + struct ub_packed_rrset_key* rrset, int rr, ldns_buffer* buf); + +#endif /* VALIDATOR_VAL_NSEC3_H */ diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c new file mode 100644 index 0000000000000..4c79c004d02c9 --- /dev/null +++ b/validator/val_sigcrypt.c @@ -0,0 +1,1824 @@ +/* + * validator/val_sigcrypt.c - validator signature crypto functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with signature verification and checking, the + * bridging between RR wireformat data and crypto calls. + */ +#include "config.h" +#include <ldns/ldns.h> +#include "validator/val_sigcrypt.h" +#include "validator/validator.h" +#include "util/data/msgreply.h" +#include "util/data/msgparse.h" +#include "util/data/dname.h" +#include "util/rbtree.h" +#include "util/module.h" +#include "util/net_help.h" +#include "util/regional.h" + +#ifndef HAVE_SSL +#error "Need SSL library to do digital signature cryptography" +#endif + +#ifdef HAVE_OPENSSL_ERR_H +#include <openssl/err.h> +#endif + +#ifdef HAVE_OPENSSL_RAND_H +#include <openssl/rand.h> +#endif + +#ifdef HAVE_OPENSSL_CONF_H +#include <openssl/conf.h> +#endif + +#ifdef HAVE_OPENSSL_ENGINE_H +#include <openssl/engine.h> +#endif + +/** return number of rrs in an rrset */ +static size_t +rrset_get_count(struct ub_packed_rrset_key* rrset) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + if(!d) return 0; + return d->count; +} + +/** + * Get RR signature count + */ +static size_t +rrset_get_sigcount(struct ub_packed_rrset_key* k) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + return d->rrsig_count; +} + +/** + * Get signature keytag value + * @param k: rrset (with signatures) + * @param sig_idx: signature index. + * @return keytag or 0 if malformed rrsig. + */ +static uint16_t +rrset_get_sig_keytag(struct ub_packed_rrset_key* k, size_t sig_idx) +{ + uint16_t t; + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + log_assert(sig_idx < d->rrsig_count); + if(d->rr_len[d->count + sig_idx] < 2+18) + return 0; + memmove(&t, d->rr_data[d->count + sig_idx]+2+16, 2); + return ntohs(t); +} + +/** + * Get signature signing algorithm value + * @param k: rrset (with signatures) + * @param sig_idx: signature index. + * @return algo or 0 if malformed rrsig. + */ +static int +rrset_get_sig_algo(struct ub_packed_rrset_key* k, size_t sig_idx) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + log_assert(sig_idx < d->rrsig_count); + if(d->rr_len[d->count + sig_idx] < 2+3) + return 0; + return (int)d->rr_data[d->count + sig_idx][2+2]; +} + +/** get rdata pointer and size */ +static void +rrset_get_rdata(struct ub_packed_rrset_key* k, size_t idx, uint8_t** rdata, + size_t* len) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + log_assert(d && idx < (d->count + d->rrsig_count)); + *rdata = d->rr_data[idx]; + *len = d->rr_len[idx]; +} + +uint16_t +dnskey_get_flags(struct ub_packed_rrset_key* k, size_t idx) +{ + uint8_t* rdata; + size_t len; + uint16_t f; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+2) + return 0; + memmove(&f, rdata+2, 2); + f = ntohs(f); + return f; +} + +/** + * Get DNSKEY protocol value from rdata + * @param k: DNSKEY rrset. + * @param idx: which key. + * @return protocol octet value + */ +static int +dnskey_get_protocol(struct ub_packed_rrset_key* k, size_t idx) +{ + uint8_t* rdata; + size_t len; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+4) + return 0; + return (int)rdata[2+2]; +} + +int +dnskey_get_algo(struct ub_packed_rrset_key* k, size_t idx) +{ + uint8_t* rdata; + size_t len; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+4) + return 0; + return (int)rdata[2+3]; +} + +/** get public key rdata field from a dnskey RR and do some checks */ +static void +dnskey_get_pubkey(struct ub_packed_rrset_key* k, size_t idx, + unsigned char** pk, unsigned int* pklen) +{ + uint8_t* rdata; + size_t len; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+5) { + *pk = NULL; + *pklen = 0; + return; + } + *pk = (unsigned char*)rdata+2+4; + *pklen = (unsigned)len-2-4; +} + +int +ds_get_key_algo(struct ub_packed_rrset_key* k, size_t idx) +{ + uint8_t* rdata; + size_t len; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+3) + return 0; + return (int)rdata[2+2]; +} + +int +ds_get_digest_algo(struct ub_packed_rrset_key* k, size_t idx) +{ + uint8_t* rdata; + size_t len; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+4) + return 0; + return (int)rdata[2+3]; +} + +uint16_t +ds_get_keytag(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx) +{ + uint16_t t; + uint8_t* rdata; + size_t len; + rrset_get_rdata(ds_rrset, ds_idx, &rdata, &len); + if(len < 2+2) + return 0; + memmove(&t, rdata+2, 2); + return ntohs(t); +} + +/** + * Return pointer to the digest in a DS RR. + * @param k: DS rrset. + * @param idx: which DS. + * @param digest: digest data is returned. + * on error, this is NULL. + * @param len: length of digest is returned. + * on error, the length is 0. + */ +static void +ds_get_sigdata(struct ub_packed_rrset_key* k, size_t idx, uint8_t** digest, + size_t* len) +{ + uint8_t* rdata; + size_t rdlen; + rrset_get_rdata(k, idx, &rdata, &rdlen); + if(rdlen < 2+5) { + *digest = NULL; + *len = 0; + return; + } + *digest = rdata + 2 + 4; + *len = rdlen - 2 - 4; +} + +/** + * Return size of DS digest according to its hash algorithm. + * @param k: DS rrset. + * @param idx: which DS. + * @return size in bytes of digest, or 0 if not supported. + */ +static size_t +ds_digest_size_algo(struct ub_packed_rrset_key* k, size_t idx) +{ + switch(ds_get_digest_algo(k, idx)) { +#ifdef HAVE_EVP_SHA1 + case LDNS_SHA1: + return SHA_DIGEST_LENGTH; +#endif +#ifdef HAVE_EVP_SHA256 + case LDNS_SHA256: + return SHA256_DIGEST_LENGTH; +#endif +#ifdef USE_GOST + case LDNS_HASH_GOST: + if(EVP_get_digestbyname("md_gost94")) + return 32; + else return 0; +#endif +#ifdef USE_ECDSA + case LDNS_SHA384: + return SHA384_DIGEST_LENGTH; +#endif + default: break; + } + return 0; +} + +#ifdef USE_GOST +/** Perform GOST hash */ +static int +do_gost94(unsigned char* data, size_t len, unsigned char* dest) +{ + const EVP_MD* md = EVP_get_digestbyname("md_gost94"); + if(!md) + return 0; + return ldns_digest_evp(data, (unsigned int)len, dest, md); +} +#endif + +/** + * Create a DS digest for a DNSKEY entry. + * + * @param env: module environment. Uses scratch space. + * @param dnskey_rrset: DNSKEY rrset. + * @param dnskey_idx: index of RR in rrset. + * @param ds_rrset: DS rrset + * @param ds_idx: index of RR in DS rrset. + * @param digest: digest is returned in here (must be correctly sized). + * @return false on error. + */ +static int +ds_create_dnskey_digest(struct module_env* env, + struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, + struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, + uint8_t* digest) +{ + ldns_buffer* b = env->scratch_buffer; + uint8_t* dnskey_rdata; + size_t dnskey_len; + rrset_get_rdata(dnskey_rrset, dnskey_idx, &dnskey_rdata, &dnskey_len); + + /* create digest source material in buffer + * digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); + * DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. */ + ldns_buffer_clear(b); + ldns_buffer_write(b, dnskey_rrset->rk.dname, + dnskey_rrset->rk.dname_len); + query_dname_tolower(ldns_buffer_begin(b)); + ldns_buffer_write(b, dnskey_rdata+2, dnskey_len-2); /* skip rdatalen*/ + ldns_buffer_flip(b); + + switch(ds_get_digest_algo(ds_rrset, ds_idx)) { +#ifdef HAVE_EVP_SHA1 + case LDNS_SHA1: + (void)SHA1((unsigned char*)ldns_buffer_begin(b), + ldns_buffer_limit(b), (unsigned char*)digest); + return 1; +#endif +#ifdef HAVE_EVP_SHA256 + case LDNS_SHA256: + (void)SHA256((unsigned char*)ldns_buffer_begin(b), + ldns_buffer_limit(b), (unsigned char*)digest); + return 1; +#endif +#ifdef USE_GOST + case LDNS_HASH_GOST: + if(do_gost94((unsigned char*)ldns_buffer_begin(b), + ldns_buffer_limit(b), (unsigned char*)digest)) + return 1; +#endif +#ifdef USE_ECDSA + case LDNS_SHA384: + (void)SHA384((unsigned char*)ldns_buffer_begin(b), + ldns_buffer_limit(b), (unsigned char*)digest); + return 1; +#endif + default: + verbose(VERB_QUERY, "unknown DS digest algorithm %d", + (int) ds_get_digest_algo(ds_rrset, ds_idx)); + break; + } + return 0; +} + +int ds_digest_match_dnskey(struct module_env* env, + struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, + struct ub_packed_rrset_key* ds_rrset, size_t ds_idx) +{ + uint8_t* ds; /* DS digest */ + size_t dslen; + uint8_t* digest; /* generated digest */ + size_t digestlen = ds_digest_size_algo(ds_rrset, ds_idx); + + if(digestlen == 0) { + verbose(VERB_QUERY, "DS fail: not supported, or DS RR " + "format error"); + return 0; /* not supported, or DS RR format error */ + } + /* check digest length in DS with length from hash function */ + ds_get_sigdata(ds_rrset, ds_idx, &ds, &dslen); + if(!ds || dslen != digestlen) { + verbose(VERB_QUERY, "DS fail: DS RR algo and digest do not " + "match each other"); + return 0; /* DS algorithm and digest do not match */ + } + + digest = regional_alloc(env->scratch, digestlen); + if(!digest) { + verbose(VERB_QUERY, "DS fail: out of memory"); + return 0; /* mem error */ + } + if(!ds_create_dnskey_digest(env, dnskey_rrset, dnskey_idx, ds_rrset, + ds_idx, digest)) { + verbose(VERB_QUERY, "DS fail: could not calc key digest"); + return 0; /* digest algo failed */ + } + if(memcmp(digest, ds, dslen) != 0) { + verbose(VERB_QUERY, "DS fail: digest is different"); + return 0; /* digest different */ + } + return 1; +} + +int +ds_digest_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, + size_t ds_idx) +{ + return (ds_digest_size_algo(ds_rrset, ds_idx) != 0); +} + +/** return true if DNSKEY algorithm id is supported */ +static int +dnskey_algo_id_is_supported(int id) +{ + switch(id) { + case LDNS_DSA: + case LDNS_DSA_NSEC3: + case LDNS_RSASHA1: + case LDNS_RSASHA1_NSEC3: + case LDNS_RSAMD5: +#if defined(HAVE_EVP_SHA256) && defined(USE_SHA2) + case LDNS_RSASHA256: +#endif +#if defined(HAVE_EVP_SHA512) && defined(USE_SHA2) + case LDNS_RSASHA512: +#endif +#ifdef USE_ECDSA + case LDNS_ECDSAP256SHA256: + case LDNS_ECDSAP384SHA384: +#endif + return 1; +#ifdef USE_GOST + case LDNS_ECC_GOST: + /* we support GOST if it can be loaded */ + return ldns_key_EVP_load_gost_id(); +#endif + default: + return 0; + } +} + +int +ds_key_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, + size_t ds_idx) +{ + return dnskey_algo_id_is_supported(ds_get_key_algo(ds_rrset, ds_idx)); +} + +uint16_t +dnskey_calc_keytag(struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx) +{ + uint8_t* data; + size_t len; + rrset_get_rdata(dnskey_rrset, dnskey_idx, &data, &len); + /* do not pass rdatalen to ldns */ + return ldns_calc_keytag_raw(data+2, len-2); +} + +int dnskey_algo_is_supported(struct ub_packed_rrset_key* dnskey_rrset, + size_t dnskey_idx) +{ + return dnskey_algo_id_is_supported(dnskey_get_algo(dnskey_rrset, + dnskey_idx)); +} + +void algo_needs_init_dnskey_add(struct algo_needs* n, + struct ub_packed_rrset_key* dnskey, uint8_t* sigalg) +{ + uint8_t algo; + size_t i, total = n->num; + size_t num = rrset_get_count(dnskey); + + for(i=0; i<num; i++) { + algo = (uint8_t)dnskey_get_algo(dnskey, i); + if(!dnskey_algo_id_is_supported((int)algo)) + continue; + if(n->needs[algo] == 0) { + n->needs[algo] = 1; + sigalg[total] = algo; + total++; + } + } + sigalg[total] = 0; + n->num = total; +} + +void algo_needs_init_list(struct algo_needs* n, uint8_t* sigalg) +{ + uint8_t algo; + size_t total = 0; + + memset(n->needs, 0, sizeof(uint8_t)*ALGO_NEEDS_MAX); + while( (algo=*sigalg++) != 0) { + log_assert(dnskey_algo_id_is_supported((int)algo)); + log_assert(n->needs[algo] == 0); + n->needs[algo] = 1; + total++; + } + n->num = total; +} + +void algo_needs_init_ds(struct algo_needs* n, struct ub_packed_rrset_key* ds, + int fav_ds_algo, uint8_t* sigalg) +{ + uint8_t algo; + size_t i, total = 0; + size_t num = rrset_get_count(ds); + + memset(n->needs, 0, sizeof(uint8_t)*ALGO_NEEDS_MAX); + for(i=0; i<num; i++) { + if(ds_get_digest_algo(ds, i) != fav_ds_algo) + continue; + algo = (uint8_t)ds_get_key_algo(ds, i); + if(!dnskey_algo_id_is_supported((int)algo)) + continue; + log_assert(algo != 0); /* we do not support 0 and is EOS */ + if(n->needs[algo] == 0) { + n->needs[algo] = 1; + sigalg[total] = algo; + total++; + } + } + sigalg[total] = 0; + n->num = total; +} + +int algo_needs_set_secure(struct algo_needs* n, uint8_t algo) +{ + if(n->needs[algo]) { + n->needs[algo] = 0; + n->num --; + if(n->num == 0) /* done! */ + return 1; + } + return 0; +} + +void algo_needs_set_bogus(struct algo_needs* n, uint8_t algo) +{ + if(n->needs[algo]) n->needs[algo] = 2; /* need it, but bogus */ +} + +size_t algo_needs_num_missing(struct algo_needs* n) +{ + return n->num; +} + +int algo_needs_missing(struct algo_needs* n) +{ + int i; + /* first check if a needed algo was bogus - report that */ + for(i=0; i<ALGO_NEEDS_MAX; i++) + if(n->needs[i] == 2) + return 0; + /* now check which algo is missing */ + for(i=0; i<ALGO_NEEDS_MAX; i++) + if(n->needs[i] == 1) + return i; + return 0; +} + +enum sec_status +dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, + uint8_t* sigalg, char** reason) +{ + enum sec_status sec; + size_t i, num; + rbtree_t* sortree = NULL; + /* make sure that for all DNSKEY algorithms there are valid sigs */ + struct algo_needs needs; + int alg; + + num = rrset_get_sigcount(rrset); + if(num == 0) { + verbose(VERB_QUERY, "rrset failed to verify due to a lack of " + "signatures"); + *reason = "no signatures"; + return sec_status_bogus; + } + + if(sigalg) { + algo_needs_init_list(&needs, sigalg); + if(algo_needs_num_missing(&needs) == 0) { + verbose(VERB_QUERY, "zone has no known algorithms"); + *reason = "zone has no known algorithms"; + return sec_status_insecure; + } + } + for(i=0; i<num; i++) { + sec = dnskeyset_verify_rrset_sig(env, ve, *env->now, rrset, + dnskey, i, &sortree, reason); + /* see which algorithm has been fixed up */ + if(sec == sec_status_secure) { + if(!sigalg) + return sec; /* done! */ + else if(algo_needs_set_secure(&needs, + (uint8_t)rrset_get_sig_algo(rrset, i))) + return sec; /* done! */ + } else if(sigalg && sec == sec_status_bogus) { + algo_needs_set_bogus(&needs, + (uint8_t)rrset_get_sig_algo(rrset, i)); + } + } + verbose(VERB_ALGO, "rrset failed to verify: no valid signatures for " + "%d algorithms", (int)algo_needs_num_missing(&needs)); + if(sigalg && (alg=algo_needs_missing(&needs)) != 0) { + algo_needs_reason(env, alg, reason, "no signatures"); + } + return sec_status_bogus; +} + +void algo_needs_reason(struct module_env* env, int alg, char** reason, char* s) +{ + char buf[256]; + ldns_lookup_table *t = ldns_lookup_by_id(ldns_algorithms, alg); + if(t&&t->name) + snprintf(buf, sizeof(buf), "%s with algorithm %s", s, t->name); + else snprintf(buf, sizeof(buf), "%s with algorithm ALG%u", s, + (unsigned)alg); + *reason = regional_strdup(env->scratch, buf); + if(!*reason) + *reason = s; +} + +enum sec_status +dnskey_verify_rrset(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, + size_t dnskey_idx, char** reason) +{ + enum sec_status sec; + size_t i, num, numchecked = 0; + rbtree_t* sortree = NULL; + int buf_canon = 0; + uint16_t tag = dnskey_calc_keytag(dnskey, dnskey_idx); + int algo = dnskey_get_algo(dnskey, dnskey_idx); + + num = rrset_get_sigcount(rrset); + if(num == 0) { + verbose(VERB_QUERY, "rrset failed to verify due to a lack of " + "signatures"); + *reason = "no signatures"; + return sec_status_bogus; + } + for(i=0; i<num; i++) { + /* see if sig matches keytag and algo */ + if(algo != rrset_get_sig_algo(rrset, i) || + tag != rrset_get_sig_keytag(rrset, i)) + continue; + buf_canon = 0; + sec = dnskey_verify_rrset_sig(env->scratch, + env->scratch_buffer, ve, *env->now, rrset, + dnskey, dnskey_idx, i, &sortree, &buf_canon, reason); + if(sec == sec_status_secure) + return sec; + numchecked ++; + } + verbose(VERB_ALGO, "rrset failed to verify: all signatures are bogus"); + if(!numchecked) *reason = "signature missing"; + return sec_status_bogus; +} + +enum sec_status +dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, + uint32_t now, struct ub_packed_rrset_key* rrset, + struct ub_packed_rrset_key* dnskey, size_t sig_idx, + struct rbtree_t** sortree, char** reason) +{ + /* find matching keys and check them */ + enum sec_status sec = sec_status_bogus; + uint16_t tag = rrset_get_sig_keytag(rrset, sig_idx); + int algo = rrset_get_sig_algo(rrset, sig_idx); + size_t i, num = rrset_get_count(dnskey); + size_t numchecked = 0; + int buf_canon = 0; + verbose(VERB_ALGO, "verify sig %d %d", (int)tag, algo); + if(!dnskey_algo_id_is_supported(algo)) { + verbose(VERB_QUERY, "verify sig: unknown algorithm"); + return sec_status_insecure; + } + + for(i=0; i<num; i++) { + /* see if key matches keytag and algo */ + if(algo != dnskey_get_algo(dnskey, i) || + tag != dnskey_calc_keytag(dnskey, i)) + continue; + numchecked ++; + + /* see if key verifies */ + sec = dnskey_verify_rrset_sig(env->scratch, + env->scratch_buffer, ve, now, rrset, dnskey, i, + sig_idx, sortree, &buf_canon, reason); + if(sec == sec_status_secure) + return sec; + } + if(numchecked == 0) { + *reason = "signatures from unknown keys"; + verbose(VERB_QUERY, "verify: could not find appropriate key"); + return sec_status_bogus; + } + return sec_status_bogus; +} + +/** + * RR entries in a canonical sorted tree of RRs + */ +struct canon_rr { + /** rbtree node, key is this structure */ + rbnode_t node; + /** rrset the RR is in */ + struct ub_packed_rrset_key* rrset; + /** which RR in the rrset */ + size_t rr_idx; +}; + +/** + * Compare two RR for canonical order, in a field-style sweep. + * @param d: rrset data + * @param desc: ldns wireformat descriptor. + * @param i: first RR to compare + * @param j: first RR to compare + * @return comparison code. + */ +static int +canonical_compare_byfield(struct packed_rrset_data* d, + const ldns_rr_descriptor* desc, size_t i, size_t j) +{ + /* sweep across rdata, keep track of some state: + * which rr field, and bytes left in field. + * current position in rdata, length left. + * are we in a dname, length left in a label. + */ + int wfi = -1; /* current wireformat rdata field (rdf) */ + int wfj = -1; + uint8_t* di = d->rr_data[i]+2; /* ptr to current rdata byte */ + uint8_t* dj = d->rr_data[j]+2; + size_t ilen = d->rr_len[i]-2; /* length left in rdata */ + size_t jlen = d->rr_len[j]-2; + int dname_i = 0; /* true if these bytes are part of a name */ + int dname_j = 0; + size_t lablen_i = 0; /* 0 for label length byte,for first byte of rdf*/ + size_t lablen_j = 0; /* otherwise remaining length of rdf or label */ + int dname_num_i = (int)desc->_dname_count; /* decreased at root label */ + int dname_num_j = (int)desc->_dname_count; + + /* loop while there are rdata bytes available for both rrs, + * and still some lowercasing needs to be done; either the dnames + * have not been reached yet, or they are currently being processed */ + while(ilen > 0 && jlen > 0 && (dname_num_i > 0 || dname_num_j > 0)) { + /* compare these two bytes */ + /* lowercase if in a dname and not a label length byte */ + if( ((dname_i && lablen_i)?(uint8_t)tolower((int)*di):*di) + != ((dname_j && lablen_j)?(uint8_t)tolower((int)*dj):*dj) + ) { + if(((dname_i && lablen_i)?(uint8_t)tolower((int)*di):*di) + < ((dname_j && lablen_j)?(uint8_t)tolower((int)*dj):*dj)) + return -1; + return 1; + } + ilen--; + jlen--; + /* bytes are equal */ + + /* advance field i */ + /* lablen 0 means that this byte is the first byte of the + * next rdata field; inspect this rdata field and setup + * to process the rest of this rdata field. + * The reason to first read the byte, then setup the rdf, + * is that we are then sure the byte is available and short + * rdata is handled gracefully (even if it is a formerr). */ + if(lablen_i == 0) { + if(dname_i) { + /* scan this dname label */ + /* capture length to lowercase */ + lablen_i = (size_t)*di; + if(lablen_i == 0) { + /* end root label */ + dname_i = 0; + dname_num_i--; + /* if dname num is 0, then the + * remainder is binary only */ + if(dname_num_i == 0) + lablen_i = ilen; + } + } else { + /* scan this rdata field */ + wfi++; + if(desc->_wireformat[wfi] + == LDNS_RDF_TYPE_DNAME) { + dname_i = 1; + lablen_i = (size_t)*di; + if(lablen_i == 0) { + dname_i = 0; + dname_num_i--; + if(dname_num_i == 0) + lablen_i = ilen; + } + } else if(desc->_wireformat[wfi] + == LDNS_RDF_TYPE_STR) + lablen_i = (size_t)*di; + else lablen_i = get_rdf_size( + desc->_wireformat[wfi]) - 1; + } + } else lablen_i--; + + /* advance field j; same as for i */ + if(lablen_j == 0) { + if(dname_j) { + lablen_j = (size_t)*dj; + if(lablen_j == 0) { + dname_j = 0; + dname_num_j--; + if(dname_num_j == 0) + lablen_j = jlen; + } + } else { + wfj++; + if(desc->_wireformat[wfj] + == LDNS_RDF_TYPE_DNAME) { + dname_j = 1; + lablen_j = (size_t)*dj; + if(lablen_j == 0) { + dname_j = 0; + dname_num_j--; + if(dname_num_j == 0) + lablen_j = jlen; + } + } else if(desc->_wireformat[wfj] + == LDNS_RDF_TYPE_STR) + lablen_j = (size_t)*dj; + else lablen_j = get_rdf_size( + desc->_wireformat[wfj]) - 1; + } + } else lablen_j--; + di++; + dj++; + } + /* end of the loop; because we advanced byte by byte; now we have + * that the rdata has ended, or that there is a binary remainder */ + /* shortest first */ + if(ilen == 0 && jlen == 0) + return 0; + if(ilen == 0) + return -1; + if(jlen == 0) + return 1; + /* binary remainder, capture comparison in wfi variable */ + if((wfi = memcmp(di, dj, (ilen<jlen)?ilen:jlen)) != 0) + return wfi; + if(ilen < jlen) + return -1; + if(jlen < ilen) + return 1; + return 0; +} + +/** + * Compare two RRs in the same RRset and determine their relative + * canonical order. + * @param rrset: the rrset in which to perform compares. + * @param i: first RR to compare + * @param j: first RR to compare + * @return 0 if RR i== RR j, -1 if <, +1 if >. + */ +static int +canonical_compare(struct ub_packed_rrset_key* rrset, size_t i, size_t j) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + const ldns_rr_descriptor* desc; + uint16_t type = ntohs(rrset->rk.type); + size_t minlen; + int c; + + if(i==j) + return 0; + /* in case rdata-len is to be compared for canonical order + c = memcmp(d->rr_data[i], d->rr_data[j], 2); + if(c != 0) + return c; */ + + switch(type) { + /* These RR types have only a name as RDATA. + * This name has to be canonicalized.*/ + case LDNS_RR_TYPE_NS: + case LDNS_RR_TYPE_MD: + case LDNS_RR_TYPE_MF: + case LDNS_RR_TYPE_CNAME: + case LDNS_RR_TYPE_MB: + case LDNS_RR_TYPE_MG: + case LDNS_RR_TYPE_MR: + case LDNS_RR_TYPE_PTR: + case LDNS_RR_TYPE_DNAME: + return query_dname_compare(d->rr_data[i]+2, + d->rr_data[j]+2); + + /* These RR types have STR and fixed size rdata fields + * before one or more name fields that need canonicalizing, + * and after that a byte-for byte remainder can be compared. + */ + /* type starts with the name; remainder is binary compared */ + case LDNS_RR_TYPE_NXT: + /* use rdata field formats */ + case LDNS_RR_TYPE_MINFO: + case LDNS_RR_TYPE_RP: + case LDNS_RR_TYPE_SOA: + case LDNS_RR_TYPE_RT: + case LDNS_RR_TYPE_AFSDB: + case LDNS_RR_TYPE_KX: + case LDNS_RR_TYPE_MX: + case LDNS_RR_TYPE_SIG: + /* RRSIG signer name has to be downcased */ + case LDNS_RR_TYPE_RRSIG: + case LDNS_RR_TYPE_PX: + case LDNS_RR_TYPE_NAPTR: + case LDNS_RR_TYPE_SRV: + desc = ldns_rr_descript(type); + log_assert(desc); + /* this holds for the types that need canonicalizing */ + log_assert(desc->_minimum == desc->_maximum); + return canonical_compare_byfield(d, desc, i, j); + + case LDNS_RR_TYPE_HINFO: /* no longer downcased */ + case LDNS_RR_TYPE_NSEC: + default: + /* For unknown RR types, or types not listed above, + * no canonicalization is needed, do binary compare */ + /* byte for byte compare, equal means shortest first*/ + minlen = d->rr_len[i]-2; + if(minlen > d->rr_len[j]-2) + minlen = d->rr_len[j]-2; + c = memcmp(d->rr_data[i]+2, d->rr_data[j]+2, minlen); + if(c!=0) + return c; + /* rdata equal, shortest is first */ + if(d->rr_len[i] < d->rr_len[j]) + return -1; + if(d->rr_len[i] > d->rr_len[j]) + return 1; + /* rdata equal, length equal */ + break; + } + return 0; +} + +int +canonical_tree_compare(const void* k1, const void* k2) +{ + struct canon_rr* r1 = (struct canon_rr*)k1; + struct canon_rr* r2 = (struct canon_rr*)k2; + log_assert(r1->rrset == r2->rrset); + return canonical_compare(r1->rrset, r1->rr_idx, r2->rr_idx); +} + +/** + * Sort RRs for rrset in canonical order. + * Does not actually canonicalize the RR rdatas. + * Does not touch rrsigs. + * @param rrset: to sort. + * @param d: rrset data. + * @param sortree: tree to sort into. + * @param rrs: rr storage. + */ +static void +canonical_sort(struct ub_packed_rrset_key* rrset, struct packed_rrset_data* d, + rbtree_t* sortree, struct canon_rr* rrs) +{ + size_t i; + /* insert into rbtree to sort and detect duplicates */ + for(i=0; i<d->count; i++) { + rrs[i].node.key = &rrs[i]; + rrs[i].rrset = rrset; + rrs[i].rr_idx = i; + if(!rbtree_insert(sortree, &rrs[i].node)) { + /* this was a duplicate */ + } + } +} + +/** + * Inser canonical owner name into buffer. + * @param buf: buffer to insert into at current position. + * @param k: rrset with its owner name. + * @param sig: signature with signer name and label count. + * must be length checked, at least 18 bytes long. + * @param can_owner: position in buffer returned for future use. + * @param can_owner_len: length of canonical owner name. + */ +static void +insert_can_owner(ldns_buffer* buf, struct ub_packed_rrset_key* k, + uint8_t* sig, uint8_t** can_owner, size_t* can_owner_len) +{ + int rrsig_labels = (int)sig[3]; + int fqdn_labels = dname_signame_label_count(k->rk.dname); + *can_owner = ldns_buffer_current(buf); + if(rrsig_labels == fqdn_labels) { + /* no change */ + ldns_buffer_write(buf, k->rk.dname, k->rk.dname_len); + query_dname_tolower(*can_owner); + *can_owner_len = k->rk.dname_len; + return; + } + log_assert(rrsig_labels < fqdn_labels); + /* *. | fqdn(rightmost rrsig_labels) */ + if(rrsig_labels < fqdn_labels) { + int i; + uint8_t* nm = k->rk.dname; + size_t len = k->rk.dname_len; + /* so skip fqdn_labels-rrsig_labels */ + for(i=0; i<fqdn_labels-rrsig_labels; i++) { + dname_remove_label(&nm, &len); + } + *can_owner_len = len+2; + ldns_buffer_write(buf, (uint8_t*)"\001*", 2); + ldns_buffer_write(buf, nm, len); + query_dname_tolower(*can_owner); + } +} + +/** + * Canonicalize Rdata in buffer. + * @param buf: buffer at position just after the rdata. + * @param rrset: rrset with type. + * @param len: length of the rdata (including rdatalen uint16). + */ +static void +canonicalize_rdata(ldns_buffer* buf, struct ub_packed_rrset_key* rrset, + size_t len) +{ + uint8_t* datstart = ldns_buffer_current(buf)-len+2; + switch(ntohs(rrset->rk.type)) { + case LDNS_RR_TYPE_NXT: + case LDNS_RR_TYPE_NS: + case LDNS_RR_TYPE_MD: + case LDNS_RR_TYPE_MF: + case LDNS_RR_TYPE_CNAME: + case LDNS_RR_TYPE_MB: + case LDNS_RR_TYPE_MG: + case LDNS_RR_TYPE_MR: + case LDNS_RR_TYPE_PTR: + case LDNS_RR_TYPE_DNAME: + /* type only has a single argument, the name */ + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_MINFO: + case LDNS_RR_TYPE_RP: + case LDNS_RR_TYPE_SOA: + /* two names after another */ + query_dname_tolower(datstart); + query_dname_tolower(datstart + + dname_valid(datstart, len-2)); + return; + case LDNS_RR_TYPE_RT: + case LDNS_RR_TYPE_AFSDB: + case LDNS_RR_TYPE_KX: + case LDNS_RR_TYPE_MX: + /* skip fixed part */ + if(len < 2+2+1) /* rdlen, skiplen, 1byteroot */ + return; + datstart += 2; + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_SIG: + /* downcase the RRSIG, compat with BIND (kept it from SIG) */ + case LDNS_RR_TYPE_RRSIG: + /* skip fixed part */ + if(len < 2+18+1) + return; + datstart += 18; + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_PX: + /* skip, then two names after another */ + if(len < 2+2+1) + return; + datstart += 2; + query_dname_tolower(datstart); + query_dname_tolower(datstart + + dname_valid(datstart, len-2-2)); + return; + case LDNS_RR_TYPE_NAPTR: + if(len < 2+4) + return; + len -= 2+4; + datstart += 4; + if(len < (size_t)datstart[0]+1) /* skip text field */ + return; + len -= (size_t)datstart[0]+1; + datstart += (size_t)datstart[0]+1; + if(len < (size_t)datstart[0]+1) /* skip text field */ + return; + len -= (size_t)datstart[0]+1; + datstart += (size_t)datstart[0]+1; + if(len < (size_t)datstart[0]+1) /* skip text field */ + return; + len -= (size_t)datstart[0]+1; + datstart += (size_t)datstart[0]+1; + if(len < 1) /* check name is at least 1 byte*/ + return; + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_SRV: + /* skip fixed part */ + if(len < 2+6+1) + return; + datstart += 6; + query_dname_tolower(datstart); + return; + + /* do not canonicalize NSEC rdata name, compat with + * from bind 9.4 signer, where it does not do so */ + case LDNS_RR_TYPE_NSEC: /* type starts with the name */ + case LDNS_RR_TYPE_HINFO: /* not downcased */ + /* A6 not supported */ + default: + /* nothing to do for unknown types */ + return; + } +} + +/** + * Create canonical form of rrset in the scratch buffer. + * @param region: temporary region. + * @param buf: the buffer to use. + * @param k: the rrset to insert. + * @param sig: RRSIG rdata to include. + * @param siglen: RRSIG rdata len excluding signature field, but inclusive + * signer name length. + * @param sortree: if NULL is passed a new sorted rrset tree is built. + * Otherwise it is reused. + * @return false on alloc error. + */ +static int +rrset_canonical(struct regional* region, ldns_buffer* buf, + struct ub_packed_rrset_key* k, uint8_t* sig, size_t siglen, + struct rbtree_t** sortree) +{ + 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; + + if(!*sortree) { + *sortree = (struct rbtree_t*)regional_alloc(region, + sizeof(rbtree_t)); + if(!*sortree) + return 0; + rrs = regional_alloc(region, sizeof(struct canon_rr)*d->count); + if(!rrs) { + *sortree = NULL; + return 0; + } + rbtree_init(*sortree, &canonical_tree_compare); + canonical_sort(k, d, *sortree, rrs); + } + + ldns_buffer_clear(buf); + ldns_buffer_write(buf, sig, siglen); + /* canonicalize signer name */ + query_dname_tolower(ldns_buffer_begin(buf)+18); + RBTREE_FOR(walk, struct canon_rr*, (*sortree)) { + /* see if there is enough space left in the buffer */ + if(ldns_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) + ldns_buffer_write(buf, can_owner, can_owner_len); + else insert_can_owner(buf, k, sig, &can_owner, + &can_owner_len); + ldns_buffer_write(buf, &k->rk.type, 2); + ldns_buffer_write(buf, &k->rk.rrset_class, 2); + ldns_buffer_write(buf, sig+4, 4); + ldns_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]); + } + ldns_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) +{ + struct tm tm; + char expi_buf[16]; + char incep_buf[16]; + char now_buf[16]; + time_t te, ti, tn; + + if(verbosity < VERB_QUERY) + return; + te = (time_t)expi; + ti = (time_t)incep; + tn = (time_t)now; + memset(&tm, 0, sizeof(tm)); + if(gmtime_r(&te, &tm) && strftime(expi_buf, 15, "%Y%m%d%H%M%S", &tm) + &&gmtime_r(&ti, &tm) && strftime(incep_buf, 15, "%Y%m%d%H%M%S", &tm) + &&gmtime_r(&tn, &tm) && strftime(now_buf, 15, "%Y%m%d%H%M%S", &tm)) { + log_info("%s expi=%s incep=%s now=%s", str, expi_buf, + incep_buf, now_buf); + } else + log_info("%s expi=%u incep=%u now=%u", str, (unsigned)expi, + (unsigned)incep, (unsigned)now); +} + +/** check rrsig dates */ +static int +check_dates(struct val_env* ve, uint32_t unow, + uint8_t* expi_p, uint8_t* incep_p, char** reason) +{ + /* read out the dates */ + int32_t expi, incep, now; + memmove(&expi, expi_p, sizeof(expi)); + memmove(&incep, incep_p, sizeof(incep)); + expi = ntohl(expi); + incep = ntohl(incep); + + /* get current date */ + if(ve->date_override) { + if(ve->date_override == -1) { + verbose(VERB_ALGO, "date override: ignore date"); + return 1; + } + now = ve->date_override; + verbose(VERB_ALGO, "date override option %d", (int)now); + } else now = (int32_t)unow; + + /* check them */ + if(incep - expi > 0) { + sigdate_error("verify: inception after expiration, " + "signature bad", expi, incep, now); + *reason = "signature inception after expiration"; + return 0; + } + if(incep - now > 0) { + /* within skew ? (calc here to avoid calculation normally) */ + int32_t skew = (expi-incep)/10; + if(skew < ve->skew_min) skew = ve->skew_min; + if(skew > ve->skew_max) skew = ve->skew_max; + if(incep - now > skew) { + sigdate_error("verify: signature bad, current time is" + " before inception date", expi, incep, now); + *reason = "signature before inception date"; + return 0; + } + sigdate_error("verify warning suspicious signature inception " + " or bad local clock", expi, incep, now); + } + if(now - expi > 0) { + int32_t skew = (expi-incep)/10; + if(skew < ve->skew_min) skew = ve->skew_min; + if(skew > ve->skew_max) skew = ve->skew_max; + if(now - expi > skew) { + sigdate_error("verify: signature expired", expi, + incep, now); + *reason = "signature expired"; + return 0; + } + sigdate_error("verify warning suspicious signature expiration " + " or bad local clock", expi, incep, now); + } + return 1; +} + +/** adjust rrset TTL for verified rrset, compare to original TTL and expi */ +static void +adjust_ttl(struct val_env* ve, uint32_t unow, + struct ub_packed_rrset_key* rrset, uint8_t* orig_p, + uint8_t* expi_p, uint8_t* incep_p) +{ + struct packed_rrset_data* d = + (struct packed_rrset_data*)rrset->entry.data; + /* read out the dates */ + int32_t origttl, expittl, expi, incep, now; + memmove(&origttl, orig_p, sizeof(origttl)); + memmove(&expi, expi_p, sizeof(expi)); + memmove(&incep, incep_p, sizeof(incep)); + expi = ntohl(expi); + incep = ntohl(incep); + origttl = ntohl(origttl); + + /* get current date */ + if(ve->date_override) { + now = ve->date_override; + } else now = (int32_t)unow; + expittl = expi - now; + + /* so now: + * d->ttl: rrset ttl read from message or cache. May be reduced + * origttl: original TTL from signature, authoritative TTL max. + * expittl: TTL until the signature expires. + * + * Use the smallest of these. + */ + if(d->ttl > (uint32_t)origttl) { + verbose(VERB_QUERY, "rrset TTL larger than original TTL," + " adjusting TTL downwards"); + d->ttl = origttl; + } + if(expittl > 0 && d->ttl > (uint32_t)expittl) { + verbose(VERB_ALGO, "rrset TTL larger than sig expiration ttl," + " adjusting TTL downwards"); + d->ttl = expittl; + } +} + + +/** + * Output a libcrypto openssl error to the logfile. + * @param str: string to add to it. + * @param e: the error to output, error number from ERR_get_error(). + */ +static void +log_crypto_error(const char* str, unsigned long e) +{ + char buf[128]; + /* or use ERR_error_string if ERR_error_string_n is not avail TODO */ + ERR_error_string_n(e, buf, sizeof(buf)); + /* buf now contains */ + /* error:[error code]:[library name]:[function name]:[reason string] */ + log_err("%s crypto %s", str, buf); +} + +/** + * Setup DSA key digest in DER encoding ... + * @param sig: input is signature output alloced ptr (unless failure). + * caller must free alloced ptr if this routine returns true. + * @param len: input is initial siglen, output is output len. + * @return false on failure. + */ +static int +setup_dsa_sig(unsigned char** sig, unsigned int* len) +{ + unsigned char* orig = *sig; + unsigned int origlen = *len; + int newlen; + BIGNUM *R, *S; + DSA_SIG *dsasig; + + /* extract the R and S field from the sig buffer */ + if(origlen < 1 + 2*SHA_DIGEST_LENGTH) + return 0; + R = BN_new(); + if(!R) return 0; + (void) BN_bin2bn(orig + 1, SHA_DIGEST_LENGTH, R); + S = BN_new(); + if(!S) return 0; + (void) BN_bin2bn(orig + 21, SHA_DIGEST_LENGTH, S); + dsasig = DSA_SIG_new(); + if(!dsasig) return 0; + + dsasig->r = R; + dsasig->s = S; + *sig = NULL; + newlen = i2d_DSA_SIG(dsasig, sig); + if(newlen < 0) { + DSA_SIG_free(dsasig); + free(*sig); + return 0; + } + *len = (unsigned int)newlen; + DSA_SIG_free(dsasig); + return 1; +} + +#ifdef USE_ECDSA +/** + * Setup the ECDSA signature in its encoding that the library wants. + * Converts from plain numbers to ASN formatted. + * @param sig: input is signature, output alloced ptr (unless failure). + * caller must free alloced ptr if this routine returns true. + * @param len: input is initial siglen, output is output len. + * @return false on failure. + */ +static int +setup_ecdsa_sig(unsigned char** sig, unsigned int* len) +{ + ECDSA_SIG* ecdsa_sig; + int newlen; + int bnsize = (int)((*len)/2); + /* if too short or not even length, fails */ + if(*len < 16 || bnsize*2 != (int)*len) + return 0; + /* use the raw data to parse two evenly long BIGNUMs, "r | s". */ + ecdsa_sig = ECDSA_SIG_new(); + if(!ecdsa_sig) return 0; + ecdsa_sig->r = BN_bin2bn(*sig, bnsize, ecdsa_sig->r); + ecdsa_sig->s = BN_bin2bn(*sig+bnsize, bnsize, ecdsa_sig->s); + if(!ecdsa_sig->r || !ecdsa_sig->s) { + ECDSA_SIG_free(ecdsa_sig); + return 0; + } + + /* spool it into ASN format */ + *sig = NULL; + newlen = i2d_ECDSA_SIG(ecdsa_sig, sig); + if(newlen <= 0) { + ECDSA_SIG_free(ecdsa_sig); + free(*sig); + return 0; + } + *len = (unsigned int)newlen; + ECDSA_SIG_free(ecdsa_sig); + return 1; +} +#endif /* USE_ECDSA */ + +/** + * Setup key and digest for verification. Adjust sig if necessary. + * + * @param algo: key algorithm + * @param evp_key: EVP PKEY public key to create. + * @param digest_type: digest type to use + * @param key: key to setup for. + * @param keylen: length of key. + * @return false on failure. + */ +static int +setup_key_digest(int algo, EVP_PKEY** evp_key, const EVP_MD** digest_type, + unsigned char* key, size_t keylen) +{ + DSA* dsa; + RSA* rsa; + + switch(algo) { + case LDNS_DSA: + case LDNS_DSA_NSEC3: + *evp_key = EVP_PKEY_new(); + if(!*evp_key) { + log_err("verify: malloc failure in crypto"); + return sec_status_unchecked; + } + dsa = ldns_key_buf2dsa_raw(key, keylen); + if(!dsa) { + verbose(VERB_QUERY, "verify: " + "ldns_key_buf2dsa_raw failed"); + return 0; + } + if(EVP_PKEY_assign_DSA(*evp_key, dsa) == 0) { + verbose(VERB_QUERY, "verify: " + "EVP_PKEY_assign_DSA failed"); + return 0; + } + *digest_type = EVP_dss1(); + + break; + case LDNS_RSASHA1: + case LDNS_RSASHA1_NSEC3: +#if defined(HAVE_EVP_SHA256) && defined(USE_SHA2) + case LDNS_RSASHA256: +#endif +#if defined(HAVE_EVP_SHA512) && defined(USE_SHA2) + case LDNS_RSASHA512: +#endif + *evp_key = EVP_PKEY_new(); + if(!*evp_key) { + log_err("verify: malloc failure in crypto"); + return sec_status_unchecked; + } + rsa = ldns_key_buf2rsa_raw(key, keylen); + if(!rsa) { + verbose(VERB_QUERY, "verify: " + "ldns_key_buf2rsa_raw SHA failed"); + return 0; + } + if(EVP_PKEY_assign_RSA(*evp_key, rsa) == 0) { + verbose(VERB_QUERY, "verify: " + "EVP_PKEY_assign_RSA SHA failed"); + return 0; + } + + /* select SHA version */ +#if defined(HAVE_EVP_SHA256) && defined(USE_SHA2) + if(algo == LDNS_RSASHA256) + *digest_type = EVP_sha256(); + else +#endif +#if defined(HAVE_EVP_SHA512) && defined(USE_SHA2) + if(algo == LDNS_RSASHA512) + *digest_type = EVP_sha512(); + else +#endif + *digest_type = EVP_sha1(); + + break; + case LDNS_RSAMD5: + *evp_key = EVP_PKEY_new(); + if(!*evp_key) { + log_err("verify: malloc failure in crypto"); + return sec_status_unchecked; + } + rsa = ldns_key_buf2rsa_raw(key, keylen); + if(!rsa) { + verbose(VERB_QUERY, "verify: " + "ldns_key_buf2rsa_raw MD5 failed"); + return 0; + } + if(EVP_PKEY_assign_RSA(*evp_key, rsa) == 0) { + verbose(VERB_QUERY, "verify: " + "EVP_PKEY_assign_RSA MD5 failed"); + return 0; + } + *digest_type = EVP_md5(); + + break; +#ifdef USE_GOST + case LDNS_ECC_GOST: + *evp_key = ldns_gost2pkey_raw(key, keylen); + if(!*evp_key) { + verbose(VERB_QUERY, "verify: " + "ldns_gost2pkey_raw failed"); + return 0; + } + *digest_type = EVP_get_digestbyname("md_gost94"); + if(!*digest_type) { + verbose(VERB_QUERY, "verify: " + "EVP_getdigest md_gost94 failed"); + return 0; + } + break; +#endif +#ifdef USE_ECDSA + case LDNS_ECDSAP256SHA256: + *evp_key = ldns_ecdsa2pkey_raw(key, keylen, + LDNS_ECDSAP256SHA256); + if(!*evp_key) { + verbose(VERB_QUERY, "verify: " + "ldns_ecdsa2pkey_raw failed"); + return 0; + } +#ifdef USE_ECDSA_EVP_WORKAROUND + /* openssl before 1.0.0 fixes RSA with the SHA256 + * hash in EVP. We create one for ecdsa_sha256 */ + { + static int md_ecdsa_256_done = 0; + static EVP_MD md; + if(!md_ecdsa_256_done) { + EVP_MD m = *EVP_sha256(); + md_ecdsa_256_done = 1; + m.required_pkey_type[0] = (*evp_key)->type; + m.verify = (void*)ECDSA_verify; + md = m; + } + *digest_type = &md; + } +#else + *digest_type = EVP_sha256(); +#endif + break; + case LDNS_ECDSAP384SHA384: + *evp_key = ldns_ecdsa2pkey_raw(key, keylen, + LDNS_ECDSAP384SHA384); + if(!*evp_key) { + verbose(VERB_QUERY, "verify: " + "ldns_ecdsa2pkey_raw failed"); + return 0; + } +#ifdef USE_ECDSA_EVP_WORKAROUND + /* openssl before 1.0.0 fixes RSA with the SHA384 + * hash in EVP. We create one for ecdsa_sha384 */ + { + static int md_ecdsa_384_done = 0; + static EVP_MD md; + if(!md_ecdsa_384_done) { + EVP_MD m = *EVP_sha384(); + md_ecdsa_384_done = 1; + m.required_pkey_type[0] = (*evp_key)->type; + m.verify = (void*)ECDSA_verify; + md = m; + } + *digest_type = &md; + } +#else + *digest_type = EVP_sha384(); +#endif + break; +#endif /* USE_ECDSA */ + default: + verbose(VERB_QUERY, "verify: unknown algorithm %d", + algo); + return 0; + } + return 1; +} + +/** + * Check a canonical sig+rrset and signature against a dnskey + * @param buf: buffer with data to verify, the first rrsig part and the + * canonicalized rrset. + * @param algo: DNSKEY algorithm. + * @param sigblock: signature rdata field from RRSIG + * @param sigblock_len: length of sigblock data. + * @param key: public key data from DNSKEY RR. + * @param keylen: length of keydata. + * @param reason: bogus reason in more detail. + * @return secure if verification succeeded, bogus on crypto failure, + * unchecked on format errors and alloc failures. + */ +static enum sec_status +verify_canonrrset(ldns_buffer* buf, int algo, unsigned char* sigblock, + unsigned int sigblock_len, unsigned char* key, unsigned int keylen, + char** reason) +{ + const EVP_MD *digest_type; + EVP_MD_CTX ctx; + int res, dofree = 0; + EVP_PKEY *evp_key = NULL; + + if(!setup_key_digest(algo, &evp_key, &digest_type, key, keylen)) { + verbose(VERB_QUERY, "verify: failed to setup key"); + *reason = "use of key for crypto failed"; + EVP_PKEY_free(evp_key); + return sec_status_bogus; + } + /* if it is a DSA signature in bind format, convert to DER format */ + if((algo == LDNS_DSA || algo == LDNS_DSA_NSEC3) && + sigblock_len == 1+2*SHA_DIGEST_LENGTH) { + if(!setup_dsa_sig(&sigblock, &sigblock_len)) { + verbose(VERB_QUERY, "verify: failed to setup DSA sig"); + *reason = "use of key for DSA crypto failed"; + EVP_PKEY_free(evp_key); + return sec_status_bogus; + } + dofree = 1; + } +#ifdef USE_ECDSA + else if(algo == LDNS_ECDSAP256SHA256 || algo == LDNS_ECDSAP384SHA384) { + /* EVP uses ASN prefix on sig, which is not in the wire data */ + if(!setup_ecdsa_sig(&sigblock, &sigblock_len)) { + verbose(VERB_QUERY, "verify: failed to setup ECDSA sig"); + *reason = "use of signature for ECDSA crypto failed"; + EVP_PKEY_free(evp_key); + return sec_status_bogus; + } + dofree = 1; + } +#endif /* USE_ECDSA */ + + /* do the signature cryptography work */ + EVP_MD_CTX_init(&ctx); + if(EVP_VerifyInit(&ctx, digest_type) == 0) { + verbose(VERB_QUERY, "verify: EVP_VerifyInit failed"); + EVP_PKEY_free(evp_key); + if(dofree) free(sigblock); + return sec_status_unchecked; + } + if(EVP_VerifyUpdate(&ctx, (unsigned char*)ldns_buffer_begin(buf), + (unsigned int)ldns_buffer_limit(buf)) == 0) { + verbose(VERB_QUERY, "verify: EVP_VerifyUpdate failed"); + EVP_PKEY_free(evp_key); + if(dofree) free(sigblock); + return sec_status_unchecked; + } + + res = EVP_VerifyFinal(&ctx, sigblock, sigblock_len, evp_key); + if(EVP_MD_CTX_cleanup(&ctx) == 0) { + verbose(VERB_QUERY, "verify: EVP_MD_CTX_cleanup failed"); + EVP_PKEY_free(evp_key); + if(dofree) free(sigblock); + return sec_status_unchecked; + } + EVP_PKEY_free(evp_key); + + if(dofree) + free(sigblock); + + if(res == 1) { + return sec_status_secure; + } else if(res == 0) { + verbose(VERB_QUERY, "verify: signature mismatch"); + *reason = "signature crypto failed"; + return sec_status_bogus; + } + + log_crypto_error("verify:", ERR_get_error()); + return sec_status_unchecked; +} + +enum sec_status +dnskey_verify_rrset_sig(struct regional* region, ldns_buffer* buf, + struct val_env* ve, uint32_t now, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, + size_t dnskey_idx, size_t sig_idx, + struct rbtree_t** sortree, int* buf_canon, char** reason) +{ + enum sec_status sec; + uint8_t* sig; /* RRSIG rdata */ + size_t siglen; + size_t rrnum = rrset_get_count(rrset); + uint8_t* signer; /* rrsig signer name */ + size_t signer_len; + unsigned char* sigblock; /* signature rdata field */ + unsigned int sigblock_len; + uint16_t ktag; /* DNSKEY key tag */ + unsigned char* key; /* public key rdata field */ + unsigned int keylen; + rrset_get_rdata(rrset, rrnum + sig_idx, &sig, &siglen); + /* min length of rdatalen, fixed rrsig, root signer, 1 byte sig */ + if(siglen < 2+20) { + verbose(VERB_QUERY, "verify: signature too short"); + *reason = "signature too short"; + return sec_status_bogus; + } + + if(!(dnskey_get_flags(dnskey, dnskey_idx) & DNSKEY_BIT_ZSK)) { + verbose(VERB_QUERY, "verify: dnskey without ZSK flag"); + *reason = "dnskey without ZSK flag"; + return sec_status_bogus; + } + + if(dnskey_get_protocol(dnskey, dnskey_idx) != LDNS_DNSSEC_KEYPROTO) { + /* RFC 4034 says DNSKEY PROTOCOL MUST be 3 */ + verbose(VERB_QUERY, "verify: dnskey has wrong key protocol"); + *reason = "dnskey has wrong protocolnumber"; + return sec_status_bogus; + } + + /* verify as many fields in rrsig as possible */ + signer = sig+2+18; + signer_len = dname_valid(signer, siglen-2-18); + if(!signer_len) { + verbose(VERB_QUERY, "verify: malformed signer name"); + *reason = "signer name malformed"; + return sec_status_bogus; /* signer name invalid */ + } + if(!dname_subdomain_c(rrset->rk.dname, signer)) { + verbose(VERB_QUERY, "verify: signer name is off-tree"); + *reason = "signer name off-tree"; + return sec_status_bogus; /* signer name offtree */ + } + sigblock = (unsigned char*)signer+signer_len; + if(siglen < 2+18+signer_len+1) { + verbose(VERB_QUERY, "verify: too short, no signature data"); + *reason = "signature too short, no signature data"; + return sec_status_bogus; /* sig rdf is < 1 byte */ + } + sigblock_len = (unsigned int)(siglen - 2 - 18 - signer_len); + + /* verify key dname == sig signer name */ + if(query_dname_compare(signer, dnskey->rk.dname) != 0) { + verbose(VERB_QUERY, "verify: wrong key for rrsig"); + log_nametypeclass(VERB_QUERY, "RRSIG signername is", + signer, 0, 0); + log_nametypeclass(VERB_QUERY, "the key name is", + dnskey->rk.dname, 0, 0); + *reason = "signer name mismatches key name"; + return sec_status_bogus; + } + + /* verify covered type */ + /* memcmp works because type is in network format for rrset */ + if(memcmp(sig+2, &rrset->rk.type, 2) != 0) { + verbose(VERB_QUERY, "verify: wrong type covered"); + *reason = "signature covers wrong type"; + return sec_status_bogus; + } + /* verify keytag and sig algo (possibly again) */ + if((int)sig[2+2] != dnskey_get_algo(dnskey, dnskey_idx)) { + verbose(VERB_QUERY, "verify: wrong algorithm"); + *reason = "signature has wrong algorithm"; + return sec_status_bogus; + } + ktag = htons(dnskey_calc_keytag(dnskey, dnskey_idx)); + if(memcmp(sig+2+16, &ktag, 2) != 0) { + verbose(VERB_QUERY, "verify: wrong keytag"); + *reason = "signature has wrong keytag"; + return sec_status_bogus; + } + + /* verify labels is in a valid range */ + if((int)sig[2+3] > dname_signame_label_count(rrset->rk.dname)) { + verbose(VERB_QUERY, "verify: labelcount out of range"); + *reason = "signature labelcount out of range"; + return sec_status_bogus; + } + + /* original ttl, always ok */ + + if(!*buf_canon) { + /* create rrset canonical format in buffer, ready for + * signature */ + if(!rrset_canonical(region, buf, rrset, sig+2, + 18 + signer_len, sortree)) { + log_err("verify: failed due to alloc error"); + return sec_status_unchecked; + } + *buf_canon = 1; + } + + /* check that dnskey is available */ + dnskey_get_pubkey(dnskey, dnskey_idx, &key, &keylen); + if(!key) { + verbose(VERB_QUERY, "verify: short DNSKEY RR"); + return sec_status_unchecked; + } + + /* verify */ + sec = verify_canonrrset(buf, (int)sig[2+2], + sigblock, sigblock_len, key, keylen, reason); + + if(sec == sec_status_secure) { + /* check if TTL is too high - reduce if so */ + adjust_ttl(ve, now, rrset, sig+2+4, sig+2+8, sig+2+12); + + /* verify inception, expiration dates + * Do this last so that if you ignore expired-sigs the + * rest is sure to be OK. */ + if(!check_dates(ve, now, sig+2+8, sig+2+12, reason)) { + return sec_status_bogus; + } + } + + return sec; +} diff --git a/validator/val_sigcrypt.h b/validator/val_sigcrypt.h new file mode 100644 index 0000000000000..c220b0083ac3d --- /dev/null +++ b/validator/val_sigcrypt.h @@ -0,0 +1,311 @@ +/* + * validator/val_sigcrypt.h - validator signature crypto functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + * The functions help with signature verification and checking, the + * bridging between RR wireformat data and crypto calls. + */ + +#ifndef VALIDATOR_VAL_SIGCRYPT_H +#define VALIDATOR_VAL_SIGCRYPT_H +#include "util/data/packed_rrset.h" +struct val_env; +struct module_env; +struct ub_packed_rrset_key; +struct rbtree_t; +struct regional; + +/** number of entries in algorithm needs array */ +#define ALGO_NEEDS_MAX 256 + +/** + * Storage for algorithm needs. DNSKEY algorithms. + */ +struct algo_needs { + /** the algorithms (8-bit) with each a number. + * 0: not marked. + * 1: marked 'necessary but not yet fulfilled' + * 2: marked bogus. + * Indexed by algorithm number. + */ + uint8_t needs[ALGO_NEEDS_MAX]; + /** the number of entries in the array that are unfulfilled */ + size_t num; +}; + +/** + * Initialize algo needs structure, set algos from rrset as needed. + * Results are added to an existing need structure. + * @param n: struct with storage. + * @param dnskey: algos from this struct set as necessary. DNSKEY set. + * @param sigalg: adds to signalled algorithm list too. + */ +void algo_needs_init_dnskey_add(struct algo_needs* n, + struct ub_packed_rrset_key* dnskey, uint8_t* sigalg); + +/** + * Initialize algo needs structure from a signalled algo list. + * @param n: struct with storage. + * @param sigalg: signalled algorithm list, numbers ends with 0. + */ +void algo_needs_init_list(struct algo_needs* n, uint8_t* sigalg); + +/** + * Initialize algo needs structure, set algos from rrset as needed. + * @param n: struct with storage. + * @param ds: algos from this struct set as necessary. DS set. + * @param fav_ds_algo: filter to use only this DS algo. + * @param sigalg: list of signalled algos, constructed as output, + * provide size ALGO_NEEDS_MAX+1. list of algonumbers, ends with a zero. + */ +void algo_needs_init_ds(struct algo_needs* n, struct ub_packed_rrset_key* ds, + int fav_ds_algo, uint8_t* sigalg); + +/** + * Mark this algorithm as a success, sec_secure, and see if we are done. + * @param n: storage structure processed. + * @param algo: the algorithm processed to be secure. + * @return if true, processing has finished successfully, we are satisfied. + */ +int algo_needs_set_secure(struct algo_needs* n, uint8_t algo); + +/** + * Mark this algorithm a failure, sec_bogus. It can later be overridden + * by a success for this algorithm (with a different signature). + * @param n: storage structure processed. + * @param algo: the algorithm processed to be bogus. + */ +void algo_needs_set_bogus(struct algo_needs* n, uint8_t algo); + +/** + * See how many algorithms are missing (not bogus or secure, but not processed) + * @param n: storage structure processed. + * @return number of algorithms missing after processing. + */ +size_t algo_needs_num_missing(struct algo_needs* n); + +/** + * See which algo is missing. + * @param n: struct after processing. + * @return if 0 an algorithm was bogus, if a number, this algorithm was + * missing. So on 0, report why that was bogus, on number report a missing + * algorithm. There could be multiple missing, this reports the first one. + */ +int algo_needs_missing(struct algo_needs* n); + +/** + * Format error reason for algorithm missing. + * @param env: module env with scratch for temp storage of string. + * @param alg: DNSKEY-algorithm missing. + * @param reason: destination. + * @param s: string, appended with 'with algorithm ..'. + */ +void algo_needs_reason(struct module_env* env, int alg, char** reason, char* s); + +/** + * Check if dnskey matches a DS digest + * Does not check dnskey-keyid footprint, just the digest. + * @param env: module environment. Uses scratch space. + * @param dnskey_rrset: DNSKEY rrset. + * @param dnskey_idx: index of RR in rrset. + * @param ds_rrset: DS rrset + * @param ds_idx: index of RR in DS rrset. + * @return true if it matches, false on error, not supported or no match. + */ +int ds_digest_match_dnskey(struct module_env* env, + struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, + struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); + +/** + * Get dnskey keytag, footprint value + * @param dnskey_rrset: DNSKEY rrset. + * @param dnskey_idx: index of RR in rrset. + * @return the keytag or 0 for badly formatted DNSKEYs. + */ +uint16_t dnskey_calc_keytag(struct ub_packed_rrset_key* dnskey_rrset, + size_t dnskey_idx); + +/** + * Get DS keytag, footprint value that matches the DNSKEY keytag it signs. + * @param ds_rrset: DS rrset + * @param ds_idx: index of RR in DS rrset. + * @return the keytag or 0 for badly formatted DSs. + */ +uint16_t ds_get_keytag(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); + +/** + * See if DNSKEY algorithm is supported + * @param dnskey_rrset: DNSKEY rrset. + * @param dnskey_idx: index of RR in rrset. + * @return true if supported. + */ +int dnskey_algo_is_supported(struct ub_packed_rrset_key* dnskey_rrset, + size_t dnskey_idx); + +/** + * See if DS digest algorithm is supported + * @param ds_rrset: DS rrset + * @param ds_idx: index of RR in DS rrset. + * @return true if supported. + */ +int ds_digest_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, + size_t ds_idx); + +/** + * Get DS RR digest algorithm + * @param ds_rrset: DS rrset. + * @param ds_idx: which DS. + * @return algorithm or 0 if DS too short. + */ +int ds_get_digest_algo(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); + +/** + * See if DS key algorithm is supported + * @param ds_rrset: DS rrset + * @param ds_idx: index of RR in DS rrset. + * @return true if supported. + */ +int ds_key_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, + size_t ds_idx); + +/** + * Get DS RR key algorithm. This value should match with the DNSKEY algo. + * @param k: DS rrset. + * @param idx: which DS. + * @return algorithm or 0 if DS too short. + */ +int ds_get_key_algo(struct ub_packed_rrset_key* k, size_t idx); + +/** + * Get DNSKEY RR signature algorithm + * @param k: DNSKEY rrset. + * @param idx: which DNSKEY RR. + * @return algorithm or 0 if DNSKEY too short. + */ +int dnskey_get_algo(struct ub_packed_rrset_key* k, size_t idx); + +/** + * Get DNSKEY RR flags + * @param k: DNSKEY rrset. + * @param idx: which DNSKEY RR. + * @return flags or 0 if DNSKEY too short. + */ +uint16_t dnskey_get_flags(struct ub_packed_rrset_key* k, size_t idx); + +/** + * Verify rrset against dnskey rrset. + * @param env: module environment, scratch space is used. + * @param ve: validator environment, date settings. + * @param rrset: to be validated. + * @param dnskey: DNSKEY rrset, keyset to try. + * @param sigalg: if nonNULL provide downgrade protection otherwise one + * algorithm is enough. + * @param reason: if bogus, a string returned, fixed or alloced in scratch. + * @return SECURE if one key in the set verifies one rrsig. + * UNCHECKED on allocation errors, unsupported algorithms, malformed data, + * and BOGUS on verification failures (no keys match any signatures). + */ +enum sec_status dnskeyset_verify_rrset(struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* rrset, + struct ub_packed_rrset_key* dnskey, uint8_t* sigalg, char** reason); + +/** + * verify rrset against one specific dnskey (from rrset) + * @param env: module environment, scratch space is used. + * @param ve: validator environment, date settings. + * @param rrset: to be validated. + * @param dnskey: DNSKEY rrset, keyset. + * @param dnskey_idx: which key from the rrset to try. + * @param reason: if bogus, a string returned, fixed or alloced in scratch. + * @return secure if *this* key signs any of the signatures on rrset. + * unchecked on error or and bogus on bad signature. + */ +enum sec_status dnskey_verify_rrset(struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* rrset, + struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, char** reason); + +/** + * verify rrset, with dnskey rrset, for a specific rrsig in rrset + * @param env: module environment, scratch space is used. + * @param ve: validator environment, date settings. + * @param now: current time for validation (can be overridden). + * @param rrset: to be validated. + * @param dnskey: DNSKEY rrset, keyset to try. + * @param sig_idx: which signature to try to validate. + * @param sortree: reused sorted order. Stored in region. Pass NULL at start, + * and for a new rrset. + * @param reason: if bogus, a string returned, fixed or alloced in scratch. + * @return secure if any key signs *this* signature. bogus if no key signs it, + * or unchecked on error. + */ +enum sec_status dnskeyset_verify_rrset_sig(struct module_env* env, + struct val_env* ve, uint32_t now, struct ub_packed_rrset_key* rrset, + struct ub_packed_rrset_key* dnskey, size_t sig_idx, + struct rbtree_t** sortree, char** reason); + +/** + * verify rrset, with specific dnskey(from set), for a specific rrsig + * @param region: scratch region used for temporary allocation. + * @param buf: scratch buffer used for canonicalized rrset data. + * @param ve: validator environment, date settings. + * @param now: current time for validation (can be overridden). + * @param rrset: to be validated. + * @param dnskey: DNSKEY rrset, keyset. + * @param dnskey_idx: which key from the rrset to try. + * @param sig_idx: which signature to try to validate. + * @param sortree: pass NULL at start, the sorted rrset order is returned. + * pass it again for the same rrset. + * @param buf_canon: if true, the buffer is already canonical. + * pass false at start. pass old value only for same rrset and same + * signature (but perhaps different key) for reuse. + * @param reason: if bogus, a string returned, fixed or alloced in scratch. + * @return secure if this key signs this signature. unchecked on error or + * bogus if it did not validate. + */ +enum sec_status dnskey_verify_rrset_sig(struct regional* region, + ldns_buffer* buf, struct val_env* ve, uint32_t now, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, + size_t dnskey_idx, size_t sig_idx, + struct rbtree_t** sortree, int* buf_canon, char** reason); + +/** + * canonical compare for two tree entries + */ +int canonical_tree_compare(const void* k1, const void* k2); + +#endif /* VALIDATOR_VAL_SIGCRYPT_H */ diff --git a/validator/val_utils.c b/validator/val_utils.c new file mode 100644 index 0000000000000..b0475d8031c4b --- /dev/null +++ b/validator/val_utils.c @@ -0,0 +1,1082 @@ +/* + * validator/val_utils.c - validator utility functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + */ +#include "config.h" +#include "validator/val_utils.h" +#include "validator/validator.h" +#include "validator/val_kentry.h" +#include "validator/val_sigcrypt.h" +#include "validator/val_anchor.h" +#include "validator/val_nsec.h" +#include "validator/val_neg.h" +#include "services/cache/rrset.h" +#include "services/cache/dns.h" +#include "util/data/msgreply.h" +#include "util/data/packed_rrset.h" +#include "util/data/dname.h" +#include "util/net_help.h" +#include "util/module.h" +#include "util/regional.h" +#include "util/config_file.h" + +enum val_classification +val_classify_response(uint16_t query_flags, struct query_info* origqinf, + struct query_info* qinf, struct reply_info* rep, size_t skip) +{ + int rcode = (int)FLAGS_GET_RCODE(rep->flags); + size_t i; + + /* Normal Name Error's are easy to detect -- but don't mistake a CNAME + * chain ending in NXDOMAIN. */ + if(rcode == LDNS_RCODE_NXDOMAIN && rep->an_numrrsets == 0) + return VAL_CLASS_NAMEERROR; + + /* check for referral: nonRD query and it looks like a nodata */ + if(!(query_flags&BIT_RD) && rep->an_numrrsets == 0 && + rcode == LDNS_RCODE_NOERROR) { + /* SOA record in auth indicates it is NODATA instead. + * All validation requiring NODATA messages have SOA in + * authority section. */ + /* uses fact that answer section is empty */ + int saw_ns = 0; + for(i=0; i<rep->ns_numrrsets; i++) { + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_SOA) + return VAL_CLASS_NODATA; + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_DS) + return VAL_CLASS_REFERRAL; + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS) + saw_ns = 1; + } + return saw_ns?VAL_CLASS_REFERRAL:VAL_CLASS_NODATA; + } + /* root referral where NS set is in the answer section */ + if(!(query_flags&BIT_RD) && rep->ns_numrrsets == 0 && + rep->an_numrrsets == 1 && rcode == LDNS_RCODE_NOERROR && + ntohs(rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_NS && + query_dname_compare(rep->rrsets[0]->rk.dname, + origqinf->qname) != 0) + return VAL_CLASS_REFERRAL; + + /* dump bad messages */ + if(rcode != LDNS_RCODE_NOERROR && rcode != LDNS_RCODE_NXDOMAIN) + return VAL_CLASS_UNKNOWN; + /* next check if the skip into the answer section shows no answer */ + if(skip>0 && rep->an_numrrsets <= skip) + return VAL_CLASS_CNAMENOANSWER; + + /* Next is NODATA */ + if(rcode == LDNS_RCODE_NOERROR && rep->an_numrrsets == 0) + return VAL_CLASS_NODATA; + + /* We distinguish between CNAME response and other positive/negative + * responses because CNAME answers require extra processing. */ + + /* We distinguish between ANY and CNAME or POSITIVE because + * ANY responses are validated differently. */ + if(rcode == LDNS_RCODE_NOERROR && qinf->qtype == LDNS_RR_TYPE_ANY) + return VAL_CLASS_ANY; + + /* Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless + * qtype=CNAME, this will yield a CNAME response. */ + for(i=skip; i<rep->an_numrrsets; i++) { + if(rcode == LDNS_RCODE_NOERROR && + ntohs(rep->rrsets[i]->rk.type) == qinf->qtype) + return VAL_CLASS_POSITIVE; + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME) + return VAL_CLASS_CNAME; + } + log_dns_msg("validator: error. failed to classify response message: ", + qinf, rep); + return VAL_CLASS_UNKNOWN; +} + +/** Get signer name from RRSIG */ +static void +rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, size_t* slen) +{ + /* RRSIG rdata is not allowed to be compressed, it is stored + * uncompressed in memory as well, so return a ptr to the name */ + if(len < 21) { + /* too short RRSig: + * short, byte, byte, long, long, long, short, "." is + * 2 1 1 4 4 4 2 1 = 19 + * and a skip of 18 bytes to the name. + * +2 for the rdatalen is 21 bytes len for root label */ + *sname = NULL; + *slen = 0; + return; + } + data += 20; /* skip the fixed size bits */ + len -= 20; + *slen = dname_valid(data, len); + if(!*slen) { + /* bad dname in this rrsig. */ + *sname = NULL; + return; + } + *sname = data; +} + +void +val_find_rrset_signer(struct ub_packed_rrset_key* rrset, uint8_t** sname, + size_t* slen) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + /* return signer for first signature, or NULL */ + if(d->rrsig_count == 0) { + *sname = NULL; + *slen = 0; + return; + } + /* get rrsig signer name out of the signature */ + rrsig_get_signer(d->rr_data[d->count], d->rr_len[d->count], + sname, slen); +} + +/** + * Find best signer name in this set of rrsigs. + * @param rrset: which rrsigs to look through. + * @param qinf: the query name that needs validation. + * @param signer_name: the best signer_name. Updated if a better one is found. + * @param signer_len: length of signer name. + * @param matchcount: count of current best name (starts at 0 for no match). + * Updated if match is improved. + */ +static void +val_find_best_signer(struct ub_packed_rrset_key* rrset, + struct query_info* qinf, uint8_t** signer_name, size_t* signer_len, + int* matchcount) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + uint8_t* sign; + size_t i; + int m; + for(i=d->count; i<d->count+d->rrsig_count; i++) { + sign = d->rr_data[i]+2+18; + /* look at signatures that are valid (long enough), + * and have a signer name that is a superdomain of qname, + * and then check the number of labels in the shared topdomain + * improve the match if possible */ + if(d->rr_len[i] > 2+19 && /* rdata, sig + root label*/ + dname_subdomain_c(qinf->qname, sign)) { + (void)dname_lab_cmp(qinf->qname, + dname_count_labels(qinf->qname), + sign, dname_count_labels(sign), &m); + if(m > *matchcount) { + *matchcount = m; + *signer_name = sign; + (void)dname_count_size_labels(*signer_name, + signer_len); + } + } + } +} + +void +val_find_signer(enum val_classification subtype, struct query_info* qinf, + struct reply_info* rep, size_t skip, uint8_t** signer_name, + size_t* signer_len) +{ + size_t i; + + if(subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_ANY) { + /* check for the answer rrset */ + for(i=skip; i<rep->an_numrrsets; i++) { + if(query_dname_compare(qinf->qname, + rep->rrsets[i]->rk.dname) == 0) { + val_find_rrset_signer(rep->rrsets[i], + signer_name, signer_len); + return; + } + } + *signer_name = NULL; + *signer_len = 0; + } else if(subtype == VAL_CLASS_CNAME) { + /* check for the first signed cname/dname rrset */ + for(i=skip; i<rep->an_numrrsets; i++) { + val_find_rrset_signer(rep->rrsets[i], + signer_name, signer_len); + if(*signer_name) + return; + if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_DNAME) + break; /* only check CNAME after a DNAME */ + } + *signer_name = NULL; + *signer_len = 0; + } else if(subtype == VAL_CLASS_NAMEERROR + || subtype == VAL_CLASS_NODATA) { + /*Check to see if the AUTH section NSEC record(s) have rrsigs*/ + for(i=rep->an_numrrsets; i< + rep->an_numrrsets+rep->ns_numrrsets; i++) { + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC + || ntohs(rep->rrsets[i]->rk.type) == + LDNS_RR_TYPE_NSEC3) { + val_find_rrset_signer(rep->rrsets[i], + signer_name, signer_len); + return; + } + } + } else if(subtype == VAL_CLASS_CNAMENOANSWER) { + /* find closest superdomain signer name in authority section + * NSEC and NSEC3s */ + int matchcount = 0; + *signer_name = NULL; + *signer_len = 0; + for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep-> + ns_numrrsets; i++) { + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC + || ntohs(rep->rrsets[i]->rk.type) == + LDNS_RR_TYPE_NSEC3) { + val_find_best_signer(rep->rrsets[i], qinf, + signer_name, signer_len, &matchcount); + } + } + } else if(subtype == VAL_CLASS_REFERRAL) { + /* find keys for the item at skip */ + if(skip < rep->rrset_count) { + val_find_rrset_signer(rep->rrsets[skip], + signer_name, signer_len); + return; + } + *signer_name = NULL; + *signer_len = 0; + } else { + verbose(VERB_QUERY, "find_signer: could not find signer name" + " for unknown type response"); + *signer_name = NULL; + *signer_len = 0; + } +} + +/** return number of rrs in an rrset */ +static size_t +rrset_get_count(struct ub_packed_rrset_key* rrset) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + if(!d) return 0; + return d->count; +} + +/** return TTL of rrset */ +static uint32_t +rrset_get_ttl(struct ub_packed_rrset_key* rrset) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + if(!d) return 0; + return d->ttl; +} + +enum sec_status +val_verify_rrset(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys, + uint8_t* sigalg, char** reason) +{ + enum sec_status sec; + struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> + entry.data; + if(d->security == sec_status_secure) { + /* re-verify all other statuses, because keyset may change*/ + log_nametypeclass(VERB_ALGO, "verify rrset cached", + rrset->rk.dname, ntohs(rrset->rk.type), + ntohs(rrset->rk.rrset_class)); + return d->security; + } + /* check in the cache if verification has already been done */ + rrset_check_sec_status(env->rrset_cache, rrset, *env->now); + if(d->security == sec_status_secure) { + log_nametypeclass(VERB_ALGO, "verify rrset from cache", + rrset->rk.dname, ntohs(rrset->rk.type), + ntohs(rrset->rk.rrset_class)); + return d->security; + } + log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname, + ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); + sec = dnskeyset_verify_rrset(env, ve, rrset, keys, sigalg, reason); + verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec)); + regional_free_all(env->scratch); + + /* update rrset security status + * only improves security status + * and bogus is set only once, even if we rechecked the status */ + if(sec > d->security) { + d->security = sec; + if(sec == sec_status_secure) + d->trust = rrset_trust_validated; + else if(sec == sec_status_bogus) { + size_t i; + /* update ttl for rrset to fixed value. */ + d->ttl = ve->bogus_ttl; + for(i=0; i<d->count+d->rrsig_count; i++) + d->rr_ttl[i] = ve->bogus_ttl; + /* leave RR specific TTL: not used for determine + * if RRset timed out and clients see proper value. */ + lock_basic_lock(&ve->bogus_lock); + ve->num_rrset_bogus++; + lock_basic_unlock(&ve->bogus_lock); + } + /* if status updated - store in cache for reuse */ + rrset_update_sec_status(env->rrset_cache, rrset, *env->now); + } + + return sec; +} + +enum sec_status +val_verify_rrset_entry(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct key_entry_key* kkey, + char** reason) +{ + /* temporary dnskey rrset-key */ + struct ub_packed_rrset_key dnskey; + struct key_entry_data* kd = (struct key_entry_data*)kkey->entry.data; + enum sec_status sec; + dnskey.rk.type = htons(kd->rrset_type); + dnskey.rk.rrset_class = htons(kkey->key_class); + dnskey.rk.flags = 0; + dnskey.rk.dname = kkey->name; + dnskey.rk.dname_len = kkey->namelen; + dnskey.entry.key = &dnskey; + dnskey.entry.data = kd->rrset_data; + sec = val_verify_rrset(env, ve, rrset, &dnskey, kd->algo, reason); + return sec; +} + +/** verify that a DS RR hashes to a key and that key signs the set */ +static enum sec_status +verify_dnskeys_with_ds_rr(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, char** reason) +{ + enum sec_status sec = sec_status_bogus; + size_t i, num, numchecked = 0, numhashok = 0; + num = rrset_get_count(dnskey_rrset); + for(i=0; i<num; i++) { + /* Skip DNSKEYs that don't match the basic criteria. */ + if(ds_get_key_algo(ds_rrset, ds_idx) + != dnskey_get_algo(dnskey_rrset, i) + || dnskey_calc_keytag(dnskey_rrset, i) + != ds_get_keytag(ds_rrset, ds_idx)) { + continue; + } + numchecked++; + verbose(VERB_ALGO, "attempt DS match algo %d keytag %d", + ds_get_key_algo(ds_rrset, ds_idx), + ds_get_keytag(ds_rrset, ds_idx)); + + /* Convert the candidate DNSKEY into a hash using the + * same DS hash algorithm. */ + if(!ds_digest_match_dnskey(env, dnskey_rrset, i, ds_rrset, + ds_idx)) { + verbose(VERB_ALGO, "DS match attempt failed"); + continue; + } + numhashok++; + verbose(VERB_ALGO, "DS match digest ok, trying signature"); + + /* Otherwise, we have a match! Make sure that the DNSKEY + * verifies *with this key* */ + sec = dnskey_verify_rrset(env, ve, dnskey_rrset, + dnskey_rrset, i, reason); + if(sec == sec_status_secure) { + return sec; + } + /* If it didn't validate with the DNSKEY, try the next one! */ + } + if(numchecked == 0) + algo_needs_reason(env, ds_get_key_algo(ds_rrset, ds_idx), + reason, "no keys have a DS"); + else if(numhashok == 0) + *reason = "DS hash mismatches key"; + else if(!*reason) + *reason = "keyset not secured by DNSKEY that matches DS"; + return sec_status_bogus; +} + +int val_favorite_ds_algo(struct ub_packed_rrset_key* ds_rrset) +{ + size_t i, num = rrset_get_count(ds_rrset); + int d, digest_algo = 0; /* DS digest algo 0 is not used. */ + /* find favorite algo, for now, highest number supported */ + for(i=0; i<num; i++) { + if(!ds_digest_algo_is_supported(ds_rrset, i) || + !ds_key_algo_is_supported(ds_rrset, i)) { + continue; + } + d = ds_get_digest_algo(ds_rrset, i); + if(d > digest_algo) + digest_algo = d; + } + return digest_algo; +} + +enum sec_status +val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** reason) +{ + /* as long as this is false, we can consider this DS rrset to be + * equivalent to no DS rrset. */ + int has_useful_ds = 0, digest_algo, alg; + struct algo_needs needs; + size_t i, num; + enum sec_status sec; + + if(dnskey_rrset->rk.dname_len != ds_rrset->rk.dname_len || + query_dname_compare(dnskey_rrset->rk.dname, ds_rrset->rk.dname) + != 0) { + verbose(VERB_QUERY, "DNSKEY RRset did not match DS RRset " + "by name"); + *reason = "DNSKEY RRset did not match DS RRset by name"; + return sec_status_bogus; + } + + digest_algo = val_favorite_ds_algo(ds_rrset); + if(sigalg) + algo_needs_init_ds(&needs, ds_rrset, digest_algo, sigalg); + num = rrset_get_count(ds_rrset); + for(i=0; i<num; i++) { + /* Check to see if we can understand this DS. + * And check it is the strongest digest */ + if(!ds_digest_algo_is_supported(ds_rrset, i) || + !ds_key_algo_is_supported(ds_rrset, i) || + ds_get_digest_algo(ds_rrset, i) != digest_algo) { + continue; + } + + /* Once we see a single DS with a known digestID and + * algorithm, we cannot return INSECURE (with a + * "null" KeyEntry). */ + has_useful_ds = true; + + sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, + ds_rrset, i, reason); + if(sec == sec_status_secure) { + if(!sigalg || algo_needs_set_secure(&needs, + (uint8_t)ds_get_key_algo(ds_rrset, i))) { + verbose(VERB_ALGO, "DS matched DNSKEY."); + return sec_status_secure; + } + } else if(sigalg && sec == sec_status_bogus) { + algo_needs_set_bogus(&needs, + (uint8_t)ds_get_key_algo(ds_rrset, i)); + } + } + + /* None of the DS's worked out. */ + + /* If no DSs were understandable, then this is OK. */ + if(!has_useful_ds) { + verbose(VERB_ALGO, "No usable DS records were found -- " + "treating as insecure."); + return sec_status_insecure; + } + /* If any were understandable, then it is bad. */ + verbose(VERB_QUERY, "Failed to match any usable DS to a DNSKEY."); + if(sigalg && (alg=algo_needs_missing(&needs)) != 0) { + algo_needs_reason(env, alg, reason, "missing verification of " + "DNSKEY signature"); + } + return sec_status_bogus; +} + +struct key_entry_key* +val_verify_new_DNSKEYs(struct regional* region, struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ds_rrset, int downprot, char** reason) +{ + uint8_t sigalg[ALGO_NEEDS_MAX+1]; + enum sec_status sec = val_verify_DNSKEY_with_DS(env, ve, + dnskey_rrset, ds_rrset, downprot?sigalg:NULL, reason); + + if(sec == sec_status_secure) { + return key_entry_create_rrset(region, + ds_rrset->rk.dname, ds_rrset->rk.dname_len, + ntohs(ds_rrset->rk.rrset_class), dnskey_rrset, + downprot?sigalg:NULL, *env->now); + } else if(sec == sec_status_insecure) { + return key_entry_create_null(region, ds_rrset->rk.dname, + ds_rrset->rk.dname_len, + ntohs(ds_rrset->rk.rrset_class), + rrset_get_ttl(ds_rrset), *env->now); + } + return key_entry_create_bad(region, ds_rrset->rk.dname, + ds_rrset->rk.dname_len, ntohs(ds_rrset->rk.rrset_class), + BOGUS_KEY_TTL, *env->now); +} + +enum sec_status +val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ta_ds, + struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason) +{ + /* as long as this is false, we can consider this anchor to be + * equivalent to no anchor. */ + int has_useful_ta = 0, digest_algo = 0, alg; + struct algo_needs needs; + size_t i, num; + enum sec_status sec; + + if(ta_ds && (dnskey_rrset->rk.dname_len != ta_ds->rk.dname_len || + query_dname_compare(dnskey_rrset->rk.dname, ta_ds->rk.dname) + != 0)) { + verbose(VERB_QUERY, "DNSKEY RRset did not match DS RRset " + "by name"); + *reason = "DNSKEY RRset did not match DS RRset by name"; + return sec_status_bogus; + } + if(ta_dnskey && (dnskey_rrset->rk.dname_len != ta_dnskey->rk.dname_len + || query_dname_compare(dnskey_rrset->rk.dname, ta_dnskey->rk.dname) + != 0)) { + verbose(VERB_QUERY, "DNSKEY RRset did not match anchor RRset " + "by name"); + *reason = "DNSKEY RRset did not match anchor RRset by name"; + return sec_status_bogus; + } + + if(ta_ds) + digest_algo = val_favorite_ds_algo(ta_ds); + if(sigalg) { + if(ta_ds) + algo_needs_init_ds(&needs, ta_ds, digest_algo, sigalg); + else memset(&needs, 0, sizeof(needs)); + if(ta_dnskey) + algo_needs_init_dnskey_add(&needs, ta_dnskey, sigalg); + } + if(ta_ds) { + num = rrset_get_count(ta_ds); + for(i=0; i<num; i++) { + /* Check to see if we can understand this DS. + * And check it is the strongest digest */ + if(!ds_digest_algo_is_supported(ta_ds, i) || + !ds_key_algo_is_supported(ta_ds, i) || + ds_get_digest_algo(ta_ds, i) != digest_algo) + continue; + + /* Once we see a single DS with a known digestID and + * algorithm, we cannot return INSECURE (with a + * "null" KeyEntry). */ + has_useful_ta = true; + + sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, + ta_ds, i, reason); + if(sec == sec_status_secure) { + if(!sigalg || algo_needs_set_secure(&needs, + (uint8_t)ds_get_key_algo(ta_ds, i))) { + verbose(VERB_ALGO, "DS matched DNSKEY."); + return sec_status_secure; + } + } else if(sigalg && sec == sec_status_bogus) { + algo_needs_set_bogus(&needs, + (uint8_t)ds_get_key_algo(ta_ds, i)); + } + } + } + + /* None of the DS's worked out: check the DNSKEYs. */ + if(ta_dnskey) { + num = rrset_get_count(ta_dnskey); + for(i=0; i<num; i++) { + /* Check to see if we can understand this DNSKEY */ + if(!dnskey_algo_is_supported(ta_dnskey, i)) + continue; + + /* we saw a useful TA */ + has_useful_ta = true; + + sec = dnskey_verify_rrset(env, ve, dnskey_rrset, + ta_dnskey, i, reason); + if(sec == sec_status_secure) { + if(!sigalg || algo_needs_set_secure(&needs, + (uint8_t)dnskey_get_algo(ta_dnskey, i))) { + verbose(VERB_ALGO, "anchor matched DNSKEY."); + return sec_status_secure; + } + } else if(sigalg && sec == sec_status_bogus) { + algo_needs_set_bogus(&needs, + (uint8_t)dnskey_get_algo(ta_dnskey, i)); + } + } + } + + /* If no DSs were understandable, then this is OK. */ + if(!has_useful_ta) { + verbose(VERB_ALGO, "No usable trust anchors were found -- " + "treating as insecure."); + return sec_status_insecure; + } + /* If any were understandable, then it is bad. */ + verbose(VERB_QUERY, "Failed to match any usable anchor to a DNSKEY."); + if(sigalg && (alg=algo_needs_missing(&needs)) != 0) { + algo_needs_reason(env, alg, reason, "missing verification of " + "DNSKEY signature"); + } + return sec_status_bogus; +} + +struct key_entry_key* +val_verify_new_DNSKEYs_with_ta(struct regional* region, struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ta_ds_rrset, + struct ub_packed_rrset_key* ta_dnskey_rrset, int downprot, + char** reason) +{ + uint8_t sigalg[ALGO_NEEDS_MAX+1]; + enum sec_status sec = val_verify_DNSKEY_with_TA(env, ve, + dnskey_rrset, ta_ds_rrset, ta_dnskey_rrset, + downprot?sigalg:NULL, reason); + + if(sec == sec_status_secure) { + return key_entry_create_rrset(region, + dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len, + ntohs(dnskey_rrset->rk.rrset_class), dnskey_rrset, + downprot?sigalg:NULL, *env->now); + } else if(sec == sec_status_insecure) { + return key_entry_create_null(region, dnskey_rrset->rk.dname, + dnskey_rrset->rk.dname_len, + ntohs(dnskey_rrset->rk.rrset_class), + rrset_get_ttl(dnskey_rrset), *env->now); + } + return key_entry_create_bad(region, dnskey_rrset->rk.dname, + dnskey_rrset->rk.dname_len, ntohs(dnskey_rrset->rk.rrset_class), + BOGUS_KEY_TTL, *env->now); +} + +int +val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset) +{ + size_t i; + for(i=0; i<rrset_get_count(ds_rrset); i++) { + if(ds_digest_algo_is_supported(ds_rrset, i) && + ds_key_algo_is_supported(ds_rrset, i)) + return 1; + } + return 0; +} + +/** get label count for a signature */ +static uint8_t +rrsig_get_labcount(struct packed_rrset_data* d, size_t sig) +{ + if(d->rr_len[sig] < 2+4) + return 0; /* bad sig length */ + return d->rr_data[sig][2+3]; +} + +int +val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> + entry.data; + uint8_t labcount; + int labdiff; + uint8_t* wn; + size_t i, wl; + if(d->rrsig_count == 0) { + return 1; + } + labcount = rrsig_get_labcount(d, d->count + 0); + /* check rest of signatures identical */ + for(i=1; i<d->rrsig_count; i++) { + if(labcount != rrsig_get_labcount(d, d->count + i)) { + return 0; + } + } + /* OK the rrsigs check out */ + /* if the RRSIG label count is shorter than the number of actual + * labels, then this rrset was synthesized from a wildcard. + * Note that the RRSIG label count doesn't count the root label. */ + wn = rrset->rk.dname; + wl = rrset->rk.dname_len; + /* skip a leading wildcard label in the dname (RFC4035 2.2) */ + if(dname_is_wild(wn)) { + wn += 2; + wl -= 2; + } + labdiff = (dname_count_labels(wn) - 1) - (int)labcount; + if(labdiff > 0) { + *wc = wn; + dname_remove_labels(wc, &wl, labdiff); + return 1; + } + return 1; +} + +int +val_chase_cname(struct query_info* qchase, struct reply_info* rep, + size_t* cname_skip) { + size_t i; + /* skip any DNAMEs, go to the CNAME for next part */ + for(i = *cname_skip; i < rep->an_numrrsets; i++) { + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME && + query_dname_compare(qchase->qname, rep->rrsets[i]-> + rk.dname) == 0) { + qchase->qname = NULL; + get_cname_target(rep->rrsets[i], &qchase->qname, + &qchase->qname_len); + if(!qchase->qname) + return 0; /* bad CNAME rdata */ + (*cname_skip) = i+1; + return 1; + } + } + return 0; /* CNAME classified but no matching CNAME ?! */ +} + +/** see if rrset has signer name as one of the rrsig signers */ +static int +rrset_has_signer(struct ub_packed_rrset_key* rrset, uint8_t* name, size_t len) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> + entry.data; + size_t i; + for(i = d->count; i< d->count+d->rrsig_count; i++) { + if(d->rr_len[i] > 2+18+len) { + /* at least rdatalen + signature + signame (+1 sig)*/ + if(query_dname_compare(name, d->rr_data[i]+2+18) == 0) + { + return 1; + } + } + } + return 0; +} + +void +val_fill_reply(struct reply_info* chase, struct reply_info* orig, + size_t skip, uint8_t* name, size_t len, uint8_t* signer) +{ + size_t i; + int seen_dname = 0; + chase->rrset_count = 0; + chase->an_numrrsets = 0; + chase->ns_numrrsets = 0; + chase->ar_numrrsets = 0; + /* ANSWER section */ + for(i=skip; i<orig->an_numrrsets; i++) { + if(!signer) { + if(query_dname_compare(name, + orig->rrsets[i]->rk.dname) == 0) + chase->rrsets[chase->an_numrrsets++] = + orig->rrsets[i]; + } else if(seen_dname && ntohs(orig->rrsets[i]->rk.type) == + LDNS_RR_TYPE_CNAME) { + chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; + seen_dname = 0; + } else if(rrset_has_signer(orig->rrsets[i], name, len)) { + chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; + if(ntohs(orig->rrsets[i]->rk.type) == + LDNS_RR_TYPE_DNAME) { + seen_dname = 1; + } + } + } + /* AUTHORITY section */ + for(i = (skip > orig->an_numrrsets)?skip:orig->an_numrrsets; + i<orig->an_numrrsets+orig->ns_numrrsets; + i++) { + if(!signer) { + if(query_dname_compare(name, + orig->rrsets[i]->rk.dname) == 0) + chase->rrsets[chase->an_numrrsets+ + chase->ns_numrrsets++] = orig->rrsets[i]; + } else if(rrset_has_signer(orig->rrsets[i], name, len)) { + chase->rrsets[chase->an_numrrsets+ + chase->ns_numrrsets++] = orig->rrsets[i]; + } + } + /* ADDITIONAL section */ + for(i= (skip>orig->an_numrrsets+orig->ns_numrrsets)? + skip:orig->an_numrrsets+orig->ns_numrrsets; + i<orig->rrset_count; i++) { + if(!signer) { + if(query_dname_compare(name, + orig->rrsets[i]->rk.dname) == 0) + chase->rrsets[chase->an_numrrsets + +orig->ns_numrrsets+chase->ar_numrrsets++] + = orig->rrsets[i]; + } else if(rrset_has_signer(orig->rrsets[i], name, len)) { + chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+ + chase->ar_numrrsets++] = orig->rrsets[i]; + } + } + chase->rrset_count = chase->an_numrrsets + chase->ns_numrrsets + + chase->ar_numrrsets; +} + +void +val_check_nonsecure(struct val_env* ve, struct reply_info* rep) +{ + size_t i; + /* authority */ + for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) { + if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security != sec_status_secure) { + /* because we want to return the authentic original + * message when presented with CD-flagged queries, + * we need to preserve AUTHORITY section data. + * However, this rrset is not signed or signed + * with the wrong keys. Validation has tried to + * verify this rrset with the keysets of import. + * But this rrset did not verify. + * Therefore the message is bogus. + */ + + /* check if authority consists of only an NS record + * which is bad, and there is an answer section with + * data. In that case, delete NS and additional to + * be lenient and make a minimal response */ + if(rep->an_numrrsets != 0 && rep->ns_numrrsets == 1 && + ntohs(rep->rrsets[i]->rk.type) + == LDNS_RR_TYPE_NS) { + verbose(VERB_ALGO, "truncate to minimal"); + rep->ns_numrrsets = 0; + rep->ar_numrrsets = 0; + rep->rrset_count = rep->an_numrrsets; + return; + } + + log_nametypeclass(VERB_QUERY, "message is bogus, " + "non secure rrset", + rep->rrsets[i]->rk.dname, + ntohs(rep->rrsets[i]->rk.type), + ntohs(rep->rrsets[i]->rk.rrset_class)); + rep->security = sec_status_bogus; + return; + } + } + /* additional */ + if(!ve->clean_additional) + return; + for(i=rep->an_numrrsets+rep->ns_numrrsets; i<rep->rrset_count; i++) { + if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security != sec_status_secure) { + /* This does not cause message invalidation. It was + * simply unsigned data in the additional. The + * RRSIG must have been truncated off the message. + * + * However, we do not want to return possible bogus + * data to clients that rely on this service for + * their authentication. + */ + /* remove this unneeded additional rrset */ + memmove(rep->rrsets+i, rep->rrsets+i+1, + sizeof(struct ub_packed_rrset_key*)* + (rep->rrset_count - i - 1)); + rep->ar_numrrsets--; + rep->rrset_count--; + i--; + } + } +} + +/** check no anchor and unlock */ +static int +check_no_anchor(struct val_anchors* anchors, uint8_t* nm, size_t l, uint16_t c) +{ + struct trust_anchor* ta; + if((ta=anchors_lookup(anchors, nm, l, c))) { + lock_basic_unlock(&ta->lock); + } + return !ta; +} + +void +val_mark_indeterminate(struct reply_info* rep, struct val_anchors* anchors, + struct rrset_cache* r, struct module_env* env) +{ + size_t i; + struct packed_rrset_data* d; + for(i=0; i<rep->rrset_count; i++) { + d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; + if(d->security == sec_status_unchecked && + check_no_anchor(anchors, rep->rrsets[i]->rk.dname, + rep->rrsets[i]->rk.dname_len, + ntohs(rep->rrsets[i]->rk.rrset_class))) + { + /* mark as indeterminate */ + d->security = sec_status_indeterminate; + rrset_update_sec_status(r, rep->rrsets[i], *env->now); + } + } +} + +void +val_mark_insecure(struct reply_info* rep, uint8_t* kname, + struct rrset_cache* r, struct module_env* env) +{ + size_t i; + struct packed_rrset_data* d; + for(i=0; i<rep->rrset_count; i++) { + d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; + if(d->security == sec_status_unchecked && + dname_subdomain_c(rep->rrsets[i]->rk.dname, kname)) { + /* mark as insecure */ + d->security = sec_status_insecure; + rrset_update_sec_status(r, rep->rrsets[i], *env->now); + } + } +} + +size_t +val_next_unchecked(struct reply_info* rep, size_t skip) +{ + size_t i; + struct packed_rrset_data* d; + for(i=skip+1; i<rep->rrset_count; i++) { + d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; + if(d->security == sec_status_unchecked) { + return i; + } + } + return rep->rrset_count; +} + +const char* +val_classification_to_string(enum val_classification subtype) +{ + switch(subtype) { + case VAL_CLASS_UNTYPED: return "untyped"; + case VAL_CLASS_UNKNOWN: return "unknown"; + case VAL_CLASS_POSITIVE: return "positive"; + case VAL_CLASS_CNAME: return "cname"; + case VAL_CLASS_NODATA: return "nodata"; + case VAL_CLASS_NAMEERROR: return "nameerror"; + case VAL_CLASS_CNAMENOANSWER: return "cnamenoanswer"; + case VAL_CLASS_REFERRAL: return "referral"; + case VAL_CLASS_ANY: return "qtype_any"; + default: + return "bad_val_classification"; + } +} + +/** log a sock_list entry */ +static void +sock_list_logentry(enum verbosity_value v, const char* s, struct sock_list* p) +{ + if(p->len) + log_addr(v, s, &p->addr, p->len); + else verbose(v, "%s cache", s); +} + +void val_blacklist(struct sock_list** blacklist, struct regional* region, + struct sock_list* origin, int cross) +{ + /* debug printout */ + if(verbosity >= VERB_ALGO) { + struct sock_list* p; + for(p=*blacklist; p; p=p->next) + sock_list_logentry(VERB_ALGO, "blacklist", p); + if(!origin) + verbose(VERB_ALGO, "blacklist add: cache"); + for(p=origin; p; p=p->next) + sock_list_logentry(VERB_ALGO, "blacklist add", p); + } + /* blacklist the IPs or the cache */ + if(!origin) { + /* only add if nothing there. anything else also stops cache*/ + if(!*blacklist) + sock_list_insert(blacklist, NULL, 0, region); + } else if(!cross) + sock_list_prepend(blacklist, origin); + else sock_list_merge(blacklist, region, origin); +} + +int val_has_signed_nsecs(struct reply_info* rep, char** reason) +{ + size_t i, num_nsec = 0, num_nsec3 = 0; + struct packed_rrset_data* d; + for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) { + if(rep->rrsets[i]->rk.type == htons(LDNS_RR_TYPE_NSEC)) + num_nsec++; + else if(rep->rrsets[i]->rk.type == htons(LDNS_RR_TYPE_NSEC3)) + num_nsec3++; + else continue; + d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; + if(d && d->rrsig_count != 0) { + return 1; + } + } + if(num_nsec == 0 && num_nsec3 == 0) + *reason = "no DNSSEC records"; + else if(num_nsec != 0) + *reason = "no signatures over NSECs"; + else *reason = "no signatures over NSEC3s"; + return 0; +} + +struct dns_msg* +val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c, + struct regional* region, uint8_t* topname) +{ + struct dns_msg* msg; + struct query_info qinfo; + struct ub_packed_rrset_key *rrset = rrset_cache_lookup( + env->rrset_cache, nm, nmlen, LDNS_RR_TYPE_DS, c, 0, + *env->now, 0); + if(rrset) { + /* DS rrset exists. Return it to the validator immediately*/ + struct ub_packed_rrset_key* copy = packed_rrset_copy_region( + rrset, region, *env->now); + lock_rw_unlock(&rrset->entry.lock); + if(!copy) + return NULL; + msg = dns_msg_create(nm, nmlen, LDNS_RR_TYPE_DS, c, region, 1); + if(!msg) + return NULL; + msg->rep->rrsets[0] = copy; + msg->rep->rrset_count++; + msg->rep->an_numrrsets++; + return msg; + } + /* lookup in rrset and negative cache for NSEC/NSEC3 */ + qinfo.qname = nm; + qinfo.qname_len = nmlen; + qinfo.qtype = LDNS_RR_TYPE_DS; + qinfo.qclass = c; + /* do not add SOA to reply message, it is going to be used internal */ + msg = val_neg_getmsg(env->neg_cache, &qinfo, region, env->rrset_cache, + env->scratch_buffer, *env->now, 0, topname); + return msg; +} diff --git a/validator/val_utils.h b/validator/val_utils.h new file mode 100644 index 0000000000000..f0afc3756e9f1 --- /dev/null +++ b/validator/val_utils.h @@ -0,0 +1,403 @@ +/* + * validator/val_utils.h - validator utility functions. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains helper functions for the validator module. + */ + +#ifndef VALIDATOR_VAL_UTILS_H +#define VALIDATOR_VAL_UTILS_H +#include "util/data/packed_rrset.h" +struct query_info; +struct reply_info; +struct val_env; +struct module_env; +struct ub_packed_rrset_key; +struct key_entry_key; +struct regional; +struct val_anchors; +struct rrset_cache; +struct sock_list; + +/** + * Response classifications for the validator. The different types of proofs. + */ +enum val_classification { + /** Not subtyped yet. */ + VAL_CLASS_UNTYPED = 0, + /** Not a recognized subtype. */ + VAL_CLASS_UNKNOWN, + /** A positive, direct, response */ + VAL_CLASS_POSITIVE, + /** A positive response, with a CNAME/DNAME chain. */ + VAL_CLASS_CNAME, + /** A NOERROR/NODATA response. */ + VAL_CLASS_NODATA, + /** A NXDOMAIN response. */ + VAL_CLASS_NAMEERROR, + /** A CNAME/DNAME chain, and the offset is at the end of it, + * but there is no answer here, it can be NAMERROR or NODATA. */ + VAL_CLASS_CNAMENOANSWER, + /** A referral, from cache with a nonRD query. */ + VAL_CLASS_REFERRAL, + /** A response to a qtype=ANY query. */ + VAL_CLASS_ANY +}; + +/** + * Given a response, classify ANSWER responses into a subtype. + * @param query_flags: query flags for the original query. + * @param origqinf: query info. The original query name. + * @param qinf: query info. The chased query name. + * @param rep: response. The original response. + * @param skip: offset into the original response answer section. + * @return A subtype, all values possible except UNTYPED . + * Once CNAME type is returned you can increase skip. + * Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible. + */ +enum val_classification val_classify_response(uint16_t query_flags, + struct query_info* origqinf, struct query_info* qinf, + struct reply_info* rep, size_t skip); + +/** + * Given a response, determine the name of the "signer". This is primarily + * to determine if the response is, in fact, signed at all, and, if so, what + * is the name of the most pertinent keyset. + * + * @param subtype: the type from classify. + * @param qinf: query, the chased query name. + * @param rep: response to that, original response. + * @param cname_skip: how many answer rrsets have been skipped due to CNAME + * chains being chased around. + * @param signer_name: signer name, if the response is signed + * (even partially), or null if the response isn't signed. + * @param signer_len: length of signer_name of 0 if signer_name is NULL. + */ +void val_find_signer(enum val_classification subtype, + struct query_info* qinf, struct reply_info* rep, + size_t cname_skip, uint8_t** signer_name, size_t* signer_len); + +/** + * Verify RRset with keys + * @param env: module environment (scratch buffer) + * @param ve: validator environment (verification settings) + * @param rrset: what to verify + * @param keys: dnskey rrset to verify with. + * @param sigalg: if nonNULL provide downgrade protection otherwise one + * algorithm is enough. Algo list is constructed in here. + * @param reason: reason of failure. Fixed string or alloced in scratch. + * @return security status of verification. + */ +enum sec_status val_verify_rrset(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys, + uint8_t* sigalg, char** reason); + +/** + * Verify RRset with keys from a keyset. + * @param env: module environment (scratch buffer) + * @param ve: validator environment (verification settings) + * @param rrset: what to verify + * @param kkey: key_entry to verify with. + * @param reason: reason of failure. Fixed string or alloced in scratch. + * @return security status of verification. + */ +enum sec_status val_verify_rrset_entry(struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* rrset, + struct key_entry_key* kkey, char** reason); + +/** + * Verify DNSKEYs with DS rrset. Like val_verify_new_DNSKEYs but + * returns a sec_status instead of a key_entry. + * @param env: module environment (scratch buffer) + * @param ve: validator environment (verification settings) + * @param dnskey_rrset: DNSKEY rrset to verify + * @param ds_rrset: DS rrset to verify with. + * @param sigalg: if nonNULL provide downgrade protection otherwise one + * algorithm is enough. The list of signalled algorithms is returned, + * must have enough space for ALGO_NEEDS_MAX+1. + * @param reason: reason of failure. Fixed string or alloced in scratch. + * @return: sec_status_secure if a DS matches. + * sec_status_insecure if end of trust (i.e., unknown algorithms). + * sec_status_bogus if it fails. + */ +enum sec_status val_verify_DNSKEY_with_DS(struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** reason); + +/** + * Verify DNSKEYs with DS and DNSKEY rrset. Like val_verify_DNSKEY_with_DS + * but for a trust anchor. + * @param env: module environment (scratch buffer) + * @param ve: validator environment (verification settings) + * @param dnskey_rrset: DNSKEY rrset to verify + * @param ta_ds: DS rrset to verify with. + * @param ta_dnskey: DNSKEY rrset to verify with. + * @param sigalg: if nonNULL provide downgrade protection otherwise one + * algorithm is enough. The list of signalled algorithms is returned, + * must have enough space for ALGO_NEEDS_MAX+1. + * @param reason: reason of failure. Fixed string or alloced in scratch. + * @return: sec_status_secure if a DS matches. + * sec_status_insecure if end of trust (i.e., unknown algorithms). + * sec_status_bogus if it fails. + */ +enum sec_status val_verify_DNSKEY_with_TA(struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ta_ds, + struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason); + +/** + * Verify new DNSKEYs with DS rrset. The DS contains hash values that should + * match the DNSKEY keys. + * match the DS to a DNSKEY and verify the DNSKEY rrset with that key. + * + * @param region: where to allocate key entry result. + * @param env: module environment (scratch buffer) + * @param ve: validator environment (verification settings) + * @param dnskey_rrset: DNSKEY rrset to verify + * @param ds_rrset: DS rrset to verify with. + * @param downprot: if true provide downgrade protection otherwise one + * algorithm is enough. + * @param reason: reason of failure. Fixed string or alloced in scratch. + * @return a KeyEntry. This will either contain the now trusted + * dnskey_rrset, a "null" key entry indicating that this DS + * rrset/DNSKEY pair indicate an secure end to the island of trust + * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey + * rrset fails to verify. Note that the "null" response should + * generally only occur in a private algorithm scenario: normally + * this sort of thing is checked before fetching the matching DNSKEY + * rrset. + * if downprot is set, a key entry with an algo list is made. + */ +struct key_entry_key* val_verify_new_DNSKEYs(struct regional* region, + struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ds_rrset, int downprot, char** reason); + + +/** + * Verify rrset with trust anchor: DS and DNSKEY rrset. + * + * @param region: where to allocate key entry result. + * @param env: module environment (scratch buffer) + * @param ve: validator environment (verification settings) + * @param dnskey_rrset: DNSKEY rrset to verify + * @param ta_ds_rrset: DS rrset to verify with. + * @param ta_dnskey_rrset: the DNSKEY rrset to verify with. + * @param downprot: if true provide downgrade protection otherwise one + * algorithm is enough. + * @param reason: reason of failure. Fixed string or alloced in scratch. + * @return a KeyEntry. This will either contain the now trusted + * dnskey_rrset, a "null" key entry indicating that this DS + * rrset/DNSKEY pair indicate an secure end to the island of trust + * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey + * rrset fails to verify. Note that the "null" response should + * generally only occur in a private algorithm scenario: normally + * this sort of thing is checked before fetching the matching DNSKEY + * rrset. + * if downprot is set, a key entry with an algo list is made. + */ +struct key_entry_key* val_verify_new_DNSKEYs_with_ta(struct regional* region, + struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey_rrset, + struct ub_packed_rrset_key* ta_ds_rrset, + struct ub_packed_rrset_key* ta_dnskey_rrset, + int downprot, char** reason); + +/** + * Determine if DS rrset is usable for validator or not. + * Returns true if the algorithms for key and DShash are supported, + * for at least one RR. + * + * @param ds_rrset: the newly received DS rrset. + * @return true or false if not usable. + */ +int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset); + +/** + * Determine by looking at a signed RRset whether or not the RRset name was + * the result of a wildcard expansion. If so, return the name of the + * generating wildcard. + * + * @param rrset The rrset to chedck. + * @param wc: the wildcard name, if the rrset was synthesized from a wildcard. + * unchanged if not. The wildcard name, without "*." in front, is + * returned. This is a pointer into the rrset owner name. + * @return false if the signatures are inconsistent in indicating the + * wildcard status; possible spoofing of wildcard response for other + * responses is being tried. We lost the status which rrsig was verified + * after the verification routine finished, so we simply check if + * the signatures are consistent; inserting a fake signature is a denial + * of service; but in that you could also have removed the real + * signature anyway. + */ +int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc); + +/** + * Chase the cname to the next query name. + * @param qchase: the current query name, updated to next target. + * @param rep: original message reply to look at CNAMEs. + * @param cname_skip: the skip into the answer section. Updated to skip + * DNAME and CNAME to the next part of the answer. + * @return false on error (bad rdata). + */ +int val_chase_cname(struct query_info* qchase, struct reply_info* rep, + size_t* cname_skip); + +/** + * Fill up the chased reply with the content from the original reply; + * as pointers to those rrsets. Select the part after the cname_skip into + * the answer section, NS and AR sections that are signed with same signer. + * + * @param chase: chased reply, filled up. + * @param orig: original reply. + * @param cname_skip: which part of the answer section to skip. + * The skipped part contains CNAME(and DNAME)s that have been chased. + * @param name: the signer name to look for. + * @param len: length of name. + * @param signer: signer name or NULL if an unsigned RRset is considered. + * If NULL, rrsets with the lookup name are copied over. + */ +void val_fill_reply(struct reply_info* chase, struct reply_info* orig, + size_t cname_skip, uint8_t* name, size_t len, uint8_t* signer); + +/** + * Remove all unsigned or non-secure status rrsets from NS and AR sections. + * So that unsigned data does not get let through to clients, when we have + * found the data to be secure. + * + * @param ve: validator environment with cleaning options. + * @param rep: reply to dump all nonsecure stuff out of. + */ +void val_check_nonsecure(struct val_env* ve, struct reply_info* rep); + +/** + * Mark all unchecked rrset entries not below a trust anchor as indeterminate. + * Only security==unchecked rrsets are updated. + * @param rep: the reply with rrsets. + * @param anchors: the trust anchors. + * @param r: rrset cache to store updated security status into. + * @param env: module environment + */ +void val_mark_indeterminate(struct reply_info* rep, + struct val_anchors* anchors, struct rrset_cache* r, + struct module_env* env); + +/** + * Mark all unchecked rrset entries below a NULL key entry as insecure. + * Only security==unchecked rrsets are updated. + * @param rep: the reply with rrsets. + * @param kname: end of secure space name. + * @param r: rrset cache to store updated security status into. + * @param env: module environment + */ +void val_mark_insecure(struct reply_info* rep, uint8_t* kname, + struct rrset_cache* r, struct module_env* env); + +/** + * Find next unchecked rrset position, return it for skip. + * @param rep: the original reply to look into. + * @param skip: the skip now. + * @return new skip, which may be at the rep->rrset_count position to signal + * there are no unchecked items. + */ +size_t val_next_unchecked(struct reply_info* rep, size_t skip); + +/** + * Find the signer name for an RRset. + * @param rrset: the rrset. + * @param sname: signer name is returned or NULL if not signed. + * @param slen: length of sname (or 0). + */ +void val_find_rrset_signer(struct ub_packed_rrset_key* rrset, uint8_t** sname, + size_t* slen); + +/** + * Get string to denote the classification result. + * @param subtype: from classification function. + * @return static string to describe the classification. + */ +const char* val_classification_to_string(enum val_classification subtype); + +/** + * Add existing list to blacklist. + * @param blacklist: the blacklist with result + * @param region: the region where blacklist is allocated. + * Allocation failures are logged. + * @param origin: origin list to add, if NULL, a cache-entry is added to + * the blacklist to stop cache from being used. + * @param cross: if true this is a cross-qstate copy, and the 'origin' + * list is not allocated in the same region as the blacklist. + */ +void val_blacklist(struct sock_list** blacklist, struct regional* region, + struct sock_list* origin, int cross); + +/** + * check if has dnssec info, and if it has signed nsecs. gives error reason. + * @param rep: reply to check. + * @param reason: returned on fail. + * @return false if message has no signed nsecs. Can not prove negatives. + */ +int val_has_signed_nsecs(struct reply_info* rep, char** reason); + +/** + * Return algo number for favorite (best) algorithm that we support in DS. + * @param ds_rrset: the DSes in this rrset are inspected and best algo chosen. + * @return algo number or 0 if none supported. 0 is unused as algo number. + */ +int val_favorite_ds_algo(struct ub_packed_rrset_key* ds_rrset); + +/** + * Find DS denial message in cache. Saves new qstate allocation and allows + * the validator to use partial content which is not enough to construct a + * message for network (or user) consumption. Without SOA for example, + * which is a common occurence in the unbound code since the referrals contain + * NSEC/NSEC3 rrs without the SOA element, thus do not allow synthesis of a + * full negative reply, but do allow synthesis of sufficient proof. + * @param env: query env with caches and time. + * @param nm: name of DS record sought. + * @param nmlen: length of name. + * @param c: class of DS RR. + * @param region: where to allocate result. + * @param topname: name of the key that is currently in use, that will get + * used to validate the result, and thus no higher entries from the + * negative cache need to be examined. + * @return a dns_msg on success. NULL on failure. + */ +struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, + uint16_t c, struct regional* region, uint8_t* topname); + +#endif /* VALIDATOR_VAL_UTILS_H */ diff --git a/validator/validator.c b/validator/validator.c new file mode 100644 index 0000000000000..af1d344a69126 --- /dev/null +++ b/validator/validator.c @@ -0,0 +1,2958 @@ +/* + * validator/validator.c - secure validator DNS query response module + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains a module that performs validation of DNS queries. + * According to RFC 4034. + */ +#include "config.h" +#include <ldns/ldns.h> +#include "validator/validator.h" +#include "validator/val_anchor.h" +#include "validator/val_kcache.h" +#include "validator/val_kentry.h" +#include "validator/val_utils.h" +#include "validator/val_nsec.h" +#include "validator/val_nsec3.h" +#include "validator/val_neg.h" +#include "validator/val_sigcrypt.h" +#include "validator/autotrust.h" +#include "services/cache/dns.h" +#include "util/data/dname.h" +#include "util/module.h" +#include "util/log.h" +#include "util/net_help.h" +#include "util/regional.h" +#include "util/config_file.h" +#include "util/fptr_wlist.h" + +/* forward decl for cache response and normal super inform calls of a DS */ +static void process_ds_response(struct module_qstate* qstate, + struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, + struct query_info* qinfo, struct sock_list* origin); + +/** fill up nsec3 key iterations config entry */ +static int +fill_nsec3_iter(struct val_env* ve, char* s, int c) +{ + char* e; + int i; + free(ve->nsec3_keysize); + free(ve->nsec3_maxiter); + ve->nsec3_keysize = (size_t*)calloc(sizeof(size_t), (size_t)c); + ve->nsec3_maxiter = (size_t*)calloc(sizeof(size_t), (size_t)c); + if(!ve->nsec3_keysize || !ve->nsec3_maxiter) { + log_err("out of memory"); + return 0; + } + for(i=0; i<c; i++) { + ve->nsec3_keysize[i] = (size_t)strtol(s, &e, 10); + if(s == e) { + log_err("cannot parse: %s", s); + return 0; + } + s = e; + ve->nsec3_maxiter[i] = (size_t)strtol(s, &e, 10); + if(s == e) { + log_err("cannot parse: %s", s); + return 0; + } + s = e; + if(i>0 && ve->nsec3_keysize[i-1] >= ve->nsec3_keysize[i]) { + log_err("nsec3 key iterations not ascending: %d %d", + (int)ve->nsec3_keysize[i-1], + (int)ve->nsec3_keysize[i]); + return 0; + } + verbose(VERB_ALGO, "validator nsec3cfg keysz %d mxiter %d", + (int)ve->nsec3_keysize[i], (int)ve->nsec3_maxiter[i]); + } + return 1; +} + +/** apply config settings to validator */ +static int +val_apply_cfg(struct module_env* env, struct val_env* val_env, + struct config_file* cfg) +{ + int c; + val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl; + val_env->clean_additional = cfg->val_clean_additional; + val_env->permissive_mode = cfg->val_permissive_mode; + if(!env->anchors) + env->anchors = anchors_create(); + if(!env->anchors) { + log_err("out of memory"); + return 0; + } + if(!val_env->kcache) + val_env->kcache = key_cache_create(cfg); + if(!val_env->kcache) { + log_err("out of memory"); + return 0; + } + env->key_cache = val_env->kcache; + if(!anchors_apply_cfg(env->anchors, cfg)) { + log_err("validator: error in trustanchors config"); + return 0; + } + val_env->date_override = cfg->val_date_override; + val_env->skew_min = cfg->val_sig_skew_min; + val_env->skew_max = cfg->val_sig_skew_max; + c = cfg_count_numbers(cfg->val_nsec3_key_iterations); + if(c < 1 || (c&1)) { + log_err("validator: unparseable or odd nsec3 key " + "iterations: %s", cfg->val_nsec3_key_iterations); + return 0; + } + val_env->nsec3_keyiter_count = c/2; + if(!fill_nsec3_iter(val_env, cfg->val_nsec3_key_iterations, c/2)) { + log_err("validator: cannot apply nsec3 key iterations"); + return 0; + } + if(!val_env->neg_cache) + val_env->neg_cache = val_neg_create(cfg, + val_env->nsec3_maxiter[val_env->nsec3_keyiter_count-1]); + if(!val_env->neg_cache) { + log_err("out of memory"); + return 0; + } + env->neg_cache = val_env->neg_cache; + return 1; +} + +int +val_init(struct module_env* env, int id) +{ + struct val_env* val_env = (struct val_env*)calloc(1, + sizeof(struct val_env)); + if(!val_env) { + log_err("malloc failure"); + return 0; + } + env->modinfo[id] = (void*)val_env; + env->need_to_validate = 1; + val_env->permissive_mode = 0; + lock_basic_init(&val_env->bogus_lock); + lock_protect(&val_env->bogus_lock, &val_env->num_rrset_bogus, + sizeof(val_env->num_rrset_bogus)); + if(!val_apply_cfg(env, val_env, env->cfg)) { + log_err("validator: could not apply configuration settings."); + return 0; + } + return 1; +} + +void +val_deinit(struct module_env* env, int id) +{ + struct val_env* val_env; + if(!env || !env->modinfo[id]) + return; + val_env = (struct val_env*)env->modinfo[id]; + lock_basic_destroy(&val_env->bogus_lock); + anchors_delete(env->anchors); + env->anchors = NULL; + key_cache_delete(val_env->kcache); + neg_cache_delete(val_env->neg_cache); + free(val_env->nsec3_keysize); + free(val_env->nsec3_maxiter); + free(val_env); + env->modinfo[id] = NULL; +} + +/** fill in message structure */ +static struct val_qstate* +val_new_getmsg(struct module_qstate* qstate, struct val_qstate* vq) +{ + if(!qstate->return_msg || qstate->return_rcode != LDNS_RCODE_NOERROR) { + /* create a message to verify */ + verbose(VERB_ALGO, "constructing reply for validation"); + vq->orig_msg = (struct dns_msg*)regional_alloc(qstate->region, + sizeof(struct dns_msg)); + if(!vq->orig_msg) + return NULL; + vq->orig_msg->qinfo = qstate->qinfo; + vq->orig_msg->rep = (struct reply_info*)regional_alloc( + qstate->region, sizeof(struct reply_info)); + if(!vq->orig_msg->rep) + return NULL; + memset(vq->orig_msg->rep, 0, sizeof(struct reply_info)); + vq->orig_msg->rep->flags = (uint16_t)(qstate->return_rcode&0xf) + |BIT_QR|BIT_RA|(qstate->query_flags|(BIT_CD|BIT_RD)); + vq->orig_msg->rep->qdcount = 1; + } else { + vq->orig_msg = qstate->return_msg; + } + vq->qchase = qstate->qinfo; + /* chase reply will be an edited (sub)set of the orig msg rrset ptrs */ + vq->chase_reply = regional_alloc_init(qstate->region, + vq->orig_msg->rep, + sizeof(struct reply_info) - sizeof(struct rrset_ref)); + if(!vq->chase_reply) + return NULL; + vq->chase_reply->rrsets = regional_alloc_init(qstate->region, + vq->orig_msg->rep->rrsets, sizeof(struct ub_packed_rrset_key*) + * vq->orig_msg->rep->rrset_count); + if(!vq->chase_reply->rrsets) + return NULL; + vq->rrset_skip = 0; + return vq; +} + +/** allocate new validator query state */ +static struct val_qstate* +val_new(struct module_qstate* qstate, int id) +{ + struct val_qstate* vq = (struct val_qstate*)regional_alloc( + qstate->region, sizeof(*vq)); + log_assert(!qstate->minfo[id]); + if(!vq) + return NULL; + memset(vq, 0, sizeof(*vq)); + qstate->minfo[id] = vq; + vq->state = VAL_INIT_STATE; + return val_new_getmsg(qstate, vq); +} + +/** + * Exit validation with an error status + * + * @param qstate: query state + * @param id: validator id. + * @return false, for use by caller to return to stop processing. + */ +static int +val_error(struct module_qstate* qstate, int id) +{ + qstate->ext_state[id] = module_error; + qstate->return_rcode = LDNS_RCODE_SERVFAIL; + return 0; +} + +/** + * Check to see if a given response needs to go through the validation + * process. Typical reasons for this routine to return false are: CD bit was + * on in the original request, or the response is a kind of message that + * is unvalidatable (i.e., SERVFAIL, REFUSED, etc.) + * + * @param qstate: query state. + * @param ret_rc: rcode for this message (if noerror - examine ret_msg). + * @param ret_msg: return msg, can be NULL; look at rcode instead. + * @return true if the response could use validation (although this does not + * mean we can actually validate this response). + */ +static int +needs_validation(struct module_qstate* qstate, int ret_rc, + struct dns_msg* ret_msg) +{ + int rcode; + + /* If the CD bit is on in the original request, then we don't bother to + * validate anything.*/ + if(qstate->query_flags & BIT_CD) { + verbose(VERB_ALGO, "not validating response due to CD bit"); + return 0; + } + + if(ret_rc != LDNS_RCODE_NOERROR || !ret_msg) + rcode = ret_rc; + else rcode = (int)FLAGS_GET_RCODE(ret_msg->rep->flags); + + if(rcode != LDNS_RCODE_NOERROR && rcode != LDNS_RCODE_NXDOMAIN) { + verbose(VERB_ALGO, "cannot validate non-answer, rcode %s", + ldns_lookup_by_id(ldns_rcodes, rcode)? + ldns_lookup_by_id(ldns_rcodes, rcode)->name:"??"); + return 0; + } + + /* cannot validate positive RRSIG response. (negatives can) */ + if(qstate->qinfo.qtype == LDNS_RR_TYPE_RRSIG && + rcode == LDNS_RCODE_NOERROR && ret_msg && + ret_msg->rep->an_numrrsets > 0) { + verbose(VERB_ALGO, "cannot validate RRSIG, no sigs on sigs."); + return 0; + } + return 1; +} + +/** + * Check to see if the response has already been validated. + * @param ret_msg: return msg, can be NULL + * @return true if the response has already been validated + */ +static int +already_validated(struct dns_msg* ret_msg) +{ + /* validate unchecked, and re-validate bogus messages */ + if (ret_msg && ret_msg->rep->security > sec_status_bogus) + { + verbose(VERB_ALGO, "response has already been validated: %s", + sec_status_to_string(ret_msg->rep->security)); + return 1; + } + return 0; +} + +/** + * Generate a request for DNS data. + * + * @param qstate: query state that is the parent. + * @param id: module id. + * @param name: what name to query for. + * @param namelen: length of name. + * @param qtype: query type. + * @param qclass: query class. + * @param flags: additional flags, such as the CD bit (BIT_CD), or 0. + * @return false on alloc failure. + */ +static int +generate_request(struct module_qstate* qstate, int id, uint8_t* name, + size_t namelen, uint16_t qtype, uint16_t qclass, uint16_t flags) +{ + struct val_qstate* vq = (struct val_qstate*)qstate->minfo[id]; + struct module_qstate* newq; + struct query_info ask; + ask.qname = name; + ask.qname_len = namelen; + ask.qtype = qtype; + ask.qclass = qclass; + log_query_info(VERB_ALGO, "generate request", &ask); + fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub)); + if(!(*qstate->env->attach_sub)(qstate, &ask, + (uint16_t)(BIT_RD|flags), 0, &newq)){ + log_err("Could not generate request: out of memory"); + return 0; + } + /* newq; validator does not need state created for that + * query, and its a 'normal' for iterator as well */ + if(newq) { + /* add our blacklist to the query blacklist */ + sock_list_merge(&newq->blacklist, newq->region, + vq->chain_blacklist); + } + qstate->ext_state[id] = module_wait_subquery; + return 1; +} + +/** + * Prime trust anchor for use. + * Generate and dispatch a priming query for the given trust anchor. + * The trust anchor can be DNSKEY or DS and does not have to be signed. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param id: module id. + * @param toprime: what to prime. + * @return false on a processing error. + */ +static int +prime_trust_anchor(struct module_qstate* qstate, struct val_qstate* vq, + int id, struct trust_anchor* toprime) +{ + int ret = generate_request(qstate, id, toprime->name, toprime->namelen, + LDNS_RR_TYPE_DNSKEY, toprime->dclass, BIT_CD); + if(!ret) { + log_err("Could not prime trust anchor: out of memory"); + return 0; + } + /* ignore newq; validator does not need state created for that + * query, and its a 'normal' for iterator as well */ + vq->wait_prime_ta = 1; /* to elicit PRIME_RESP_STATE processing + from the validator inform_super() routine */ + /* store trust anchor name for later lookup when prime returns */ + vq->trust_anchor_name = regional_alloc_init(qstate->region, + toprime->name, toprime->namelen); + vq->trust_anchor_len = toprime->namelen; + vq->trust_anchor_labs = toprime->namelabs; + if(!vq->trust_anchor_name) { + log_err("Could not prime trust anchor: out of memory"); + return 0; + } + return 1; +} + +/** + * Validate if the ANSWER and AUTHORITY sections contain valid rrsets. + * They must be validly signed with the given key. + * Tries to validate ADDITIONAL rrsets as well, but only to check them. + * Allows unsigned CNAME after a DNAME that expands the DNAME. + * + * Note that by the time this method is called, the process of finding the + * trusted DNSKEY rrset that signs this response must already have been + * completed. + * + * @param qstate: query state. + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to validate. + * @param key_entry: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + * @return false if any of the rrsets in the an or ns sections of the message + * fail to verify. The message is then set to bogus. + */ +static int +validate_msg_signatures(struct module_qstate* qstate, struct module_env* env, + struct val_env* ve, struct query_info* qchase, + struct reply_info* chase_reply, struct key_entry_key* key_entry) +{ + uint8_t* sname; + size_t i, slen; + struct ub_packed_rrset_key* s; + enum sec_status sec; + int dname_seen = 0; + char* reason = NULL; + + /* validate the ANSWER section */ + for(i=0; i<chase_reply->an_numrrsets; i++) { + s = chase_reply->rrsets[i]; + /* Skip the CNAME following a (validated) DNAME. + * Because of the normalization routines in the iterator, + * there will always be an unsigned CNAME following a DNAME + * (unless qtype=DNAME). */ + if(dname_seen && ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) { + dname_seen = 0; + /* CNAME was synthesized by our own iterator */ + /* since the DNAME verified, mark the CNAME as secure */ + ((struct packed_rrset_data*)s->entry.data)->security = + sec_status_secure; + ((struct packed_rrset_data*)s->entry.data)->trust = + rrset_trust_validated; + continue; + } + + /* Verify the answer rrset */ + sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason); + /* If the (answer) rrset failed to validate, then this + * message is BAD. */ + if(sec != sec_status_secure) { + log_nametypeclass(VERB_QUERY, "validator: response " + "has failed ANSWER rrset:", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + errinf(qstate, reason); + if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) + errinf(qstate, "for CNAME"); + else if(ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) + errinf(qstate, "for DNAME"); + errinf_origin(qstate, qstate->reply_origin); + chase_reply->security = sec_status_bogus; + return 0; + } + + /* Notice a DNAME that should be followed by an unsigned + * CNAME. */ + if(qchase->qtype != LDNS_RR_TYPE_DNAME && + ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) { + dname_seen = 1; + } + } + + /* validate the AUTHORITY section */ + for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason); + /* If anything in the authority section fails to be secure, + * we have a bad message. */ + if(sec != sec_status_secure) { + log_nametypeclass(VERB_QUERY, "validator: response " + "has failed AUTHORITY rrset:", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + errinf(qstate, reason); + errinf_rrset(qstate, s); + errinf_origin(qstate, qstate->reply_origin); + chase_reply->security = sec_status_bogus; + return 0; + } + } + + /* attempt to validate the ADDITIONAL section rrsets */ + if(!ve->clean_additional) + return 1; + for(i=chase_reply->an_numrrsets+chase_reply->ns_numrrsets; + i<chase_reply->rrset_count; i++) { + s = chase_reply->rrsets[i]; + /* only validate rrs that have signatures with the key */ + /* leave others unchecked, those get removed later on too */ + val_find_rrset_signer(s, &sname, &slen); + if(sname && query_dname_compare(sname, key_entry->name)==0) + (void)val_verify_rrset_entry(env, ve, s, key_entry, + &reason); + /* the additional section can fail to be secure, + * it is optional, check signature in case we need + * to clean the additional section later. */ + } + + return 1; +} + +/** + * Detect wrong truncated response (say from BIND 9.6.1 that is forwarding + * and saw the NS record without signatures from a referral). + * The positive response has a mangled authority section. + * Remove that authority section and the additional section. + * @param rep: reply + * @return true if a wrongly truncated response. + */ +static int +detect_wrongly_truncated(struct reply_info* rep) +{ + size_t i; + /* only NS in authority, and it is bogus */ + if(rep->ns_numrrsets != 1 || rep->an_numrrsets == 0) + return 0; + if(ntohs(rep->rrsets[ rep->an_numrrsets ]->rk.type) != LDNS_RR_TYPE_NS) + return 0; + if(((struct packed_rrset_data*)rep->rrsets[ rep->an_numrrsets ] + ->entry.data)->security == sec_status_secure) + return 0; + /* answer section is present and secure */ + for(i=0; i<rep->an_numrrsets; i++) { + if(((struct packed_rrset_data*)rep->rrsets[ i ] + ->entry.data)->security != sec_status_secure) + return 0; + } + verbose(VERB_ALGO, "truncating to minimal response"); + return 1; +} + + +/** + * Given a "positive" response -- a response that contains an answer to the + * question, and no CNAME chain, validate this response. + * + * The answer and authority RRsets must already be verified as secure. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +static void +validate_positive_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) +{ + uint8_t* wc = NULL; + int wc_NSEC_ok = 0; + int nsec3s_seen = 0; + size_t i; + struct ub_packed_rrset_key* s; + + /* validate the ANSWER section - this will be the answer itself */ + for(i=0; i<chase_reply->an_numrrsets; i++) { + s = chase_reply->rrsets[i]; + + /* Check to see if the rrset is the result of a wildcard + * expansion. If so, an additional check will need to be + * made in the authority section. */ + if(!val_rrset_wildcard(s, &wc)) { + log_nametypeclass(VERB_QUERY, "Positive response has " + "inconsistent wildcard sigs:", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + } + + /* validate the AUTHORITY section as well - this will generally be + * the NS rrset (which could be missing, no problem) */ + for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + + /* If this is a positive wildcard response, and we have a + * (just verified) NSEC record, try to use it to 1) prove + * that qname doesn't exist and 2) that the correct wildcard + * was used. */ + if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(val_nsec_proves_positive_wildcard(s, qchase, wc)) { + wc_NSEC_ok = 1; + } + /* if not, continue looking for proof */ + } + + /* Otherwise, if this is a positive wildcard response and + * we have NSEC3 records */ + if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + nsec3s_seen = 1; + } + } + + /* If this was a positive wildcard response that we haven't already + * proven, and we have NSEC3 records, try to prove it using the NSEC3 + * records. */ + if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { + enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey, wc); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "Positive wildcard response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) + wc_NSEC_ok = 1; + } + + /* If after all this, we still haven't proven the positive wildcard + * response, fail. */ + if(wc != NULL && !wc_NSEC_ok) { + verbose(VERB_QUERY, "positive response was wildcard " + "expansion and did not prove original data " + "did not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + verbose(VERB_ALGO, "Successfully validated positive response"); + chase_reply->security = sec_status_secure; +} + +/** + * Validate a NOERROR/NODATA signed response -- a response that has a + * NOERROR Rcode but no ANSWER section RRsets. This consists of making + * certain that the authority section NSEC/NSEC3s proves that the qname + * does exist and the qtype doesn't. + * + * The answer and authority RRsets must already be verified as secure. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +static void +validate_nodata_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) +{ + /* Since we are here, there must be nothing in the ANSWER section to + * validate. */ + /* (Note: CNAME/DNAME responses will not directly get here -- + * instead, they are chased down into indiviual CNAME validations, + * and at the end of the cname chain a POSITIVE, or CNAME_NOANSWER + * validation.) */ + + /* validate the AUTHORITY section */ + int has_valid_nsec = 0; /* If true, then the NODATA has been proven.*/ + uint8_t* ce = NULL; /* for wildcard nodata responses. This is the + proven closest encloser. */ + uint8_t* wc = NULL; /* for wildcard nodata responses. wildcard nsec */ + int nsec3s_seen = 0; /* nsec3s seen */ + struct ub_packed_rrset_key* s; + size_t i; + + for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + /* If we encounter an NSEC record, try to use it to prove + * NODATA. + * This needs to handle the ENT NODATA case. */ + if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(nsec_proves_nodata(s, qchase, &wc)) { + has_valid_nsec = 1; + /* sets wc-encloser if wildcard applicable */ + } + if(val_nsec_proves_name_error(s, qchase->qname)) { + ce = nsec_closest_encloser(qchase->qname, s); + } + if(val_nsec_proves_insecuredelegation(s, qchase)) { + verbose(VERB_ALGO, "delegation is insecure"); + chase_reply->security = sec_status_insecure; + return; + } + } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + nsec3s_seen = 1; + } + } + + /* check to see if we have a wildcard NODATA proof. */ + + /* The wildcard NODATA is 1 NSEC proving that qname does not exist + * (and also proving what the closest encloser is), and 1 NSEC + * showing the matching wildcard, which must be *.closest_encloser. */ + if(wc && !ce) + has_valid_nsec = 0; + else if(wc && ce) { + if(query_dname_compare(wc, ce) != 0) { + has_valid_nsec = 0; + } + } + + if(!has_valid_nsec && nsec3s_seen) { + enum sec_status sec = nsec3_prove_nodata(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "NODATA response is insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) + has_valid_nsec = 1; + } + + if(!has_valid_nsec) { + verbose(VERB_QUERY, "NODATA response failed to prove NODATA " + "status with NSEC/NSEC3"); + if(verbosity >= VERB_ALGO) + log_dns_msg("Failed NODATA", qchase, chase_reply); + chase_reply->security = sec_status_bogus; + return; + } + + verbose(VERB_ALGO, "successfully validated NODATA response."); + chase_reply->security = sec_status_secure; +} + +/** + * Validate a NAMEERROR signed response -- a response that has a NXDOMAIN + * Rcode. + * This consists of making certain that the authority section NSEC proves + * that the qname doesn't exist and the covering wildcard also doesn't exist.. + * + * The answer and authority RRsets must have already been verified as secure. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +static void +validate_nameerror_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) +{ + int has_valid_nsec = 0; + int has_valid_wnsec = 0; + int nsec3s_seen = 0; + struct ub_packed_rrset_key* s; + size_t i; + + for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(val_nsec_proves_name_error(s, qchase->qname)) + has_valid_nsec = 1; + if(val_nsec_proves_no_wc(s, qchase->qname, + qchase->qname_len)) + has_valid_wnsec = 1; + if(val_nsec_proves_insecuredelegation(s, qchase)) { + verbose(VERB_ALGO, "delegation is insecure"); + chase_reply->security = sec_status_insecure; + return; + } + } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) + nsec3s_seen = 1; + } + + if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen) { + /* use NSEC3 proof, both answer and auth rrsets, in case + * NSEC3s end up in the answer (due to qtype=NSEC3 or so) */ + chase_reply->security = nsec3_prove_nameerror(env, ve, + chase_reply->rrsets, chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets, qchase, kkey); + if(chase_reply->security != sec_status_secure) { + verbose(VERB_QUERY, "NameError response failed nsec, " + "nsec3 proof was %s", sec_status_to_string( + chase_reply->security)); + return; + } + has_valid_nsec = 1; + has_valid_wnsec = 1; + } + + /* If the message fails to prove either condition, it is bogus. */ + if(!has_valid_nsec) { + verbose(VERB_QUERY, "NameError response has failed to prove: " + "qname does not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + if(!has_valid_wnsec) { + verbose(VERB_QUERY, "NameError response has failed to prove: " + "covering wildcard does not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + /* Otherwise, we consider the message secure. */ + verbose(VERB_ALGO, "successfully validated NAME ERROR response."); + chase_reply->security = sec_status_secure; +} + +/** + * Given a referral response, validate rrsets and take least trusted rrset + * as the current validation status. + * + * Note that by the time this method is called, the process of finding the + * trusted DNSKEY rrset that signs this response must already have been + * completed. + * + * @param chase_reply: answer to validate. + */ +static void +validate_referral_response(struct reply_info* chase_reply) +{ + size_t i; + enum sec_status s; + /* message security equals lowest rrset security */ + chase_reply->security = sec_status_secure; + for(i=0; i<chase_reply->rrset_count; i++) { + s = ((struct packed_rrset_data*)chase_reply->rrsets[i] + ->entry.data)->security; + if(s < chase_reply->security) + chase_reply->security = s; + } + verbose(VERB_ALGO, "validated part of referral response as %s", + sec_status_to_string(chase_reply->security)); +} + +/** + * Given an "ANY" response -- a response that contains an answer to a + * qtype==ANY question, with answers. This does no checking that all + * types are present. + * + * NOTE: it may be possible to get parent-side delegation point records + * here, which won't all be signed. Right now, this routine relies on the + * upstream iterative resolver to not return these responses -- instead + * treating them as referrals. + * + * NOTE: RFC 4035 is silent on this issue, so this may change upon + * clarification. Clarification draft -05 says to not check all types are + * present. + * + * Note that by the time this method is called, the process of finding the + * trusted DNSKEY rrset that signs this response must already have been + * completed. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +static void +validate_any_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) +{ + /* all answer and auth rrsets already verified */ + /* but check if a wildcard response is given, then check NSEC/NSEC3 + * for qname denial to see if wildcard is applicable */ + uint8_t* wc = NULL; + int wc_NSEC_ok = 0; + int nsec3s_seen = 0; + size_t i; + struct ub_packed_rrset_key* s; + + if(qchase->qtype != LDNS_RR_TYPE_ANY) { + log_err("internal error: ANY validation called for non-ANY"); + chase_reply->security = sec_status_bogus; + return; + } + + /* validate the ANSWER section - this will be the answer itself */ + for(i=0; i<chase_reply->an_numrrsets; i++) { + s = chase_reply->rrsets[i]; + + /* Check to see if the rrset is the result of a wildcard + * expansion. If so, an additional check will need to be + * made in the authority section. */ + if(!val_rrset_wildcard(s, &wc)) { + log_nametypeclass(VERB_QUERY, "Positive ANY response" + " has inconsistent wildcard sigs:", + s->rk.dname, ntohs(s->rk.type), + ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + } + + /* if it was a wildcard, check for NSEC/NSEC3s in both answer + * and authority sections (NSEC may be moved to the ANSWER section) */ + if(wc != NULL) + for(i=0; i<chase_reply->an_numrrsets+chase_reply->ns_numrrsets; + i++) { + s = chase_reply->rrsets[i]; + + /* If this is a positive wildcard response, and we have a + * (just verified) NSEC record, try to use it to 1) prove + * that qname doesn't exist and 2) that the correct wildcard + * was used. */ + if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(val_nsec_proves_positive_wildcard(s, qchase, wc)) { + wc_NSEC_ok = 1; + } + /* if not, continue looking for proof */ + } + + /* Otherwise, if this is a positive wildcard response and + * we have NSEC3 records */ + if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + nsec3s_seen = 1; + } + } + + /* If this was a positive wildcard response that we haven't already + * proven, and we have NSEC3 records, try to prove it using the NSEC3 + * records. */ + if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { + /* look both in answer and auth section for NSEC3s */ + enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets, + chase_reply->an_numrrsets+chase_reply->ns_numrrsets, + qchase, kkey, wc); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "Positive ANY wildcard response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) + wc_NSEC_ok = 1; + } + + /* If after all this, we still haven't proven the positive wildcard + * response, fail. */ + if(wc != NULL && !wc_NSEC_ok) { + verbose(VERB_QUERY, "positive ANY response was wildcard " + "expansion and did not prove original data " + "did not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + verbose(VERB_ALGO, "Successfully validated positive ANY response"); + chase_reply->security = sec_status_secure; +} + +/** + * Validate CNAME response, or DNAME+CNAME. + * This is just like a positive proof, except that this is about a + * DNAME+CNAME. Possible wildcard proof. + * Difference with positive proof is that this routine refuses + * wildcarded DNAMEs. + * + * The answer and authority rrsets must already be verified as secure. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +static void +validate_cname_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) +{ + uint8_t* wc = NULL; + int wc_NSEC_ok = 0; + int nsec3s_seen = 0; + size_t i; + struct ub_packed_rrset_key* s; + + /* validate the ANSWER section - this will be the CNAME (+DNAME) */ + for(i=0; i<chase_reply->an_numrrsets; i++) { + s = chase_reply->rrsets[i]; + + /* Check to see if the rrset is the result of a wildcard + * expansion. If so, an additional check will need to be + * made in the authority section. */ + if(!val_rrset_wildcard(s, &wc)) { + log_nametypeclass(VERB_QUERY, "Cname response has " + "inconsistent wildcard sigs:", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + + /* Refuse wildcarded DNAMEs rfc 4597. + * Do not follow a wildcarded DNAME because + * its synthesized CNAME expansion is underdefined */ + if(qchase->qtype != LDNS_RR_TYPE_DNAME && + ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME && wc) { + log_nametypeclass(VERB_QUERY, "cannot validate a " + "wildcarded DNAME:", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + } + + /* AUTHORITY section */ + for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + + /* If this is a positive wildcard response, and we have a + * (just verified) NSEC record, try to use it to 1) prove + * that qname doesn't exist and 2) that the correct wildcard + * was used. */ + if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(val_nsec_proves_positive_wildcard(s, qchase, wc)) { + wc_NSEC_ok = 1; + } + /* if not, continue looking for proof */ + } + + /* Otherwise, if this is a positive wildcard response and + * we have NSEC3 records */ + if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + nsec3s_seen = 1; + } + } + + /* If this was a positive wildcard response that we haven't already + * proven, and we have NSEC3 records, try to prove it using the NSEC3 + * records. */ + if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { + enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey, wc); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "wildcard CNAME response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) + wc_NSEC_ok = 1; + } + + /* If after all this, we still haven't proven the positive wildcard + * response, fail. */ + if(wc != NULL && !wc_NSEC_ok) { + verbose(VERB_QUERY, "CNAME response was wildcard " + "expansion and did not prove original data " + "did not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + verbose(VERB_ALGO, "Successfully validated CNAME response"); + chase_reply->security = sec_status_secure; +} + +/** + * Validate CNAME NOANSWER response, no more data after a CNAME chain. + * This can be a NODATA or a NAME ERROR case, but not both at the same time. + * We don't know because the rcode has been set to NOERROR by the CNAME. + * + * The answer and authority rrsets must already be verified as secure. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +static void +validate_cname_noanswer_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, + struct key_entry_key* kkey) +{ + int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/ + uint8_t* ce = NULL; /* for wildcard nodata responses. This is the + proven closest encloser. */ + uint8_t* wc = NULL; /* for wildcard nodata responses. wildcard nsec */ + int nxdomain_valid_nsec = 0; /* if true, namerror has been proven */ + int nxdomain_valid_wnsec = 0; + int nsec3s_seen = 0; /* nsec3s seen */ + struct ub_packed_rrset_key* s; + size_t i; + + /* the AUTHORITY section */ + for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + + /* If we encounter an NSEC record, try to use it to prove + * NODATA. This needs to handle the ENT NODATA case. + * Also try to prove NAMEERROR, and absence of a wildcard */ + if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(nsec_proves_nodata(s, qchase, &wc)) { + nodata_valid_nsec = 1; + /* set wc encloser if wildcard applicable */ + } + if(val_nsec_proves_name_error(s, qchase->qname)) { + ce = nsec_closest_encloser(qchase->qname, s); + nxdomain_valid_nsec = 1; + } + if(val_nsec_proves_no_wc(s, qchase->qname, + qchase->qname_len)) + nxdomain_valid_wnsec = 1; + if(val_nsec_proves_insecuredelegation(s, qchase)) { + verbose(VERB_ALGO, "delegation is insecure"); + chase_reply->security = sec_status_insecure; + return; + } + } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + nsec3s_seen = 1; + } + } + + /* check to see if we have a wildcard NODATA proof. */ + + /* The wildcard NODATA is 1 NSEC proving that qname does not exists + * (and also proving what the closest encloser is), and 1 NSEC + * showing the matching wildcard, which must be *.closest_encloser. */ + if(wc && !ce) + nodata_valid_nsec = 0; + else if(wc && ce) { + if(query_dname_compare(wc, ce) != 0) { + nodata_valid_nsec = 0; + } + } + if(nxdomain_valid_nsec && !nxdomain_valid_wnsec) { + /* name error is missing wildcard denial proof */ + nxdomain_valid_nsec = 0; + } + + if(nodata_valid_nsec && nxdomain_valid_nsec) { + verbose(VERB_QUERY, "CNAMEchain to noanswer proves that name " + "exists and not exists, bogus"); + chase_reply->security = sec_status_bogus; + return; + } + if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) { + int nodata; + enum sec_status sec = nsec3_prove_nxornodata(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, + chase_reply->ns_numrrsets, qchase, kkey, &nodata); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "CNAMEchain to noanswer response " + "is insecure"); + chase_reply->security = sec_status_insecure; + return; + } else if(sec == sec_status_secure) { + if(nodata) + nodata_valid_nsec = 1; + else nxdomain_valid_nsec = 1; + } + } + + if(!nodata_valid_nsec && !nxdomain_valid_nsec) { + verbose(VERB_QUERY, "CNAMEchain to noanswer response failed " + "to prove status with NSEC/NSEC3"); + if(verbosity >= VERB_ALGO) + log_dns_msg("Failed CNAMEnoanswer", qchase, chase_reply); + chase_reply->security = sec_status_bogus; + return; + } + + if(nodata_valid_nsec) + verbose(VERB_ALGO, "successfully validated CNAME chain to a " + "NODATA response."); + else verbose(VERB_ALGO, "successfully validated CNAME chain to a " + "NAMEERROR response."); + chase_reply->security = sec_status_secure; +} + +/** + * Process init state for validator. + * Process the INIT state. First tier responses start in the INIT state. + * This is where they are vetted for validation suitability, and the initial + * key search is done. + * + * Currently, events the come through this routine will be either promoted + * to FINISHED/CNAME_RESP (no validation needed), FINDKEY (next step to + * validation), or will be (temporarily) retired and a new priming request + * event will be generated. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param ve: validator shared global environment. + * @param id: module id. + * @return true if the event should be processed further on return, false if + * not. + */ +static int +processInit(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve, int id) +{ + uint8_t* lookup_name; + size_t lookup_len; + struct trust_anchor* anchor; + enum val_classification subtype = val_classify_response( + qstate->query_flags, &qstate->qinfo, &vq->qchase, + vq->orig_msg->rep, vq->rrset_skip); + if(vq->restart_count > VAL_MAX_RESTART_COUNT) { + verbose(VERB_ALGO, "restart count exceeded"); + return val_error(qstate, id); + } + verbose(VERB_ALGO, "validator classification %s", + val_classification_to_string(subtype)); + if(subtype == VAL_CLASS_REFERRAL && + vq->rrset_skip < vq->orig_msg->rep->rrset_count) { + /* referral uses the rrset name as qchase, to find keys for + * that rrset */ + vq->qchase.qname = vq->orig_msg->rep-> + rrsets[vq->rrset_skip]->rk.dname; + vq->qchase.qname_len = vq->orig_msg->rep-> + rrsets[vq->rrset_skip]->rk.dname_len; + vq->qchase.qtype = ntohs(vq->orig_msg->rep-> + rrsets[vq->rrset_skip]->rk.type); + vq->qchase.qclass = ntohs(vq->orig_msg->rep-> + rrsets[vq->rrset_skip]->rk.rrset_class); + } + lookup_name = vq->qchase.qname; + lookup_len = vq->qchase.qname_len; + /* for type DS look at the parent side for keys/trustanchor */ + /* also for NSEC not at apex */ + if(vq->qchase.qtype == LDNS_RR_TYPE_DS || + (vq->qchase.qtype == LDNS_RR_TYPE_NSEC && + vq->orig_msg->rep->rrset_count > vq->rrset_skip && + ntohs(vq->orig_msg->rep->rrsets[vq->rrset_skip]->rk.type) == + LDNS_RR_TYPE_NSEC && + !(vq->orig_msg->rep->rrsets[vq->rrset_skip]-> + rk.flags&PACKED_RRSET_NSEC_AT_APEX))) { + dname_remove_label(&lookup_name, &lookup_len); + } + + val_mark_indeterminate(vq->chase_reply, qstate->env->anchors, + qstate->env->rrset_cache, qstate->env); + vq->key_entry = NULL; + vq->empty_DS_name = NULL; + vq->ds_rrset = 0; + anchor = anchors_lookup(qstate->env->anchors, + lookup_name, lookup_len, vq->qchase.qclass); + + /* Determine the signer/lookup name */ + val_find_signer(subtype, &vq->qchase, vq->orig_msg->rep, + vq->rrset_skip, &vq->signer_name, &vq->signer_len); + if(vq->signer_name != NULL && + !dname_subdomain_c(lookup_name, vq->signer_name)) { + log_nametypeclass(VERB_ALGO, "this signer name is not a parent " + "of lookupname, omitted", vq->signer_name, 0, 0); + vq->signer_name = NULL; + } + if(vq->signer_name == NULL) { + log_nametypeclass(VERB_ALGO, "no signer, using", lookup_name, + 0, 0); + } else { + lookup_name = vq->signer_name; + lookup_len = vq->signer_len; + log_nametypeclass(VERB_ALGO, "signer is", lookup_name, 0, 0); + } + + /* for NXDOMAIN it could be signed by a parent of the trust anchor */ + if(subtype == VAL_CLASS_NAMEERROR && vq->signer_name && + anchor && dname_strict_subdomain_c(anchor->name, lookup_name)){ + lock_basic_unlock(&anchor->lock); + anchor = anchors_lookup(qstate->env->anchors, + lookup_name, lookup_len, vq->qchase.qclass); + if(!anchor) { /* unsigned parent denies anchor*/ + verbose(VERB_QUERY, "unsigned parent zone denies" + " trust anchor, indeterminate"); + vq->chase_reply->security = sec_status_indeterminate; + vq->state = VAL_FINISHED_STATE; + return 1; + } + verbose(VERB_ALGO, "trust anchor NXDOMAIN by signed parent"); + } else if(subtype == VAL_CLASS_POSITIVE && + qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY && + query_dname_compare(lookup_name, qstate->qinfo.qname) == 0) { + /* is a DNSKEY so lookup a bit higher since we want to + * get it from a parent or from trustanchor */ + dname_remove_label(&lookup_name, &lookup_len); + } + + if(vq->rrset_skip > 0 || subtype == VAL_CLASS_CNAME || + subtype == VAL_CLASS_REFERRAL) { + /* extract this part of orig_msg into chase_reply for + * the eventual VALIDATE stage */ + val_fill_reply(vq->chase_reply, vq->orig_msg->rep, + vq->rrset_skip, lookup_name, lookup_len, + vq->signer_name); + if(verbosity >= VERB_ALGO) + log_dns_msg("chased extract", &vq->qchase, + vq->chase_reply); + } + + vq->key_entry = key_cache_obtain(ve->kcache, lookup_name, lookup_len, + vq->qchase.qclass, qstate->region, *qstate->env->now); + + /* there is no key(from DLV) and no trust anchor */ + if(vq->key_entry == NULL && anchor == NULL) { + /*response isn't under a trust anchor, so we cannot validate.*/ + vq->chase_reply->security = sec_status_indeterminate; + /* go to finished state to cache this result */ + vq->state = VAL_FINISHED_STATE; + return 1; + } + /* if not key, or if keyentry is *above* the trustanchor, i.e. + * the keyentry is based on another (higher) trustanchor */ + else if(vq->key_entry == NULL || (anchor && + dname_strict_subdomain_c(anchor->name, vq->key_entry->name))) { + /* trust anchor is an 'unsigned' trust anchor */ + if(anchor && anchor->numDS == 0 && anchor->numDNSKEY == 0) { + vq->chase_reply->security = sec_status_insecure; + val_mark_insecure(vq->chase_reply, anchor->name, + qstate->env->rrset_cache, qstate->env); + lock_basic_unlock(&anchor->lock); + vq->dlv_checked=1; /* skip DLV check */ + /* go to finished state to cache this result */ + vq->state = VAL_FINISHED_STATE; + return 1; + } + /* fire off a trust anchor priming query. */ + verbose(VERB_DETAIL, "prime trust anchor"); + if(!prime_trust_anchor(qstate, vq, id, anchor)) { + lock_basic_unlock(&anchor->lock); + return val_error(qstate, id); + } + lock_basic_unlock(&anchor->lock); + /* and otherwise, don't continue processing this event. + * (it will be reactivated when the priming query returns). */ + vq->state = VAL_FINDKEY_STATE; + return 0; + } + if(anchor) { + lock_basic_unlock(&anchor->lock); + } + + if(key_entry_isnull(vq->key_entry)) { + /* response is under a null key, so we cannot validate + * However, we do set the status to INSECURE, since it is + * essentially proven insecure. */ + vq->chase_reply->security = sec_status_insecure; + val_mark_insecure(vq->chase_reply, vq->key_entry->name, + qstate->env->rrset_cache, qstate->env); + /* go to finished state to cache this result */ + vq->state = VAL_FINISHED_STATE; + return 1; + } else if(key_entry_isbad(vq->key_entry)) { + /* key is bad, chain is bad, reply is bogus */ + errinf_dname(qstate, "key for validation", vq->key_entry->name); + errinf(qstate, "is marked as invalid"); + if(key_entry_get_reason(vq->key_entry)) { + errinf(qstate, "because of a previous"); + errinf(qstate, key_entry_get_reason(vq->key_entry)); + } + /* no retries, stop bothering the authority until timeout */ + vq->restart_count = VAL_MAX_RESTART_COUNT; + vq->chase_reply->security = sec_status_bogus; + vq->state = VAL_FINISHED_STATE; + return 1; + } + + /* otherwise, we have our "closest" cached key -- continue + * processing in the next state. */ + vq->state = VAL_FINDKEY_STATE; + return 1; +} + +/** + * Process the FINDKEY state. Generally this just calculates the next name + * to query and either issues a DS or a DNSKEY query. It will check to see + * if the correct key has already been reached, in which case it will + * advance the event to the next state. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param id: module id. + * @return true if the event should be processed further on return, false if + * not. + */ +static int +processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id) +{ + uint8_t* target_key_name, *current_key_name; + size_t target_key_len; + int strip_lab; + + log_query_info(VERB_ALGO, "validator: FindKey", &vq->qchase); + /* We know that state.key_entry is not 0 or bad key -- if it were, + * then previous processing should have directed this event to + * a different state. + * It could be an isnull key, which signals that a DLV was just + * done and the DNSKEY after the DLV failed with dnssec-retry state + * and the DNSKEY has to be performed again. */ + log_assert(vq->key_entry && !key_entry_isbad(vq->key_entry)); + if(key_entry_isnull(vq->key_entry)) { + if(!generate_request(qstate, id, vq->ds_rrset->rk.dname, + vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY, + vq->qchase.qclass, BIT_CD)) { + log_err("mem error generating DNSKEY request"); + return val_error(qstate, id); + } + return 0; + } + + target_key_name = vq->signer_name; + target_key_len = vq->signer_len; + if(!target_key_name) { + target_key_name = vq->qchase.qname; + target_key_len = vq->qchase.qname_len; + } + + current_key_name = vq->key_entry->name; + + /* If our current key entry matches our target, then we are done. */ + if(query_dname_compare(target_key_name, current_key_name) == 0) { + vq->state = VAL_VALIDATE_STATE; + return 1; + } + + if(vq->empty_DS_name) { + /* if the last empty nonterminal/emptyDS name we detected is + * below the current key, use that name to make progress + * along the chain of trust */ + if(query_dname_compare(target_key_name, + vq->empty_DS_name) == 0) { + /* do not query for empty_DS_name again */ + verbose(VERB_ALGO, "Cannot retrieve DS for signature"); + errinf(qstate, "no signatures"); + errinf_origin(qstate, qstate->reply_origin); + vq->chase_reply->security = sec_status_bogus; + vq->state = VAL_FINISHED_STATE; + return 1; + } + current_key_name = vq->empty_DS_name; + } + + log_nametypeclass(VERB_ALGO, "current keyname", current_key_name, + LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN); + log_nametypeclass(VERB_ALGO, "target keyname", target_key_name, + LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN); + /* assert we are walking down the DNS tree */ + if(!dname_subdomain_c(target_key_name, current_key_name)) { + verbose(VERB_ALGO, "bad signer name"); + vq->chase_reply->security = sec_status_bogus; + vq->state = VAL_FINISHED_STATE; + return 1; + } + /* so this value is >= -1 */ + strip_lab = dname_count_labels(target_key_name) - + dname_count_labels(current_key_name) - 1; + log_assert(strip_lab >= -1); + verbose(VERB_ALGO, "striplab %d", strip_lab); + if(strip_lab > 0) { + dname_remove_labels(&target_key_name, &target_key_len, + strip_lab); + } + log_nametypeclass(VERB_ALGO, "next keyname", target_key_name, + LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN); + + /* The next step is either to query for the next DS, or to query + * for the next DNSKEY. */ + if(vq->ds_rrset) + log_nametypeclass(VERB_ALGO, "DS RRset", vq->ds_rrset->rk.dname, LDNS_RR_TYPE_DS, LDNS_RR_CLASS_IN); + else verbose(VERB_ALGO, "No DS RRset"); + + if(vq->ds_rrset && query_dname_compare(vq->ds_rrset->rk.dname, + vq->key_entry->name) != 0) { + if(!generate_request(qstate, id, vq->ds_rrset->rk.dname, + vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY, + vq->qchase.qclass, BIT_CD)) { + log_err("mem error generating DNSKEY request"); + return val_error(qstate, id); + } + return 0; + } + + if(!vq->ds_rrset || query_dname_compare(vq->ds_rrset->rk.dname, + target_key_name) != 0) { + /* check if there is a cache entry : pick up an NSEC if + * there is no DS, check if that NSEC has DS-bit unset, and + * thus can disprove the secure delagation we seek. + * We can then use that NSEC even in the absence of a SOA + * record that would be required by the iterator to supply + * a completely protocol-correct response. + * Uses negative cache for NSEC3 lookup of DS responses. */ + /* only if cache not blacklisted, of course */ + struct dns_msg* msg; + if(!qstate->blacklist && !vq->chain_blacklist && + (msg=val_find_DS(qstate->env, target_key_name, + target_key_len, vq->qchase.qclass, qstate->region, + vq->key_entry->name)) ) { + verbose(VERB_ALGO, "Process cached DS response"); + process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR, + msg, &msg->qinfo, NULL); + return 1; /* continue processing ds-response results */ + } + if(!generate_request(qstate, id, target_key_name, + target_key_len, LDNS_RR_TYPE_DS, vq->qchase.qclass, + BIT_CD)) { + log_err("mem error generating DS request"); + return val_error(qstate, id); + } + return 0; + } + + /* Otherwise, it is time to query for the DNSKEY */ + if(!generate_request(qstate, id, vq->ds_rrset->rk.dname, + vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY, + vq->qchase.qclass, BIT_CD)) { + log_err("mem error generating DNSKEY request"); + return val_error(qstate, id); + } + + return 0; +} + +/** + * Process the VALIDATE stage, the init and findkey stages are finished, + * and the right keys are available to validate the response. + * Or, there are no keys available, in order to invalidate the response. + * + * After validation, the status is recorded in the message and rrsets, + * and finished state is started. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param ve: validator shared global environment. + * @param id: module id. + * @return true if the event should be processed further on return, false if + * not. + */ +static int +processValidate(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve, int id) +{ + enum val_classification subtype; + + if(!vq->key_entry) { + verbose(VERB_ALGO, "validate: no key entry, failed"); + return val_error(qstate, id); + } + + /* This is the default next state. */ + vq->state = VAL_FINISHED_STATE; + + /* Unsigned responses must be underneath a "null" key entry.*/ + if(key_entry_isnull(vq->key_entry)) { + verbose(VERB_DETAIL, "Verified that %sresponse is INSECURE", + vq->signer_name?"":"unsigned "); + vq->chase_reply->security = sec_status_insecure; + val_mark_insecure(vq->chase_reply, vq->key_entry->name, + qstate->env->rrset_cache, qstate->env); + key_cache_insert(ve->kcache, vq->key_entry, qstate); + return 1; + } + + if(key_entry_isbad(vq->key_entry)) { + log_nametypeclass(VERB_DETAIL, "Could not establish a chain " + "of trust to keys for", vq->key_entry->name, + LDNS_RR_TYPE_DNSKEY, vq->key_entry->key_class); + vq->chase_reply->security = sec_status_bogus; + errinf(qstate, "while building chain of trust"); + if(vq->restart_count >= VAL_MAX_RESTART_COUNT) + key_cache_insert(ve->kcache, vq->key_entry, qstate); + return 1; + } + + /* signerName being null is the indicator that this response was + * unsigned */ + if(vq->signer_name == NULL) { + log_query_info(VERB_ALGO, "processValidate: state has no " + "signer name", &vq->qchase); + verbose(VERB_DETAIL, "Could not establish validation of " + "INSECURE status of unsigned response."); + errinf(qstate, "no signatures"); + errinf_origin(qstate, qstate->reply_origin); + vq->chase_reply->security = sec_status_bogus; + return 1; + } + subtype = val_classify_response(qstate->query_flags, &qstate->qinfo, + &vq->qchase, vq->orig_msg->rep, vq->rrset_skip); + + /* check signatures in the message; + * answer and authority must be valid, additional is only checked. */ + if(!validate_msg_signatures(qstate, qstate->env, ve, &vq->qchase, + vq->chase_reply, vq->key_entry)) { + /* workaround bad recursor out there that truncates (even + * with EDNS4k) to 512 by removing RRSIG from auth section + * for positive replies*/ + if((subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_ANY + || subtype == VAL_CLASS_CNAME) && + detect_wrongly_truncated(vq->orig_msg->rep)) { + /* truncate the message some more */ + vq->orig_msg->rep->ns_numrrsets = 0; + vq->orig_msg->rep->ar_numrrsets = 0; + vq->orig_msg->rep->rrset_count = + vq->orig_msg->rep->an_numrrsets; + vq->chase_reply->ns_numrrsets = 0; + vq->chase_reply->ar_numrrsets = 0; + vq->chase_reply->rrset_count = + vq->chase_reply->an_numrrsets; + qstate->errinf = NULL; + } + else { + verbose(VERB_DETAIL, "Validate: message contains " + "bad rrsets"); + return 1; + } + } + + switch(subtype) { + case VAL_CLASS_POSITIVE: + verbose(VERB_ALGO, "Validating a positive response"); + validate_positive_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); + verbose(VERB_DETAIL, "validate(positive): %s", + sec_status_to_string( + vq->chase_reply->security)); + break; + + case VAL_CLASS_NODATA: + verbose(VERB_ALGO, "Validating a nodata response"); + validate_nodata_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); + verbose(VERB_DETAIL, "validate(nodata): %s", + sec_status_to_string( + vq->chase_reply->security)); + break; + + case VAL_CLASS_NAMEERROR: + verbose(VERB_ALGO, "Validating a nxdomain response"); + validate_nameerror_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); + verbose(VERB_DETAIL, "validate(nxdomain): %s", + sec_status_to_string( + vq->chase_reply->security)); + break; + + case VAL_CLASS_CNAME: + verbose(VERB_ALGO, "Validating a cname response"); + validate_cname_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); + verbose(VERB_DETAIL, "validate(cname): %s", + sec_status_to_string( + vq->chase_reply->security)); + break; + + case VAL_CLASS_CNAMENOANSWER: + verbose(VERB_ALGO, "Validating a cname noanswer " + "response"); + validate_cname_noanswer_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); + verbose(VERB_DETAIL, "validate(cname_noanswer): %s", + sec_status_to_string( + vq->chase_reply->security)); + break; + + case VAL_CLASS_REFERRAL: + verbose(VERB_ALGO, "Validating a referral response"); + validate_referral_response(vq->chase_reply); + verbose(VERB_DETAIL, "validate(referral): %s", + sec_status_to_string( + vq->chase_reply->security)); + break; + + case VAL_CLASS_ANY: + verbose(VERB_ALGO, "Validating a positive ANY " + "response"); + validate_any_response(qstate->env, ve, &vq->qchase, + vq->chase_reply, vq->key_entry); + verbose(VERB_DETAIL, "validate(positive_any): %s", + sec_status_to_string( + vq->chase_reply->security)); + break; + + default: + log_err("validate: unhandled response subtype: %d", + subtype); + } + if(vq->chase_reply->security == sec_status_bogus) { + if(subtype == VAL_CLASS_POSITIVE) + errinf(qstate, "wildcard"); + else errinf(qstate, val_classification_to_string(subtype)); + errinf(qstate, "proof failed"); + errinf_origin(qstate, qstate->reply_origin); + } + + return 1; +} + +/** + * Init DLV check. + * Called when a query is determined by other trust anchors to be insecure + * (or indeterminate). Then we look if there is a key in the DLV. + * Performs aggressive negative cache check to see if there is no key. + * Otherwise, spawns a DLV query, and changes to the DLV wait state. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param ve: validator shared global environment. + * @param id: module id. + * @return true if there is no DLV. + * false: processing is finished for the validator operate(). + * This function may exit in three ways: + * o no DLV (agressive cache), so insecure. (true) + * o error - stop processing (false) + * o DLV lookup was started, stop processing (false) + */ +static int +val_dlv_init(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve, int id) +{ + uint8_t* nm; + size_t nm_len; + /* there must be a DLV configured */ + log_assert(qstate->env->anchors->dlv_anchor); + /* this bool is true to avoid looping in the DLV checks */ + log_assert(vq->dlv_checked); + + /* init the DLV lookup variables */ + vq->dlv_lookup_name = NULL; + vq->dlv_lookup_name_len = 0; + vq->dlv_insecure_at = NULL; + vq->dlv_insecure_at_len = 0; + + /* Determine the name for which we want to lookup DLV. + * This name is for the current message, or + * for the current RRset for CNAME, referral subtypes. + * If there is a signer, use that, otherwise the domain name */ + if(vq->signer_name) { + nm = vq->signer_name; + nm_len = vq->signer_len; + } else { + /* use qchase */ + nm = vq->qchase.qname; + nm_len = vq->qchase.qname_len; + if(vq->qchase.qtype == LDNS_RR_TYPE_DS) + dname_remove_label(&nm, &nm_len); + } + log_nametypeclass(VERB_ALGO, "DLV init look", nm, LDNS_RR_TYPE_DS, + vq->qchase.qclass); + log_assert(nm && nm_len); + /* sanity check: no DLV lookups below the DLV anchor itself. + * Like, an securely insecure delegation there makes no sense. */ + if(dname_subdomain_c(nm, qstate->env->anchors->dlv_anchor->name)) { + verbose(VERB_ALGO, "DLV lookup within DLV repository denied"); + return 1; + } + /* concat name (minus root label) + dlv name */ + vq->dlv_lookup_name_len = nm_len - 1 + + qstate->env->anchors->dlv_anchor->namelen; + vq->dlv_lookup_name = regional_alloc(qstate->region, + vq->dlv_lookup_name_len); + if(!vq->dlv_lookup_name) { + log_err("Out of memory preparing DLV lookup"); + return val_error(qstate, id); + } + memmove(vq->dlv_lookup_name, nm, nm_len-1); + memmove(vq->dlv_lookup_name+nm_len-1, + qstate->env->anchors->dlv_anchor->name, + qstate->env->anchors->dlv_anchor->namelen); + log_nametypeclass(VERB_ALGO, "DLV name", vq->dlv_lookup_name, + LDNS_RR_TYPE_DLV, vq->qchase.qclass); + + /* determine where the insecure point was determined, the DLV must + * be equal or below that to continue building the trust chain + * down. May be NULL if no trust chain was built yet */ + nm = NULL; + if(vq->key_entry && key_entry_isnull(vq->key_entry)) { + nm = vq->key_entry->name; + nm_len = vq->key_entry->namelen; + } + if(nm) { + vq->dlv_insecure_at_len = nm_len - 1 + + qstate->env->anchors->dlv_anchor->namelen; + vq->dlv_insecure_at = regional_alloc(qstate->region, + vq->dlv_insecure_at_len); + if(!vq->dlv_insecure_at) { + log_err("Out of memory preparing DLV lookup"); + return val_error(qstate, id); + } + memmove(vq->dlv_insecure_at, nm, nm_len-1); + memmove(vq->dlv_insecure_at+nm_len-1, + qstate->env->anchors->dlv_anchor->name, + qstate->env->anchors->dlv_anchor->namelen); + log_nametypeclass(VERB_ALGO, "insecure_at", + vq->dlv_insecure_at, 0, vq->qchase.qclass); + } + + /* If we can find the name in the aggressive negative cache, + * give up; insecure is the answer */ + while(val_neg_dlvlookup(ve->neg_cache, vq->dlv_lookup_name, + vq->dlv_lookup_name_len, vq->qchase.qclass, + qstate->env->rrset_cache, *qstate->env->now)) { + /* go up */ + dname_remove_label(&vq->dlv_lookup_name, + &vq->dlv_lookup_name_len); + /* too high? */ + if(!dname_subdomain_c(vq->dlv_lookup_name, + qstate->env->anchors->dlv_anchor->name)) { + verbose(VERB_ALGO, "ask above dlv repo"); + return 1; /* Above the repo is insecure */ + } + /* above chain of trust? */ + if(vq->dlv_insecure_at && !dname_subdomain_c( + vq->dlv_lookup_name, vq->dlv_insecure_at)) { + verbose(VERB_ALGO, "ask above insecure endpoint"); + return 1; + } + } + + /* perform a lookup for the DLV; with validation */ + vq->state = VAL_DLVLOOKUP_STATE; + if(!generate_request(qstate, id, vq->dlv_lookup_name, + vq->dlv_lookup_name_len, LDNS_RR_TYPE_DLV, + vq->qchase.qclass, 0)) { + return val_error(qstate, id); + } + + /* Find the closest encloser DLV from the repository. + * then that is used to build another chain of trust + * This may first require a query 'too low' that has NSECs in + * the answer, from which we determine the closest encloser DLV. + * When determine the closest encloser, skip empty nonterminals, + * since we want a nonempty node in the DLV repository. */ + + return 0; +} + +/** + * The Finished state. The validation status (good or bad) has been determined. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param ve: validator shared global environment. + * @param id: module id. + * @return true if the event should be processed further on return, false if + * not. + */ +static int +processFinished(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve, int id) +{ + enum val_classification subtype = val_classify_response( + qstate->query_flags, &qstate->qinfo, &vq->qchase, + vq->orig_msg->rep, vq->rrset_skip); + + /* if the result is insecure or indeterminate and we have not + * checked the DLV yet, check the DLV */ + if((vq->chase_reply->security == sec_status_insecure || + vq->chase_reply->security == sec_status_indeterminate) && + qstate->env->anchors->dlv_anchor && !vq->dlv_checked) { + vq->dlv_checked = 1; + if(!val_dlv_init(qstate, vq, ve, id)) + return 0; + } + + /* store overall validation result in orig_msg */ + if(vq->rrset_skip == 0) + vq->orig_msg->rep->security = vq->chase_reply->security; + else if(vq->rrset_skip < vq->orig_msg->rep->an_numrrsets + + vq->orig_msg->rep->ns_numrrsets) { + /* ignore sec status of additional section if a referral + * type message skips there and + * use the lowest security status as end result. */ + if(vq->chase_reply->security < vq->orig_msg->rep->security) + vq->orig_msg->rep->security = + vq->chase_reply->security; + } + + if(subtype == VAL_CLASS_REFERRAL) { + /* for a referral, move to next unchecked rrset and check it*/ + vq->rrset_skip = val_next_unchecked(vq->orig_msg->rep, + vq->rrset_skip); + if(vq->rrset_skip < vq->orig_msg->rep->rrset_count) { + /* and restart for this rrset */ + verbose(VERB_ALGO, "validator: go to next rrset"); + vq->chase_reply->security = sec_status_unchecked; + vq->dlv_checked = 0; /* can do DLV for this RR */ + vq->state = VAL_INIT_STATE; + return 1; + } + /* referral chase is done */ + } + if(vq->chase_reply->security != sec_status_bogus && + subtype == VAL_CLASS_CNAME) { + /* chase the CNAME; process next part of the message */ + if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep, + &vq->rrset_skip)) { + verbose(VERB_ALGO, "validator: failed to chase CNAME"); + vq->orig_msg->rep->security = sec_status_bogus; + } else { + /* restart process for new qchase at rrset_skip */ + log_query_info(VERB_ALGO, "validator: chased to", + &vq->qchase); + vq->chase_reply->security = sec_status_unchecked; + vq->dlv_checked = 0; /* can do DLV for this RR */ + vq->state = VAL_INIT_STATE; + return 1; + } + } + + if(vq->orig_msg->rep->security == sec_status_secure) { + /* If the message is secure, check that all rrsets are + * secure (i.e. some inserted RRset for CNAME chain with + * a different signer name). And drop additional rrsets + * that are not secure (if clean-additional option is set) */ + /* this may cause the msg to be marked bogus */ + val_check_nonsecure(ve, vq->orig_msg->rep); + if(vq->orig_msg->rep->security == sec_status_secure) { + log_query_info(VERB_DETAIL, "validation success", + &qstate->qinfo); + } + } + + /* if the result is bogus - set message ttl to bogus ttl to avoid + * endless bogus revalidation */ + if(vq->orig_msg->rep->security == sec_status_bogus) { + /* see if we can try again to fetch data */ + if(vq->restart_count < VAL_MAX_RESTART_COUNT) { + int restart_count = vq->restart_count+1; + verbose(VERB_ALGO, "validation failed, " + "blacklist and retry to fetch data"); + val_blacklist(&qstate->blacklist, qstate->region, + qstate->reply_origin, 0); + qstate->reply_origin = NULL; + qstate->errinf = NULL; + memset(vq, 0, sizeof(*vq)); + vq->restart_count = restart_count; + vq->state = VAL_INIT_STATE; + verbose(VERB_ALGO, "pass back to next module"); + qstate->ext_state[id] = module_restart_next; + return 0; + } + + vq->orig_msg->rep->ttl = ve->bogus_ttl; + vq->orig_msg->rep->prefetch_ttl = + PREFETCH_TTL_CALC(vq->orig_msg->rep->ttl); + if(qstate->env->cfg->val_log_level >= 1 && + !qstate->env->cfg->val_log_squelch) { + if(qstate->env->cfg->val_log_level < 2) + log_query_info(0, "validation failure", + &qstate->qinfo); + else { + char* err = errinf_to_str(qstate); + if(err) log_info("%s", err); + free(err); + } + } + /* If we are in permissive mode, bogus gets indeterminate */ + if(ve->permissive_mode) + vq->orig_msg->rep->security = sec_status_indeterminate; + } + + /* store results in cache */ + if(qstate->query_flags&BIT_RD) { + /* if secure, this will override cache anyway, no need + * to check if from parentNS */ + if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, + vq->orig_msg->rep, 0, qstate->prefetch_leeway, 0, NULL)) { + log_err("out of memory caching validator results"); + } + } else { + /* for a referral, store the verified RRsets */ + /* and this does not get prefetched, so no leeway */ + if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, + vq->orig_msg->rep, 1, 0, 0, NULL)) { + log_err("out of memory caching validator results"); + } + } + qstate->return_rcode = LDNS_RCODE_NOERROR; + qstate->return_msg = vq->orig_msg; + qstate->ext_state[id] = module_finished; + return 0; +} + +/** + * The DLVLookup state. Process DLV lookups. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param ve: validator shared global environment. + * @param id: module id. + * @return true if the event should be processed further on return, false if + * not. + */ +static int +processDLVLookup(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve, int id) +{ + /* see if this we are ready to continue normal resolution */ + /* we may need more DLV lookups */ + if(vq->dlv_status==dlv_error) + verbose(VERB_ALGO, "DLV woke up with status dlv_error"); + else if(vq->dlv_status==dlv_success) + verbose(VERB_ALGO, "DLV woke up with status dlv_success"); + else if(vq->dlv_status==dlv_ask_higher) + verbose(VERB_ALGO, "DLV woke up with status dlv_ask_higher"); + else if(vq->dlv_status==dlv_there_is_no_dlv) + verbose(VERB_ALGO, "DLV woke up with status dlv_there_is_no_dlv"); + else verbose(VERB_ALGO, "DLV woke up with status unknown"); + + if(vq->dlv_status == dlv_error) { + verbose(VERB_QUERY, "failed DLV lookup"); + return val_error(qstate, id); + } else if(vq->dlv_status == dlv_success) { + uint8_t* nm; + size_t nmlen; + /* chain continues with DNSKEY, continue in FINDKEY */ + vq->state = VAL_FINDKEY_STATE; + + /* strip off the DLV suffix from the name; could result in . */ + log_assert(dname_subdomain_c(vq->ds_rrset->rk.dname, + qstate->env->anchors->dlv_anchor->name)); + nmlen = vq->ds_rrset->rk.dname_len - + qstate->env->anchors->dlv_anchor->namelen + 1; + nm = regional_alloc_init(qstate->region, + vq->ds_rrset->rk.dname, nmlen); + if(!nm) { + log_err("Out of memory in DLVLook"); + return val_error(qstate, id); + } + nm[nmlen-1] = 0; + + vq->ds_rrset->rk.dname = nm; + vq->ds_rrset->rk.dname_len = nmlen; + + /* create a nullentry for the key so the dnskey lookup + * can be retried after a validation failure for it */ + vq->key_entry = key_entry_create_null(qstate->region, + nm, nmlen, vq->qchase.qclass, 0, 0); + if(!vq->key_entry) { + log_err("Out of memory in DLVLook"); + return val_error(qstate, id); + } + + if(!generate_request(qstate, id, vq->ds_rrset->rk.dname, + vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY, + vq->qchase.qclass, BIT_CD)) { + log_err("mem error generating DNSKEY request"); + return val_error(qstate, id); + } + return 0; + } else if(vq->dlv_status == dlv_there_is_no_dlv) { + /* continue with the insecure result we got */ + vq->state = VAL_FINISHED_STATE; + return 1; + } + log_assert(vq->dlv_status == dlv_ask_higher); + + /* ask higher, make sure we stay in DLV repo, below dlv_at */ + if(!dname_subdomain_c(vq->dlv_lookup_name, + qstate->env->anchors->dlv_anchor->name)) { + /* just like, there is no DLV */ + verbose(VERB_ALGO, "ask above dlv repo"); + vq->state = VAL_FINISHED_STATE; + return 1; + } + if(vq->dlv_insecure_at && !dname_subdomain_c(vq->dlv_lookup_name, + vq->dlv_insecure_at)) { + /* already checked a chain lower than dlv_lookup_name */ + verbose(VERB_ALGO, "ask above insecure endpoint"); + log_nametypeclass(VERB_ALGO, "enpt", vq->dlv_insecure_at, 0, 0); + vq->state = VAL_FINISHED_STATE; + return 1; + } + + /* check negative cache before making new request */ + if(val_neg_dlvlookup(ve->neg_cache, vq->dlv_lookup_name, + vq->dlv_lookup_name_len, vq->qchase.qclass, + qstate->env->rrset_cache, *qstate->env->now)) { + /* does not exist, go up one (go higher). */ + dname_remove_label(&vq->dlv_lookup_name, + &vq->dlv_lookup_name_len); + /* limit number of labels, limited number of recursion */ + return processDLVLookup(qstate, vq, ve, id); + } + + if(!generate_request(qstate, id, vq->dlv_lookup_name, + vq->dlv_lookup_name_len, LDNS_RR_TYPE_DLV, + vq->qchase.qclass, 0)) { + return val_error(qstate, id); + } + + return 0; +} + +/** + * Handle validator state. + * If a method returns true, the next state is started. If false, then + * processing will stop. + * @param qstate: query state. + * @param vq: validator query state. + * @param ve: validator shared global environment. + * @param id: module id. + */ +static void +val_handle(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve, int id) +{ + int cont = 1; + while(cont) { + verbose(VERB_ALGO, "val handle processing q with state %s", + val_state_to_string(vq->state)); + switch(vq->state) { + case VAL_INIT_STATE: + cont = processInit(qstate, vq, ve, id); + break; + case VAL_FINDKEY_STATE: + cont = processFindKey(qstate, vq, id); + break; + case VAL_VALIDATE_STATE: + cont = processValidate(qstate, vq, ve, id); + break; + case VAL_FINISHED_STATE: + cont = processFinished(qstate, vq, ve, id); + break; + case VAL_DLVLOOKUP_STATE: + cont = processDLVLookup(qstate, vq, ve, id); + break; + default: + log_warn("validator: invalid state %d", + vq->state); + cont = 0; + break; + } + } +} + +void +val_operate(struct module_qstate* qstate, enum module_ev event, int id, + struct outbound_entry* outbound) +{ + struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; + struct val_qstate* vq = (struct val_qstate*)qstate->minfo[id]; + verbose(VERB_QUERY, "validator[module %d] operate: extstate:%s " + "event:%s", id, strextstate(qstate->ext_state[id]), + strmodulevent(event)); + log_query_info(VERB_QUERY, "validator operate: query", + &qstate->qinfo); + if(vq && qstate->qinfo.qname != vq->qchase.qname) + log_query_info(VERB_QUERY, "validator operate: chased to", + &vq->qchase); + (void)outbound; + if(event == module_event_new || + (event == module_event_pass && vq == NULL)) { + /* pass request to next module, to get it */ + verbose(VERB_ALGO, "validator: pass to next module"); + qstate->ext_state[id] = module_wait_module; + return; + } + if(event == module_event_moddone) { + /* check if validation is needed */ + verbose(VERB_ALGO, "validator: nextmodule returned"); + if(!needs_validation(qstate, qstate->return_rcode, + qstate->return_msg)) { + /* no need to validate this */ + if(qstate->return_msg) + qstate->return_msg->rep->security = + sec_status_indeterminate; + qstate->ext_state[id] = module_finished; + return; + } + if(already_validated(qstate->return_msg)) { + qstate->ext_state[id] = module_finished; + return; + } + /* qclass ANY should have validation result from spawned + * queries. If we get here, it is bogus or an internal error */ + if(qstate->qinfo.qclass == LDNS_RR_CLASS_ANY) { + verbose(VERB_ALGO, "cannot validate classANY: bogus"); + if(qstate->return_msg) + qstate->return_msg->rep->security = + sec_status_bogus; + qstate->ext_state[id] = module_finished; + return; + } + /* create state to start validation */ + qstate->ext_state[id] = module_error; /* override this */ + if(!vq) { + vq = val_new(qstate, id); + if(!vq) { + log_err("validator: malloc failure"); + qstate->ext_state[id] = module_error; + return; + } + } else if(!vq->orig_msg) { + if(!val_new_getmsg(qstate, vq)) { + log_err("validator: malloc failure"); + qstate->ext_state[id] = module_error; + return; + } + } + val_handle(qstate, vq, ve, id); + return; + } + if(event == module_event_pass) { + qstate->ext_state[id] = module_error; /* override this */ + /* continue processing, since val_env exists */ + val_handle(qstate, vq, ve, id); + return; + } + log_err("validator: bad event %s", strmodulevent(event)); + qstate->ext_state[id] = module_error; + return; +} + +/** + * Evaluate the response to a priming request. + * + * @param dnskey_rrset: DNSKEY rrset (can be NULL if none) in prime reply. + * (this rrset is allocated in the wrong region, not the qstate). + * @param ta: trust anchor. + * @param qstate: qstate that needs key. + * @param id: module id. + * @return new key entry or NULL on allocation failure. + * The key entry will either contain a validated DNSKEY rrset, or + * represent a Null key (query failed, but validation did not), or a + * Bad key (validation failed). + */ +static struct key_entry_key* +primeResponseToKE(struct ub_packed_rrset_key* dnskey_rrset, + struct trust_anchor* ta, struct module_qstate* qstate, int id) +{ + struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; + struct key_entry_key* kkey = NULL; + enum sec_status sec = sec_status_unchecked; + char* reason = NULL; + int downprot = 1; + + if(!dnskey_rrset) { + log_nametypeclass(VERB_OPS, "failed to prime trust anchor -- " + "could not fetch DNSKEY rrset", + ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); + if(qstate->env->cfg->harden_dnssec_stripped) { + errinf(qstate, "no DNSKEY rrset"); + kkey = key_entry_create_bad(qstate->region, ta->name, + ta->namelen, ta->dclass, BOGUS_KEY_TTL, + *qstate->env->now); + } else kkey = key_entry_create_null(qstate->region, ta->name, + ta->namelen, ta->dclass, NULL_KEY_TTL, + *qstate->env->now); + if(!kkey) { + log_err("out of memory: allocate fail prime key"); + return NULL; + } + return kkey; + } + /* attempt to verify with trust anchor DS and DNSKEY */ + kkey = val_verify_new_DNSKEYs_with_ta(qstate->region, qstate->env, ve, + dnskey_rrset, ta->ds_rrset, ta->dnskey_rrset, downprot, + &reason); + if(!kkey) { + log_err("out of memory: verifying prime TA"); + return NULL; + } + if(key_entry_isgood(kkey)) + sec = sec_status_secure; + else + sec = sec_status_bogus; + verbose(VERB_DETAIL, "validate keys with anchor(DS): %s", + sec_status_to_string(sec)); + + if(sec != sec_status_secure) { + log_nametypeclass(VERB_OPS, "failed to prime trust anchor -- " + "DNSKEY rrset is not secure", + ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); + /* NOTE: in this case, we should probably reject the trust + * anchor for longer, perhaps forever. */ + if(qstate->env->cfg->harden_dnssec_stripped) { + errinf(qstate, reason); + kkey = key_entry_create_bad(qstate->region, ta->name, + ta->namelen, ta->dclass, BOGUS_KEY_TTL, + *qstate->env->now); + } else kkey = key_entry_create_null(qstate->region, ta->name, + ta->namelen, ta->dclass, NULL_KEY_TTL, + *qstate->env->now); + if(!kkey) { + log_err("out of memory: allocate null prime key"); + return NULL; + } + return kkey; + } + + log_nametypeclass(VERB_DETAIL, "Successfully primed trust anchor", + ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); + return kkey; +} + +/** + * In inform supers, with the resulting message and rcode and the current + * keyset in the super state, validate the DS response, returning a KeyEntry. + * + * @param qstate: query state that is validating and asked for a DS. + * @param vq: validator query state + * @param id: module id. + * @param rcode: rcode result value. + * @param msg: result message (if rcode is OK). + * @param qinfo: from the sub query state, query info. + * @param ke: the key entry to return. It returns + * is_bad if the DS response fails to validate, is_null if the + * DS response indicated an end to secure space, is_good if the DS + * validated. It returns ke=NULL if the DS response indicated that the + * request wasn't a delegation point. + * @return 0 on servfail error (malloc failure). + */ +static int +ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, + struct key_entry_key** ke) +{ + struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; + char* reason = NULL; + enum val_classification subtype; + if(rcode != LDNS_RCODE_NOERROR) { + char* rc = ldns_pkt_rcode2str(rcode); + /* errors here pretty much break validation */ + verbose(VERB_DETAIL, "DS response was error, thus bogus"); + errinf(qstate, rc); + errinf(qstate, "no DS"); + free(rc); + goto return_bogus; + } + + subtype = val_classify_response(BIT_RD, qinfo, qinfo, msg->rep, 0); + if(subtype == VAL_CLASS_POSITIVE) { + struct ub_packed_rrset_key* ds; + enum sec_status sec; + ds = reply_find_answer_rrset(qinfo, msg->rep); + /* If there was no DS rrset, then we have mis-classified + * this message. */ + if(!ds) { + log_warn("internal error: POSITIVE DS response was " + "missing DS."); + errinf(qstate, "no DS record"); + goto return_bogus; + } + /* Verify only returns BOGUS or SECURE. If the rrset is + * bogus, then we are done. */ + sec = val_verify_rrset_entry(qstate->env, ve, ds, + vq->key_entry, &reason); + if(sec != sec_status_secure) { + verbose(VERB_DETAIL, "DS rrset in DS response did " + "not verify"); + errinf(qstate, reason); + goto return_bogus; + } + + /* If the DS rrset validates, we still have to make sure + * that they are usable. */ + if(!val_dsset_isusable(ds)) { + /* If they aren't usable, then we treat it like + * there was no DS. */ + *ke = key_entry_create_null(qstate->region, + qinfo->qname, qinfo->qname_len, qinfo->qclass, + ub_packed_rrset_ttl(ds), *qstate->env->now); + return (*ke) != NULL; + } + + /* Otherwise, we return the positive response. */ + log_query_info(VERB_DETAIL, "validated DS", qinfo); + *ke = key_entry_create_rrset(qstate->region, + qinfo->qname, qinfo->qname_len, qinfo->qclass, ds, + NULL, *qstate->env->now); + return (*ke) != NULL; + } else if(subtype == VAL_CLASS_NODATA || + subtype == VAL_CLASS_NAMEERROR) { + /* NODATA means that the qname exists, but that there was + * no DS. This is a pretty normal case. */ + uint32_t proof_ttl = 0; + enum sec_status sec; + + /* make sure there are NSECs or NSEC3s with signatures */ + if(!val_has_signed_nsecs(msg->rep, &reason)) { + verbose(VERB_ALGO, "no NSECs: %s", reason); + errinf(qstate, reason); + goto return_bogus; + } + + /* For subtype Name Error. + * attempt ANS 2.8.1.0 compatibility where it sets rcode + * to nxdomain, but really this is an Nodata/Noerror response. + * Find and prove the empty nonterminal in that case */ + + /* Try to prove absence of the DS with NSEC */ + sec = val_nsec_prove_nodata_dsreply( + qstate->env, ve, qinfo, msg->rep, vq->key_entry, + &proof_ttl, &reason); + switch(sec) { + case sec_status_secure: + verbose(VERB_DETAIL, "NSEC RRset for the " + "referral proved no DS."); + *ke = key_entry_create_null(qstate->region, + qinfo->qname, qinfo->qname_len, + qinfo->qclass, proof_ttl, + *qstate->env->now); + return (*ke) != NULL; + case sec_status_insecure: + verbose(VERB_DETAIL, "NSEC RRset for the " + "referral proved not a delegation point"); + *ke = NULL; + return 1; + case sec_status_bogus: + verbose(VERB_DETAIL, "NSEC RRset for the " + "referral did not prove no DS."); + errinf(qstate, reason); + goto return_bogus; + case sec_status_unchecked: + default: + /* NSEC proof did not work, try next */ + break; + } + + sec = nsec3_prove_nods(qstate->env, ve, + msg->rep->rrsets + msg->rep->an_numrrsets, + msg->rep->ns_numrrsets, qinfo, vq->key_entry, &reason); + switch(sec) { + case sec_status_insecure: + /* case insecure also continues to unsigned + * space. If nsec3-iter-count too high or + * optout, then treat below as unsigned */ + case sec_status_secure: + verbose(VERB_DETAIL, "NSEC3s for the " + "referral proved no DS."); + *ke = key_entry_create_null(qstate->region, + qinfo->qname, qinfo->qname_len, + qinfo->qclass, proof_ttl, + *qstate->env->now); + return (*ke) != NULL; + case sec_status_indeterminate: + verbose(VERB_DETAIL, "NSEC3s for the " + "referral proved no delegation"); + *ke = NULL; + return 1; + case sec_status_bogus: + verbose(VERB_DETAIL, "NSEC3s for the " + "referral did not prove no DS."); + errinf(qstate, reason); + goto return_bogus; + case sec_status_unchecked: + default: + /* NSEC3 proof did not work */ + break; + } + + /* Apparently, no available NSEC/NSEC3 proved NODATA, so + * this is BOGUS. */ + verbose(VERB_DETAIL, "DS %s ran out of options, so return " + "bogus", val_classification_to_string(subtype)); + errinf(qstate, "no DS but also no proof of that"); + goto return_bogus; + } else if(subtype == VAL_CLASS_CNAME || + subtype == VAL_CLASS_CNAMENOANSWER) { + /* if the CNAME matches the exact name we want and is signed + * properly, then also, we are sure that no DS exists there, + * much like a NODATA proof */ + enum sec_status sec; + struct ub_packed_rrset_key* cname; + cname = reply_find_rrset_section_an(msg->rep, qinfo->qname, + qinfo->qname_len, LDNS_RR_TYPE_CNAME, qinfo->qclass); + if(!cname) { + errinf(qstate, "validator classified CNAME but no " + "CNAME of the queried name for DS"); + goto return_bogus; + } + if(((struct packed_rrset_data*)cname->entry.data)->rrsig_count + == 0) { + if(msg->rep->an_numrrsets != 0 && ntohs(msg->rep-> + rrsets[0]->rk.type)==LDNS_RR_TYPE_DNAME) { + errinf(qstate, "DS got DNAME answer"); + } else { + errinf(qstate, "DS got unsigned CNAME answer"); + } + goto return_bogus; + } + sec = val_verify_rrset_entry(qstate->env, ve, cname, + vq->key_entry, &reason); + if(sec == sec_status_secure) { + verbose(VERB_ALGO, "CNAME validated, " + "proof that DS does not exist"); + /* and that it is not a referral point */ + *ke = NULL; + return 1; + } + errinf(qstate, "CNAME in DS response was not secure."); + errinf(qstate, reason); + goto return_bogus; + } else { + verbose(VERB_QUERY, "Encountered an unhandled type of " + "DS response, thus bogus."); + errinf(qstate, "no DS and"); + if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR) { + char* rc = ldns_pkt_rcode2str( + FLAGS_GET_RCODE(msg->rep->flags)); + errinf(qstate, rc); + free(rc); + } else errinf(qstate, val_classification_to_string(subtype)); + errinf(qstate, "message fails to prove that"); + goto return_bogus; + } +return_bogus: + *ke = key_entry_create_bad(qstate->region, qinfo->qname, + qinfo->qname_len, qinfo->qclass, + BOGUS_KEY_TTL, *qstate->env->now); + return (*ke) != NULL; +} + +/** + * Process DS response. Called from inform_supers. + * Because it is in inform_supers, the mesh itself is busy doing callbacks + * for a state that is to be deleted soon; don't touch the mesh; instead + * set a state in the super, as the super will be reactivated soon. + * Perform processing to determine what state to set in the super. + * + * @param qstate: query state that is validating and asked for a DS. + * @param vq: validator query state + * @param id: module id. + * @param rcode: rcode result value. + * @param msg: result message (if rcode is OK). + * @param qinfo: from the sub query state, query info. + * @param origin: the origin of msg. + */ +static void +process_ds_response(struct module_qstate* qstate, struct val_qstate* vq, + int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, + struct sock_list* origin) +{ + struct key_entry_key* dske = NULL; + uint8_t* olds = vq->empty_DS_name; + vq->empty_DS_name = NULL; + if(!ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske)) { + log_err("malloc failure in process_ds_response"); + vq->key_entry = NULL; /* make it error */ + vq->state = VAL_VALIDATE_STATE; + return; + } + if(dske == NULL) { + vq->empty_DS_name = regional_alloc_init(qstate->region, + qinfo->qname, qinfo->qname_len); + if(!vq->empty_DS_name) { + log_err("malloc failure in empty_DS_name"); + vq->key_entry = NULL; /* make it error */ + vq->state = VAL_VALIDATE_STATE; + return; + } + vq->empty_DS_len = qinfo->qname_len; + vq->chain_blacklist = NULL; + /* ds response indicated that we aren't on a delegation point. + * Keep the forState.state on FINDKEY. */ + } else if(key_entry_isgood(dske)) { + vq->ds_rrset = key_entry_get_rrset(dske, qstate->region); + if(!vq->ds_rrset) { + log_err("malloc failure in process DS"); + vq->key_entry = NULL; /* make it error */ + vq->state = VAL_VALIDATE_STATE; + return; + } + vq->chain_blacklist = NULL; /* fresh blacklist for next part*/ + /* Keep the forState.state on FINDKEY. */ + } else if(key_entry_isbad(dske) + && vq->restart_count < VAL_MAX_RESTART_COUNT) { + vq->empty_DS_name = olds; + val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); + qstate->errinf = NULL; + vq->restart_count++; + } else { + if(key_entry_isbad(dske)) { + errinf_origin(qstate, origin); + errinf_dname(qstate, "for DS", qinfo->qname); + } + /* NOTE: the reason for the DS to be not good (that is, + * either bad or null) should have been logged by + * dsResponseToKE. */ + vq->key_entry = dske; + /* The FINDKEY phase has ended, so move on. */ + vq->state = VAL_VALIDATE_STATE; + } +} + +/** + * Process DNSKEY response. Called from inform_supers. + * Sets the key entry in the state. + * Because it is in inform_supers, the mesh itself is busy doing callbacks + * for a state that is to be deleted soon; don't touch the mesh; instead + * set a state in the super, as the super will be reactivated soon. + * Perform processing to determine what state to set in the super. + * + * @param qstate: query state that is validating and asked for a DNSKEY. + * @param vq: validator query state + * @param id: module id. + * @param rcode: rcode result value. + * @param msg: result message (if rcode is OK). + * @param qinfo: from the sub query state, query info. + * @param origin: the origin of msg. + */ +static void +process_dnskey_response(struct module_qstate* qstate, struct val_qstate* vq, + int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, + struct sock_list* origin) +{ + struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; + struct key_entry_key* old = vq->key_entry; + struct ub_packed_rrset_key* dnskey = NULL; + int downprot; + char* reason = NULL; + + if(rcode == LDNS_RCODE_NOERROR) + dnskey = reply_find_answer_rrset(qinfo, msg->rep); + + if(dnskey == NULL) { + /* bad response */ + verbose(VERB_DETAIL, "Missing DNSKEY RRset in response to " + "DNSKEY query."); + if(vq->restart_count < VAL_MAX_RESTART_COUNT) { + val_blacklist(&vq->chain_blacklist, qstate->region, + origin, 1); + qstate->errinf = NULL; + vq->restart_count++; + return; + } + vq->key_entry = key_entry_create_bad(qstate->region, + qinfo->qname, qinfo->qname_len, qinfo->qclass, + BOGUS_KEY_TTL, *qstate->env->now); + if(!vq->key_entry) { + log_err("alloc failure in missing dnskey response"); + /* key_entry is NULL for failure in Validate */ + } + errinf(qstate, "No DNSKEY record"); + errinf_origin(qstate, origin); + errinf_dname(qstate, "for key", qinfo->qname); + vq->state = VAL_VALIDATE_STATE; + return; + } + if(!vq->ds_rrset) { + log_err("internal error: no DS rrset for new DNSKEY response"); + vq->key_entry = NULL; + vq->state = VAL_VALIDATE_STATE; + return; + } + downprot = 1; + vq->key_entry = val_verify_new_DNSKEYs(qstate->region, qstate->env, + ve, dnskey, vq->ds_rrset, downprot, &reason); + + if(!vq->key_entry) { + log_err("out of memory in verify new DNSKEYs"); + vq->state = VAL_VALIDATE_STATE; + return; + } + /* If the key entry isBad or isNull, then we can move on to the next + * state. */ + if(!key_entry_isgood(vq->key_entry)) { + if(key_entry_isbad(vq->key_entry)) { + if(vq->restart_count < VAL_MAX_RESTART_COUNT) { + val_blacklist(&vq->chain_blacklist, + qstate->region, origin, 1); + qstate->errinf = NULL; + vq->restart_count++; + vq->key_entry = old; + return; + } + verbose(VERB_DETAIL, "Did not match a DS to a DNSKEY, " + "thus bogus."); + errinf(qstate, reason); + errinf_origin(qstate, origin); + errinf_dname(qstate, "for key", qinfo->qname); + } + vq->chain_blacklist = NULL; + vq->state = VAL_VALIDATE_STATE; + return; + } + vq->chain_blacklist = NULL; + qstate->errinf = NULL; + + /* The DNSKEY validated, so cache it as a trusted key rrset. */ + key_cache_insert(ve->kcache, vq->key_entry, qstate); + + /* If good, we stay in the FINDKEY state. */ + log_query_info(VERB_DETAIL, "validated DNSKEY", qinfo); +} + +/** + * Process prime response + * Sets the key entry in the state. + * + * @param qstate: query state that is validating and primed a trust anchor. + * @param vq: validator query state + * @param id: module id. + * @param rcode: rcode result value. + * @param msg: result message (if rcode is OK). + * @param origin: the origin of msg. + */ +static void +process_prime_response(struct module_qstate* qstate, struct val_qstate* vq, + int id, int rcode, struct dns_msg* msg, struct sock_list* origin) +{ + struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; + struct ub_packed_rrset_key* dnskey_rrset = NULL; + struct trust_anchor* ta = anchor_find(qstate->env->anchors, + vq->trust_anchor_name, vq->trust_anchor_labs, + vq->trust_anchor_len, vq->qchase.qclass); + if(!ta) { + /* trust anchor revoked, restart with less anchors */ + vq->state = VAL_INIT_STATE; + if(!vq->trust_anchor_name) + vq->state = VAL_VALIDATE_STATE; /* break a loop */ + vq->trust_anchor_name = NULL; + return; + } + /* Fetch and validate the keyEntry that corresponds to the + * current trust anchor. */ + if(rcode == LDNS_RCODE_NOERROR) { + dnskey_rrset = reply_find_rrset_section_an(msg->rep, + ta->name, ta->namelen, LDNS_RR_TYPE_DNSKEY, + ta->dclass); + } + if(ta->autr) { + if(!autr_process_prime(qstate->env, ve, ta, dnskey_rrset)) { + /* trust anchor revoked, restart with less anchors */ + vq->state = VAL_INIT_STATE; + vq->trust_anchor_name = NULL; + return; + } + } + vq->key_entry = primeResponseToKE(dnskey_rrset, ta, qstate, id); + lock_basic_unlock(&ta->lock); + if(vq->key_entry) { + if(key_entry_isbad(vq->key_entry) + && vq->restart_count < VAL_MAX_RESTART_COUNT) { + val_blacklist(&vq->chain_blacklist, qstate->region, + origin, 1); + qstate->errinf = NULL; + vq->restart_count++; + vq->key_entry = NULL; + vq->state = VAL_INIT_STATE; + return; + } + vq->chain_blacklist = NULL; + errinf_origin(qstate, origin); + errinf_dname(qstate, "for trust anchor", ta->name); + /* store the freshly primed entry in the cache */ + key_cache_insert(ve->kcache, vq->key_entry, qstate); + } + + /* If the result of the prime is a null key, skip the FINDKEY state.*/ + if(!vq->key_entry || key_entry_isnull(vq->key_entry) || + key_entry_isbad(vq->key_entry)) { + vq->state = VAL_VALIDATE_STATE; + } + /* the qstate will be reactivated after inform_super is done */ +} + +/** + * Process DLV response. Called from inform_supers. + * Because it is in inform_supers, the mesh itself is busy doing callbacks + * for a state that is to be deleted soon; don't touch the mesh; instead + * set a state in the super, as the super will be reactivated soon. + * Perform processing to determine what state to set in the super. + * + * @param qstate: query state that is validating and asked for a DLV. + * @param vq: validator query state + * @param id: module id. + * @param rcode: rcode result value. + * @param msg: result message (if rcode is OK). + * @param qinfo: from the sub query state, query info. + */ +static void +process_dlv_response(struct module_qstate* qstate, struct val_qstate* vq, + int id, int rcode, struct dns_msg* msg, struct query_info* qinfo) +{ + struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; + + verbose(VERB_ALGO, "process dlv response to super"); + if(rcode != LDNS_RCODE_NOERROR) { + /* lookup failed, set in vq to give up */ + vq->dlv_status = dlv_error; + verbose(VERB_ALGO, "response is error"); + return; + } + if(msg->rep->security != sec_status_secure) { + vq->dlv_status = dlv_error; + verbose(VERB_ALGO, "response is not secure, %s", + sec_status_to_string(msg->rep->security)); + return; + } + /* was the lookup a success? validated DLV? */ + if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NOERROR && + msg->rep->an_numrrsets == 1 && + msg->rep->security == sec_status_secure && + ntohs(msg->rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_DLV && + ntohs(msg->rep->rrsets[0]->rk.rrset_class) == qinfo->qclass && + query_dname_compare(msg->rep->rrsets[0]->rk.dname, + vq->dlv_lookup_name) == 0) { + /* yay! it is just like a DS */ + vq->ds_rrset = (struct ub_packed_rrset_key*) + regional_alloc_init(qstate->region, + msg->rep->rrsets[0], sizeof(*vq->ds_rrset)); + if(!vq->ds_rrset) { + log_err("out of memory in process_dlv"); + return; + } + vq->ds_rrset->entry.key = vq->ds_rrset; + vq->ds_rrset->rk.dname = (uint8_t*)regional_alloc_init( + qstate->region, vq->ds_rrset->rk.dname, + vq->ds_rrset->rk.dname_len); + if(!vq->ds_rrset->rk.dname) { + log_err("out of memory in process_dlv"); + vq->dlv_status = dlv_error; + return; + } + vq->ds_rrset->entry.data = regional_alloc_init(qstate->region, + vq->ds_rrset->entry.data, + packed_rrset_sizeof(vq->ds_rrset->entry.data)); + if(!vq->ds_rrset->entry.data) { + log_err("out of memory in process_dlv"); + vq->dlv_status = dlv_error; + return; + } + packed_rrset_ptr_fixup(vq->ds_rrset->entry.data); + /* make vq do a DNSKEY query next up */ + vq->dlv_status = dlv_success; + return; + } + /* store NSECs into negative cache */ + val_neg_addreply(ve->neg_cache, msg->rep); + + /* was the lookup a failure? + * if we have to go up into the DLV for a higher DLV anchor + * then set this in the vq, so it can make queries when activated. + * See if the NSECs indicate that we should look for higher DLV + * or, that there is no DLV securely */ + if(!val_nsec_check_dlv(qinfo, msg->rep, &vq->dlv_lookup_name, + &vq->dlv_lookup_name_len)) { + vq->dlv_status = dlv_error; + verbose(VERB_ALGO, "nsec error"); + return; + } + if(!dname_subdomain_c(vq->dlv_lookup_name, + qstate->env->anchors->dlv_anchor->name)) { + vq->dlv_status = dlv_there_is_no_dlv; + return; + } + vq->dlv_status = dlv_ask_higher; +} + +/* + * inform validator super. + * + * @param qstate: query state that finished. + * @param id: module id. + * @param super: the qstate to inform. + */ +void +val_inform_super(struct module_qstate* qstate, int id, + struct module_qstate* super) +{ + struct val_qstate* vq = (struct val_qstate*)super->minfo[id]; + log_query_info(VERB_ALGO, "validator: inform_super, sub is", + &qstate->qinfo); + log_query_info(VERB_ALGO, "super is", &super->qinfo); + if(!vq) { + verbose(VERB_ALGO, "super: has no validator state"); + return; + } + if(vq->wait_prime_ta) { + vq->wait_prime_ta = 0; + process_prime_response(super, vq, id, qstate->return_rcode, + qstate->return_msg, qstate->reply_origin); + return; + } + if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) { + process_ds_response(super, vq, id, qstate->return_rcode, + qstate->return_msg, &qstate->qinfo, + qstate->reply_origin); + return; + } else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY) { + process_dnskey_response(super, vq, id, qstate->return_rcode, + qstate->return_msg, &qstate->qinfo, + qstate->reply_origin); + return; + } else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DLV) { + process_dlv_response(super, vq, id, qstate->return_rcode, + qstate->return_msg, &qstate->qinfo); + return; + } + log_err("internal error in validator: no inform_supers possible"); +} + +void +val_clear(struct module_qstate* qstate, int id) +{ + if(!qstate) + return; + /* everything is allocated in the region, so assign NULL */ + qstate->minfo[id] = NULL; +} + +size_t +val_get_mem(struct module_env* env, int id) +{ + struct val_env* ve = (struct val_env*)env->modinfo[id]; + if(!ve) + return 0; + return sizeof(*ve) + key_cache_get_mem(ve->kcache) + + val_neg_get_mem(ve->neg_cache) + + sizeof(size_t)*2*ve->nsec3_keyiter_count; +} + +/** + * The validator function block + */ +static struct module_func_block val_block = { + "validator", + &val_init, &val_deinit, &val_operate, &val_inform_super, &val_clear, + &val_get_mem +}; + +struct module_func_block* +val_get_funcblock(void) +{ + return &val_block; +} + +const char* +val_state_to_string(enum val_state state) +{ + switch(state) { + case VAL_INIT_STATE: return "VAL_INIT_STATE"; + case VAL_FINDKEY_STATE: return "VAL_FINDKEY_STATE"; + case VAL_VALIDATE_STATE: return "VAL_VALIDATE_STATE"; + case VAL_FINISHED_STATE: return "VAL_FINISHED_STATE"; + case VAL_DLVLOOKUP_STATE: return "VAL_DLVLOOKUP_STATE"; + } + return "UNKNOWN VALIDATOR STATE"; +} + diff --git a/validator/validator.h b/validator/validator.h new file mode 100644 index 0000000000000..18e905efcd2b6 --- /dev/null +++ b/validator/validator.h @@ -0,0 +1,294 @@ +/* + * validator/validator.h - secure validator DNS query response module + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains a module that performs validation of DNS queries. + * According to RFC 4034. + */ + +#ifndef VALIDATOR_VALIDATOR_H +#define VALIDATOR_VALIDATOR_H +#include "util/module.h" +#include "util/data/msgreply.h" +#include "validator/val_utils.h" +struct val_anchors; +struct key_cache; +struct key_entry_key; +struct val_neg_cache; +struct config_strlist; + +/** + * This is the TTL to use when a trust anchor fails to prime. A trust anchor + * will be primed no more often than this interval. Used when harden- + * dnssec-stripped is off and the trust anchor fails. + */ +#define NULL_KEY_TTL 900 /* seconds */ + +/** + * TTL for bogus key entries. When a DS or DNSKEY fails in the chain of + * trust the entire zone for that name is blacked out for this TTL. + */ +#define BOGUS_KEY_TTL 900 /* seconds */ + +/** max number of query restarts, number of IPs to probe */ +#define VAL_MAX_RESTART_COUNT 5 + +/** + * Global state for the validator. + */ +struct val_env { + /** key cache; these are validated keys. trusted keys only + * end up here after being primed. */ + struct key_cache* kcache; + + /** aggressive negative cache. index into NSECs in rrset cache. */ + struct val_neg_cache* neg_cache; + + /** for debug testing a fixed validation date can be entered. + * if 0, current time is used for rrsig validation */ + int32_t date_override; + + /** clock skew min for signatures */ + int32_t skew_min; + + /** clock skew max for signatures */ + int32_t skew_max; + + /** TTL for bogus data; used instead of untrusted TTL from data. + * Bogus data will not be verified more often than this interval. + * seconds. */ + uint32_t bogus_ttl; + + /** If set, the validator should clean the additional section of + * secure messages. + */ + int clean_additional; + + /** + * If set, the validator will not make messages bogus, instead + * indeterminate is issued, so that no clients receive SERVFAIL. + * This allows an operator to run validation 'shadow' without + * hurting responses to clients. + */ + int permissive_mode; + + /** + * Number of entries in the NSEC3 maximum iteration count table. + * Keep this table short, and sorted by size + */ + int nsec3_keyiter_count; + + /** + * NSEC3 maximum iteration count per signing key size. + * This array contains key size values (in increasing order) + */ + size_t* nsec3_keysize; + + /** + * NSEC3 maximum iteration count per signing key size. + * This array contains the maximum iteration count for the keysize + * in the keysize array. + */ + size_t* nsec3_maxiter; + + /** lock on bogus counter */ + lock_basic_t bogus_lock; + /** number of times rrsets marked bogus */ + size_t num_rrset_bogus; +}; + +/** + * State of the validator for a query. + */ +enum val_state { + /** initial state for validation */ + VAL_INIT_STATE = 0, + /** find the proper keys for validation, follow trust chain */ + VAL_FINDKEY_STATE, + /** validate the answer, using found key entry */ + VAL_VALIDATE_STATE, + /** finish up */ + VAL_FINISHED_STATE, + /** DLV lookup state, processing DLV queries */ + VAL_DLVLOOKUP_STATE +}; + +/** + * Per query state for the validator module. + */ +struct val_qstate { + /** + * State of the validator module. + */ + enum val_state state; + + /** + * The original message we have been given to validate. + */ + struct dns_msg* orig_msg; + + /** + * The query restart count + */ + int restart_count; + /** The blacklist saved for chainoftrust elements */ + struct sock_list* chain_blacklist; + + /** + * The query name we have chased to; qname after following CNAMEs + */ + struct query_info qchase; + + /** + * The chased reply, extract from original message. Can be: + * o CNAME + * o DNAME + CNAME + * o answer + * plus authority, additional (nsecs) that have same signature. + */ + struct reply_info* chase_reply; + + /** + * The cname skip value; the number of rrsets that have been skipped + * due to chasing cnames. This is the offset into the + * orig_msg->rep->rrsets array, into the answer section. + * starts at 0 - for the full original message. + * if it is >0 - qchase followed the cname, chase_reply setup to be + * that message and relevant authority rrsets. + * + * The skip is also used for referral messages, where it will + * range from 0, over the answer, authority and additional sections. + */ + size_t rrset_skip; + + /** trust anchor name */ + uint8_t* trust_anchor_name; + /** trust anchor labels */ + int trust_anchor_labs; + /** trust anchor length */ + size_t trust_anchor_len; + + /** the DS rrset */ + struct ub_packed_rrset_key* ds_rrset; + + /** domain name for empty nonterminal detection */ + uint8_t* empty_DS_name; + /** length of empty_DS_name */ + size_t empty_DS_len; + + /** the current key entry */ + struct key_entry_key* key_entry; + + /** subtype */ + enum val_classification subtype; + + /** signer name */ + uint8_t* signer_name; + /** length of signer_name */ + size_t signer_len; + + /** true if this state is waiting to prime a trust anchor */ + int wait_prime_ta; + + /** have we already checked the DLV? */ + int dlv_checked; + /** The name for which the DLV is looked up. For the current message + * or for the current RRset (for CNAME, REFERRAL types). + * If there is signer name, that may be it, else a domain name */ + uint8_t* dlv_lookup_name; + /** length of dlv lookup name */ + size_t dlv_lookup_name_len; + /** Name at which chain of trust stopped with insecure, starting DLV + * DLV must result in chain going further down */ + uint8_t* dlv_insecure_at; + /** length of dlv insecure point name */ + size_t dlv_insecure_at_len; + /** status of DLV lookup. Indication to VAL_DLV_STATE what to do */ + enum dlv_status { + dlv_error, /* server failure */ + dlv_success, /* got a DLV */ + dlv_ask_higher, /* ask again */ + dlv_there_is_no_dlv /* got no DLV, sure of it */ + } dlv_status; +}; + +/** + * Get the validator function block. + * @return: function block with function pointers to validator methods. + */ +struct module_func_block* val_get_funcblock(void); + +/** + * Get validator state as a string + * @param state: to convert + * @return constant string that is printable. + */ +const char* val_state_to_string(enum val_state state); + +/** validator init */ +int val_init(struct module_env* env, int id); + +/** validator deinit */ +void val_deinit(struct module_env* env, int id); + +/** validator operate on a query */ +void val_operate(struct module_qstate* qstate, enum module_ev event, int id, + struct outbound_entry* outbound); + +/** + * inform validator super. + * + * @param qstate: query state that finished. + * @param id: module id. + * @param super: the qstate to inform. + */ +void val_inform_super(struct module_qstate* qstate, int id, + struct module_qstate* super); + +/** validator cleanup query state */ +void val_clear(struct module_qstate* qstate, int id); + +/** + * Debug helper routine that assists worker in determining memory in + * use. + * @param env: module environment + * @param id: module id. + * @return memory in use in bytes. + */ +size_t val_get_mem(struct module_env* env, int id); + +#endif /* VALIDATOR_VALIDATOR_H */ |