diff --git a/src/anchor.c b/src/anchor.c index a2903b96..86338d4f 100644 --- a/src/anchor.c +++ b/src/anchor.c @@ -45,9 +45,10 @@ #include "gldns/parseutil.h" #include "gldns/gbuffer.h" #include "gldns/str2wire.h" +#include "gldns/wire2str.h" #include "gldns/pkthdr.h" +#include "gldns/keyraw.h" #include "general.h" -#include "rr-iter.h" #include "util-internal.h" /* get key usage out of its extension, returns 0 if no key_usage extension */ @@ -741,7 +742,6 @@ void _getdns_context_equip_with_anchor( else if (!verify_CA || !*verify_CA) DEBUG_ANCHOR("NOTICE: Trust anchor verification explicitely " "disabled by empty verify CA\n"); - return; else if ((r = getdns_context_get_trust_anchor_verify_email( context, ".", &verify_email))) @@ -1612,4 +1612,305 @@ void _getdns_start_fetching_ta(getdns_context *context, getdns_eventloop *loop) context->trust_anchors_source = GETDNS_TASRC_FETCHING; } + +static int _uint16_cmp(const void *a, const void *b) +{ return (int)*(uint16_t *)a - (int)*(uint16_t *)b; } + +static int _uint8x16_cmp(const void *a, const void *b) +{ return memcmp(a, b, RRSIG_RDATA_LEN); } + +static void +_getdns_init_ksks(_getdns_ksks *ksks, _getdns_rrset *dnskey_set) +{ + _getdns_rrtype_iter *rr, rr_space; + _getdns_rrsig_iter *rrsig, rrsig_space; + + assert(ksks); + assert(dnskey_set); + assert(dnskey_set->rr_type == GETDNS_RRTYPE_DNSKEY); + + ksks->n = 0; + for ( rr = _getdns_rrtype_iter_init(&rr_space, dnskey_set) + ; rr && ksks->n < MAX_KSKS + ; rr = _getdns_rrtype_iter_next(rr)) { + + if (rr->rr_i.nxt - rr->rr_i.rr_type < 12 + || !(rr->rr_i.rr_type[11] & 1)) + continue; /* Not a KSK */ + + ksks->ids[ksks->n++] = gldns_calc_keytag_raw( + rr->rr_i.rr_type + 10, + rr->rr_i.nxt - rr->rr_i.rr_type - 10); + } + qsort(ksks->ids, ksks->n, sizeof(uint16_t), _uint16_cmp); + + ksks->n_rrsigs = 0; + for ( rrsig = _getdns_rrsig_iter_init(&rrsig_space, dnskey_set) + ; rrsig && ksks->n_rrsigs < MAX_KSKS + ; rrsig = _getdns_rrsig_iter_next(rrsig)) { + + if (rrsig->rr_i.nxt - rrsig->rr_i.rr_type < 28) + continue; + + (void) memcpy(ksks->rrsigs[ksks->n_rrsigs++], + rrsig->rr_i.rr_type + 12, RRSIG_RDATA_LEN); + } + qsort(ksks->rrsigs, ksks->n_rrsigs, RRSIG_RDATA_LEN, _uint8x16_cmp); +} + + +static int +_getdns_ksks_equal(_getdns_ksks *a, _getdns_ksks *b) +{ + return a == b + || ( a != NULL && b != NULL + && a->n == b->n + && memcmp(a->ids, b->ids, a->n * sizeof(uint16_t)) == 0 + && a->n_rrsigs == b->n_rrsigs + && memcmp(a->rrsigs, b->rrsigs, a->n_rrsigs * RRSIG_RDATA_LEN) == 0); +} + +static void _getdns_context_read_root_ksk(getdns_context *context) +{ + FILE *fp; + struct gldns_file_parse_state pst; + size_t len, dname_len; + uint8_t buf_spc[4096], *buf = buf_spc, *ptr = buf_spc; + size_t buf_sz = sizeof(buf_spc); + _getdns_rrset root_dnskey; + uint8_t *root_dname = (uint8_t *)"\00"; + + + if (!(fp = _getdns_context_get_priv_fp(context, "root.key"))) + return; + + for (;;) { + size_t n_rrs = 0; + + *pst.origin = 0; + pst.origin_len = 1; + *pst.prev_rr = 0; + pst.prev_rr_len = 1; + pst.default_ttl = 0; + pst.lineno = 1; + + (void) memset(buf, 0, 12); + ptr += 12; + + while (!feof(fp)) { + len = buf + buf_sz - ptr; + dname_len = 0; + if (gldns_fp2wire_rr_buf(fp, ptr, &len, &dname_len, &pst)) + break; + if ((ptr += len) > buf + buf_sz) + break; + if (len) + n_rrs += 1; + if (dname_len && dname_len < sizeof(pst.prev_rr)) { + memcpy(pst.prev_rr, ptr, dname_len); + pst.prev_rr_len = dname_len; + } + } + if (ptr <= buf + buf_sz) { + gldns_write_uint16(buf + GLDNS_ANCOUNT_OFF, n_rrs); + break; + } + rewind(fp); + if (buf == buf_spc) + buf_sz = 65536; + else { + GETDNS_FREE(context->mf, buf); + buf_sz *= 2; + } + if (!(buf = GETDNS_XMALLOC(context->mf, uint8_t, buf_sz))) { + DEBUG_ANCHOR("ERROR %s(): Memory error\n", __FUNC__); + break;; + } + ptr = buf; + }; + fclose(fp); + if (!buf) + return; + + root_dnskey.name = root_dname; + root_dnskey.rr_class = GETDNS_RRCLASS_IN; + root_dnskey.rr_type = GETDNS_RRTYPE_DNSKEY; + root_dnskey.pkt = buf; + root_dnskey.pkt_len = ptr - buf; + root_dnskey.sections = SECTION_ANSWER; + + _getdns_init_ksks(&context->root_ksk, &root_dnskey); + + if (buf && buf != buf_spc) + GETDNS_FREE(context->mf, buf); +} + +void +_getdns_context_update_root_ksk( + getdns_context *context, _getdns_rrset *dnskey_set) +{ + _getdns_ksks root_ksk_seen; + _getdns_rrtype_iter *rr, rr_space; + _getdns_rrsig_iter *rrsig, rrsig_space; + char str_spc[4096], *str_buf, *str_pos; + int sz_needed; + int remaining; + size_t str_sz = 0; + getdns_bindata root_key_bd; + + _getdns_init_ksks(&root_ksk_seen, dnskey_set); + if (_getdns_ksks_equal(&context->root_ksk, &root_ksk_seen)) + return; /* root DNSKEY rrset already known */ + + /* Try to read root DNSKEY rrset from root.key */ + _getdns_context_read_root_ksk(context); + if (_getdns_ksks_equal(&context->root_ksk, &root_ksk_seen)) + return; /* root DNSKEY rrset same as the safed one */ + + /* Different root DNSKEY rrset. Perhaps because of failure to read + * from disk. If we cannot write to our appdata directory, bail out + */ + if (context->can_write_appdata == PROP_UNABLE) + return; + + /* We might be able to write or we do not know whether we can write + * to the appdata directory. In the latter we'll try to write to + * find out. The section below converts the wireformat DNSKEY rrset + * to presentationformat. + */ + str_pos = str_buf = str_spc; + remaining = sizeof(str_spc); + for (;;) { + for ( rr = _getdns_rrtype_iter_init(&rr_space, dnskey_set) + ; rr ; rr = _getdns_rrtype_iter_next(rr)) { + + sz_needed = gldns_wire2str_rr_buf((uint8_t *)rr->rr_i.pos, + rr->rr_i.nxt - rr->rr_i.pos, str_pos, + (size_t)(remaining > 0 ? remaining : 0)); + + str_pos += sz_needed; + remaining -= sz_needed; + } + for ( rrsig = _getdns_rrsig_iter_init(&rrsig_space, dnskey_set) + ; rrsig + ; rrsig = _getdns_rrsig_iter_next(rrsig)) { + sz_needed = gldns_wire2str_rr_buf((uint8_t *)rrsig->rr_i.pos, + rrsig->rr_i.nxt - rrsig->rr_i.pos, str_pos, + (size_t)(remaining > 0 ? remaining : 0)); + + str_pos += sz_needed; + remaining -= sz_needed; + } + if (remaining > 0) { + *str_pos = 0; + if (str_buf == str_spc) + str_sz = sizeof(str_spc) - remaining; + break; + } + if (str_buf != str_spc) { + DEBUG_ANCHOR("ERROR %s(): Buffer size determination " + "error\n", __FUNC__); + if (str_buf) + GETDNS_FREE(context->mf, str_buf); + + return; + } + if (!(str_pos = str_buf = GETDNS_XMALLOC( context->mf, char, + (str_sz = sizeof(str_spc) - remaining) + 1))) { + DEBUG_ANCHOR("ERROR %s(): Memory error\n", __FUNC__); + return; + } + remaining = str_sz + 1; + DEBUG_ANCHOR("Retrying with buf size: %d\n", remaining); + }; + + /* Write presentation format DNSKEY rrset to "root.key" file */ + root_key_bd.size = str_sz; + root_key_bd.data = (void *)str_buf; + if (_getdns_context_write_priv_file( + context, "root.key", &root_key_bd)) { + size_t i; + + /* A new "root.key" file was written. When they contain + * key_id's which are not in "root-anchors.xml", then update + * "root-anchors.xml". + */ + + for (i = 0; i < context->root_ksk.n; i++) { + _getdns_rrset_iter tas_iter_spc, *ta; + + for ( ta = _getdns_rrset_iter_init(&tas_iter_spc + , context->trust_anchors + , context->trust_anchors_len + , SECTION_ANSWER) + ; ta ; ta = _getdns_rrset_iter_next(ta)) { + _getdns_rrtype_iter *rr, rr_space; + _getdns_rrset *rrset; + + if (!(rrset = _getdns_rrset_iter_value(ta))) + continue; + + if (*rrset->name != '\0') + continue; /* Not a root anchor */ + + if (rrset->rr_type == GETDNS_RRTYPE_DS) { + for ( rr = _getdns_rrtype_iter_init( + &rr_space, rrset) + ; rr + ; rr = _getdns_rrtype_iter_next(rr) + ) { + if (rr->rr_i.nxt - + rr->rr_i.rr_type < 12) + continue; + + DEBUG_ANCHOR("DS with id: %d\n" + , (int)gldns_read_uint16(rr->rr_i.rr_type + 10)); + if (gldns_read_uint16( + rr->rr_i.rr_type + 10) == + context->root_ksk.ids[i]) + break; + } + if (rr) + break; + continue; + } + if (rrset->rr_type != GETDNS_RRTYPE_DNSKEY) + continue; + + for ( rr = _getdns_rrtype_iter_init(&rr_space + , rrset) + ; rr ; rr = _getdns_rrtype_iter_next(rr)) { + + + if (rr->rr_i.nxt-rr->rr_i.rr_type < 12 + || !(rr->rr_i.rr_type[11] & 1)) + continue; /* Not a KSK */ + + if (gldns_calc_keytag_raw( + rr->rr_i.rr_type + 10, + rr->rr_i.nxt-rr->rr_i.rr_type - 10) + == context->root_ksk.ids[i]) + break; + } + if (rr) + break; + } + if (!ta) { + DEBUG_ANCHOR("NOTICE %s(): Key with id %d " + "*not* found in TA.\n" + "\"root-anchors.xml\" need " + "updating.\n", __FUNC__ + , context->root_ksk.ids[i]); + context->trust_anchors_source = + GETDNS_TASRC_XML_UPDATE; + break; + } + DEBUG_ANCHOR("DEBUG %s(): Key with id %d found in TA\n" + , __FUNC__, context->root_ksk.ids[i]); + } + } + if (str_buf && str_buf != str_spc) + GETDNS_FREE(context->mf, str_buf); +} + /* anchor.c */ diff --git a/src/anchor.h b/src/anchor.h index fda7433b..3c826384 100644 --- a/src/anchor.h +++ b/src/anchor.h @@ -37,10 +37,23 @@ #include "getdns/getdns.h" #include "getdns/getdns_extra.h" #include +#include "rr-iter.h" void _getdns_context_equip_with_anchor(getdns_context *context, uint64_t *now_ms); void _getdns_start_fetching_ta(getdns_context *context, getdns_eventloop *loop); +#define MAX_KSKS 16 +#define RRSIG_RDATA_LEN 16 +typedef struct _getdns_ksks { + size_t n; + uint16_t ids[MAX_KSKS]; + size_t n_rrsigs; + uint8_t rrsigs[MAX_KSKS][RRSIG_RDATA_LEN]; +} _getdns_ksks; + +void _getdns_context_update_root_ksk( + getdns_context *context, _getdns_rrset *dnskey_set); + #endif /* anchor.h */ diff --git a/src/context.c b/src/context.c index 89a86005..ae89a098 100644 --- a/src/context.c +++ b/src/context.c @@ -1500,6 +1500,8 @@ getdns_context_create_with_extended_memory_functions( = _getdns_default_root_anchor_verify_email; result->root_anchor_verify_CA = _getdns_default_root_anchor_verify_CA; + (void) memset(&result->root_ksk, 0, sizeof(result->root_ksk)); + (void) memset(&result->a, 0, sizeof(result->a)); (void) memset(&result->aaaa, 0, sizeof(result->aaaa)); result->a.fd = -1; @@ -4654,13 +4656,13 @@ static size_t _getdns_get_appdata(char *path) return 0; } -uint8_t *_getdns_context_get_priv_file(getdns_context *context, - const char *fn, uint8_t *buf, size_t buf_len, size_t *file_sz) +FILE *_getdns_context_get_priv_fp(getdns_context *context, const char *fn) { char path[PATH_MAX]; FILE *f = NULL; size_t len; + (void) context; if (!(len = _getdns_get_appdata(path))) DEBUG_ANCHOR("ERROR %s(): Could nog get application data path\n" , __FUNC__); @@ -4675,6 +4677,17 @@ uint8_t *_getdns_context_get_priv_file(getdns_context *context, DEBUG_ANCHOR("ERROR %s(): Opening \"%s\": %s\n" , __FUNC__, path, strerror(errno)); + return f; +} + +uint8_t *_getdns_context_get_priv_file(getdns_context *context, + const char *fn, uint8_t *buf, size_t buf_len, size_t *file_sz) +{ + FILE *f = NULL; + + if (!(f = _getdns_context_get_priv_fp(context, fn))) + ; /* pass */ + else if ((*file_sz = fread(buf, 1, buf_len, f)) < (buf_len - 1) && feof(f)) { buf[*file_sz] = 0; (void) fclose(f); @@ -4682,19 +4695,19 @@ uint8_t *_getdns_context_get_priv_file(getdns_context *context, } else if (fseek(f, 0, SEEK_END) < 0) DEBUG_ANCHOR("ERROR %s(): Determining size of \"%s\": %s\n" - , __FUNC__, path, strerror(errno)); + , __FUNC__, fn, strerror(errno)); else if (!(buf = GETDNS_XMALLOC( context->mf, uint8_t, (buf_len = ftell(f) + 1)))) DEBUG_ANCHOR("ERROR %s(): Allocating %d memory for \"%s\"\n" - , __FUNC__, (int)buf_len, path); + , __FUNC__, (int)buf_len, fn); else { rewind(f); if ((*file_sz = fread(buf, 1, buf_len, f)) >= buf_len || !feof(f)) { GETDNS_FREE(context->mf, buf); DEBUG_ANCHOR("ERROR %s(): Reading \"%s\": %s\n" - , __FUNC__, path, strerror(errno)); + , __FUNC__, fn, strerror(errno)); } else { buf[*file_sz] = 0; diff --git a/src/context.h b/src/context.h index 8acc8466..8225d10b 100644 --- a/src/context.h +++ b/src/context.h @@ -49,6 +49,7 @@ #include "util/lruhash.h" #endif #include "rr-iter.h" +#include "anchor.h" struct getdns_dns_req; struct ub_ctx; @@ -99,6 +100,7 @@ typedef enum getdns_tasrc { GETDNS_TASRC_APP, GETDNS_TASRC_FETCHING, GETDNS_TASRC_XML, + GETDNS_TASRC_XML_UPDATE, GETDNS_TASRC_FAILED } getdns_tasrc; @@ -341,6 +343,8 @@ struct getdns_context { const char *root_anchor_verify_CA; const char *root_anchor_verify_email; + _getdns_ksks root_ksk; + getdns_upstreams *upstreams; uint16_t limit_outstanding_queries; uint32_t dnssec_allowed_skew; @@ -545,6 +549,7 @@ void _getdns_upstreams_dereference(getdns_upstreams *upstreams); void _getdns_upstream_shutdown(getdns_upstream *upstream); +FILE *_getdns_context_get_priv_fp(getdns_context *context, const char *fn); uint8_t *_getdns_context_get_priv_file(getdns_context *context, const char *fn, uint8_t *buf, size_t buf_len, size_t *file_sz); diff --git a/src/dnssec.c b/src/dnssec.c index d166e91f..4ea9fd97 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -2998,6 +2998,11 @@ static void append_empty_ds2val_chain_list( if (_getdns_list_append_this_dict(val_chain_list, rr_dict)) getdns_dict_destroy(rr_dict); } +static inline chain_node *_to_the_root(chain_node *node) +{ + while (node->parent) node = node->parent; + return node; +} static void check_chain_complete(chain_head *chain) { @@ -3063,6 +3068,16 @@ static void check_chain_complete(chain_head *chain) , context->trust_anchors_len , SECTION_ANSWER)); #endif + if (context->trust_anchors_source != GETDNS_TASRC_XML) + ; /* pass */ + + /* Find root key or query for it in full recursion... */ + else if (!(head = chain) || !(node = _to_the_root(head->parent))) + ; /* pass */ + + else if (node->dnskey.name && *node->dnskey.name == 0) + _getdns_context_update_root_ksk(context, &node->dnskey); + #ifdef DNSSEC_ROADBLOCK_AVOIDANCE if ( dnsreq->dnssec_roadblock_avoidance && !dnsreq->avoid_dnssec_roadblocks diff --git a/src/general.c b/src/general.c index 8ca253b9..60644115 100644 --- a/src/general.c +++ b/src/general.c @@ -581,11 +581,15 @@ getdns_general_ns(getdns_context *context, getdns_eventloop *loop, req->internal_cb = internal_cb; req->is_sync_request = loop == &context->sync_eventloop.loop; - if (req->dnssec_return_status && - context->trust_anchors_source == GETDNS_TASRC_NONE) { - _getdns_context_equip_with_anchor(context, &now_ms); - if (context->trust_anchors_source == GETDNS_TASRC_NONE) + if (req->dnssec_return_status) { + if (context->trust_anchors_source == GETDNS_TASRC_XML_UPDATE) _getdns_start_fetching_ta(context, loop); + + else if (context->trust_anchors_source == GETDNS_TASRC_NONE) { + _getdns_context_equip_with_anchor(context, &now_ms); + if (context->trust_anchors_source == GETDNS_TASRC_NONE) + _getdns_start_fetching_ta(context, loop); + } } /* Set up the context assuming we won't use the specified namespaces. This is (currently) identical to setting up a pure DNS namespace */ diff --git a/src/gldns/keyraw.c b/src/gldns/keyraw.c index 3f6f1a20..365d6a0e 100644 --- a/src/gldns/keyraw.c +++ b/src/gldns/keyraw.c @@ -95,7 +95,7 @@ gldns_rr_dnskey_key_size_raw(const unsigned char* keydata, } } -uint16_t gldns_calc_keytag_raw(uint8_t* key, size_t keysize) +uint16_t gldns_calc_keytag_raw(const uint8_t* key, size_t keysize) { if(keysize < 4) { return 0; diff --git a/src/gldns/keyraw.h b/src/gldns/keyraw.h index 60e2bfde..3d19e96f 100644 --- a/src/gldns/keyraw.h +++ b/src/gldns/keyraw.h @@ -44,7 +44,7 @@ size_t gldns_rr_dnskey_key_size_raw(const unsigned char *keydata, * \param[in] keysize length of key data. * \return the keytag */ -uint16_t gldns_calc_keytag_raw(uint8_t* key, size_t keysize); +uint16_t gldns_calc_keytag_raw(const uint8_t* key, size_t keysize); #if GLDNS_BUILD_CONFIG_HAVE_SSL /**