From: Wouter Wijngaards Date: Fri, 3 Aug 2007 11:51:20 +0000 (+0000) Subject: trust anchor storage and config. X-Git-Tag: release-0.5~155 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d48e17e1ddf59b44805c07d8b41ea58b4a1f8e41;p=thirdparty%2Funbound.git trust anchor storage and config. git-svn-id: file:///svn/unbound/trunk@486 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index b9554f4b7..d6f278758 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +3 August 2007: Wouter + - replanning. + - scrubber check section of lame NS set. + - trust anchors can be in config file or read from zone file, + DS and DNSKEY entries. + 2 August 2007: Wouter - configure change for latest libevent trunk version (needs -lrt). - query_done and walk_supers are moved out of module interface. diff --git a/doc/example.conf b/doc/example.conf index 4d1b04afe..30db19437 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -152,9 +152,16 @@ server: # separated by spaces. "iterator" or "validator iterator" # module-config: "validator iterator" - # File with trusted keys for validation. + # File with trusted keys for validation. Specify more than one file + # with several entries, one file per entry. # Zone file format, with DS and DNSKEY entries. # trust-anchor-file: "" + + # Trusted key for validation. DS or DNSKEY. specify the RR on a + # single line, surrounded by "". TTL is ignored. class is IN default. + # (These examples are from August 2007 and may not be valid anymore). + # trust-anchor: "nlnetlabs.nl. DNSKEY 257 3 5 AQPzzTWMz8qSWIQlfRnPckx2BiVmkVN6LPupO3mbz7FhLSnm26n6iG9N Lby97Ji453aWZY3M5/xJBSOS2vWtco2t8C0+xeO1bc/d6ZTy32DHchpW 6rDH1vp86Ll+ha0tmwyy9QP7y2bVw5zSbFCrefk8qCUBgfHm9bHzMG1U BYtEIQ==" + # trust-anchor: "jelte.nlnetlabs.nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A" # Stub zones. # Create entries like below, to make all queries for 'example.com' and diff --git a/doc/unbound.conf.5 b/doc/unbound.conf.5 index b2166ecce..2c3f5429a 100644 --- a/doc/unbound.conf.5 +++ b/doc/unbound.conf.5 @@ -192,6 +192,13 @@ Setting this to "validator iterator" will turn on validation. File with trusted keys for validation. Both DS and DNSKEY entries can appear in the file. The format of the file is the standard DNS Zone file format. Default is "", or no trust anchor file. +.It \fBtrust-anchor:\fR <"Resource Record"> +A DS or DNSKEY RR for a key to use for validation. Multiple entries can be +given to specify multiple trusted keys, in addition to the trust-anchor-files. +The resource record is entered in the same format as 'dig' or 'drill' prints +them, the same format as in the zone file. Has to be on a single line, with +"" around it. A TTL can be specified for ease of cut and paste, but is ignored. +A class can be specified, but class IN is default. .El .Ss Stub Zone Options diff --git a/util/config_file.c b/util/config_file.c index 94175f074..0647fcffb 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -111,7 +111,8 @@ config_create() cfg->hide_version = 0; cfg->identity = NULL; cfg->version = NULL; - cfg->trust_anchor_file = NULL; + cfg->trust_anchor_file_list = NULL; + cfg->trust_anchor_list = NULL; if(!(cfg->module_conf = strdup("iterator"))) goto error_exit; return cfg; error_exit: @@ -206,7 +207,8 @@ config_delete(struct config_file* cfg) free(cfg->identity); free(cfg->version); free(cfg->module_conf); - free(cfg->trust_anchor_file); + config_delstrlist(cfg->trust_anchor_file_list); + config_delstrlist(cfg->trust_anchor_list); free(cfg); } diff --git a/util/config_file.h b/util/config_file.h index 71d5ead0e..d91082e52 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -139,8 +139,10 @@ struct config_file { /** the module configuration string */ char* module_conf; - /** file with trusted DS and DNSKEYs in zonefile format */ - char* trust_anchor_file; + /** files with trusted DS and DNSKEYs in zonefile format, list */ + struct config_strlist* trust_anchor_file_list; + /** list of trustanchor keys, linked list */ + struct config_strlist* trust_anchor_list; /** daemonize, i.e. fork into the background. */ int do_daemonize; diff --git a/util/configlexer.lex b/util/configlexer.lex index d902f988a..5f128ec4c 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -143,6 +143,7 @@ identity{COLON} { YDOUT; return VAR_IDENTITY;} version{COLON} { YDOUT; return VAR_VERSION;} module-conf{COLON} { YDOUT; return VAR_MODULE_CONF;} trust-anchor-file{COLON} { YDOUT; return VAR_TRUST_ANCHOR_FILE;} +trust-anchor{COLON} { YDOUT; return VAR_TRUST_ANCHOR;} {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;} /* Quoted strings. Strip leading and ending quotes */ diff --git a/util/configparser.y b/util/configparser.y index b086f8cbe..fa2a57bfd 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -80,7 +80,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_FORWARD_ZONE VAR_FORWARD_HOST VAR_FORWARD_ADDR %token VAR_DO_NOT_QUERY_ADDRESS VAR_HIDE_IDENTITY VAR_HIDE_VERSION %token VAR_IDENTITY VAR_VERSION VAR_HARDEN_GLUE VAR_MODULE_CONF -%token VAR_TRUST_ANCHOR_FILE +%token VAR_TRUST_ANCHOR_FILE VAR_TRUST_ANCHOR %% toplevelvars: /* empty */ | toplevelvars toplevelvar ; @@ -113,7 +113,8 @@ content_server: server_num_threads | server_verbosity | server_port | server_harden_short_bufsize | server_harden_large_queries | server_do_not_query_address | server_hide_identity | server_hide_version | server_identity | server_version | - server_harden_glue | server_module_conf | server_trust_anchor_file + server_harden_glue | server_module_conf | server_trust_anchor_file | + server_trust_anchor ; stubstart: VAR_STUB_ZONE { @@ -288,8 +289,16 @@ server_pidfile: VAR_PIDFILE STRING server_trust_anchor_file: VAR_TRUST_ANCHOR_FILE STRING { OUTYY(("P(server_trust_anchor_file:%s)\n", $2)); - free(cfg_parser->cfg->trust_anchor_file); - cfg_parser->cfg->trust_anchor_file = $2; + if(!cfg_strlist_insert(&cfg_parser->cfg-> + trust_anchor_file_list, $2)) + yyerror("out of memory"); + } + ; +server_trust_anchor: VAR_TRUST_ANCHOR STRING + { + OUTYY(("P(server_trust_anchor:%s)\n", $2)); + if(!cfg_strlist_insert(&cfg_parser->cfg->trust_anchor_list, $2)) + yyerror("out of memory"); } ; server_hide_identity: VAR_HIDE_IDENTITY STRING diff --git a/validator/val_anchor.c b/validator/val_anchor.c index af0fef763..770765e5e 100644 --- a/validator/val_anchor.c +++ b/validator/val_anchor.c @@ -43,6 +43,7 @@ #include "util/data/packed_rrset.h" #include "util/data/dname.h" #include "util/log.h" +#include "util/net_help.h" #include "util/region-allocator.h" #include "util/config_file.h" @@ -121,13 +122,274 @@ init_parents(struct val_anchors* anchors) } } +/** + * 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. + */ +static 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; + key.node.key = &key; + key.name = name; + key.namelabs = namelabs; + key.namelen = namelen; + key.dclass = dclass; + n = rbtree_search(anchors->tree, &key); + 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) +{ + struct trust_anchor* ta = (struct trust_anchor*)region_alloc( + anchors->region, sizeof(struct trust_anchor)); + if(!ta) + return NULL; + memset(ta, 0, sizeof(*ta)); + ta->node.key = ta; + ta->name = name; + ta->namelabs = namelabs; + ta->namelen = namelen; + ta->dclass = dclass; + 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(struct val_anchors* anchors, uint8_t* rdata, size_t rdata_len, + uint16_t type) +{ + struct ta_key* k = (struct ta_key*)region_alloc(anchors->region, + sizeof(*k)); + if(!k) + return NULL; + memset(k, 0, sizeof(*k)); + k->data = region_alloc_init(anchors->region, rdata, rdata_len); + if(!k->data) + 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. + * @param rdata_len: length of rdata including rdlength. + * @return: 0 on error. + */ +static int +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); + if(!ta) + return 0; + } + /* look for duplicates */ + if(anchor_find_key(ta, rdata, rdata_len, type)) { + return 1; + } + k = anchor_new_ta_key(anchors, rdata, rdata_len, type); + if(!k) + return 0; + /* add new key */ + if(type == LDNS_RR_TYPE_DS) + ta->numDS++; + else ta->numDNSKEY++; + k->next = ta->keylist; + ta->keylist = k; + return 1; +} + +/** + * 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 false on error. + */ +static int +anchor_store_new_rr(struct val_anchors* anchors, ldns_buffer* buffer, + ldns_rr* rr) +{ + 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 0; + } + ldns_buffer_flip(buffer); + ldns_buffer_write_u16_at(buffer, 0, ldns_buffer_limit(buffer) - 2); + + if(!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 0; + } + log_nametypeclass(VERB_DETAIL, "adding trusted key", + ldns_rdf_data(owner), + ldns_rr_get_type(rr), ldns_rr_get_class(rr)); + return 1; +} + +/** + * Store one string as trust anchor RR. + * @param anchors: anchor storage. + * @param buffer: parsing buffer. + * @param str: string. + * @return false on error. + */ +static int +anchor_store_str(struct val_anchors* anchors, ldns_buffer* buffer, + const char* str) +{ + 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 0; + } + if(!anchor_store_new_rr(anchors, buffer, rr)) { + log_err("out of memory"); + ldns_rr_free(rr); + return 0; + } + ldns_rr_free(rr); + return 1; +} + +/** + * Read a file with trust anchors + * @param anchors: anchor storage. + * @param buffer: parsing buffer. + * @param fname: string. + * @return false on error. + */ +static int +anchor_read_file(struct val_anchors* anchors, ldns_buffer* buffer, + const char* fname) +{ + 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(!anchor_store_new_rr(anchors, buffer, rr)) { + log_err("error at %s line %d", fname, line_nr); + ldns_rr_free(rr); + ok = 0; + break; + } + ldns_rr_free(rr); + } + ldns_rdf_deep_free(origin); + ldns_rdf_deep_free(prev); + fclose(in); + return ok; +} + int anchors_apply_cfg(struct val_anchors* anchors, struct config_file* cfg) { - if(cfg->trust_anchor_file && cfg->trust_anchor_file[0]) { - /* read trust anchor file */ + struct config_strlist* f; + ldns_buffer* parsebuf = ldns_buffer_new(65535); + for(f = cfg->trust_anchor_file_list; f; f = f->next) { + if(!f->str || f->str[0] == 0) /* empty "" */ + continue; + if(!anchor_read_file(anchors, parsebuf, f->str)) { + log_err("error reading trust-anchor-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; + } } init_parents(anchors); + ldns_buffer_free(parsebuf); return 1; } @@ -135,5 +397,33 @@ struct trust_anchor* anchors_lookup(struct val_anchors* anchors, uint8_t* qname, size_t qname_len, uint16_t qclass) { - return NULL; + 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; + if(rbtree_find_less_equal(anchors->tree, &key, &res)) { + /* exact */ + result = (struct trust_anchor*)res->key; + } else { + /* smaller element (or no element) */ + int m; + result = (struct trust_anchor*)res->key; + if(!result || result->dclass != qclass) + 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) + return NULL; + } + return result; } diff --git a/validator/val_anchor.h b/validator/val_anchor.h index f0de67eaa..c2cd40c49 100644 --- a/validator/val_anchor.h +++ b/validator/val_anchor.h @@ -67,9 +67,9 @@ struct val_anchors { struct ta_key { /** next in list */ struct ta_key* next; - /** rdata, in wireformat of the key RR. */ + /** rdata, in wireformat of the key RR. starts with rdlength. */ uint8_t* data; - /** length of the rdata */ + /** length of the rdata (including rdlength). */ size_t len; /** DNS type (host format) of the key, DS or DNSKEY */ uint16_t type; @@ -84,6 +84,8 @@ struct trust_anchor { rbnode_t node; /** 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 */