getdns/src/dnssec.c

3738 lines
111 KiB
C
Raw Normal View History

/**
*
* /brief functions for DNSSEC
*
* In this file, the "dnssec_return_validation_chain" extension is implemented
* (with the _getdns_get_validation_chain() function)
* Also the function getdns_validate_dnssec is implemented.
* DNSSEC validation as a stub combines those two functionalities, by first
* fetching all the records that are necessary to be able to validate a
* request (i.e. the "dnssec_return_validation_chain" extension) and then
* performing DNSSEC validation for a request with those support records
* (and a trust anchor of course).
*/
/*
2014-02-25 07:12:33 -06:00
* Copyright (c) 2013, NLnet Labs, Verisign, Inc.
* All rights reserved.
*
* 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 names of the copyright holders 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 Verisign, Inc. 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.
*/
/*
* From the API:
*
* The "dnssec_return_validation_chain" extension as explained in section 3.1:
*
* Applications that want to do their own validation will want to have the
* DNSSEC-related records for a particular response. Use the
* dnssec_return_validation_chain extension. The extension's value
* (an int) is set to GETDNS_EXTENSION_TRUE to cause a set of additional
* DNSSEC-related records needed for validation to be returned in the
* response object. This set comes as validation_chain (a list) at the top
* level of the response object. This list includes all resource record
* dicts for all the resource records (DS, DNSKEY and their RRSIGs) that
* are needed to perform the validation from the root up.
*
*
* The getdns_validate_dnssec() function as explained in section 7:
*
* If an application wants the API do perform DNSSEC validation without
* using the extensions, it can use the getdns_validate_dnssec() helper
* function.
*
* getdns_return_t
* getdns_validate_dnssec(
* getdns_list *record_to_validate,
* getdns_list *bundle_of_support_records,
* getdns_list *trust_anchor_records
* );
*
* The record_to_validate is the resource record being validated together
* with the associated signatures. The API will use the resource records
* in bundle_of_support_records to construct the validation chain and the
* DNSKEY or DS records in trust_anchor_records as trust anchors. The
* function returns one of GETDNS_DNSSEC_SECURE, GETDNS_DNSSEC_BOGUS,
* GETDNS_DNSSEC_INDETERMINATE, or GETDNS_DNSSEC_INSECURE.
*/
/* Outline of operations in this file
* ==================================
*
* Data structure to represent the delegation/referal hierarchy
* ------------------------------------------------------------
* Both the "dnssec_return_validation_chain" extension, and the
* getdns_validate_dnssec() function use the same structs to represent the
* involved pieces of the DNS in a hierarchical manner.
*
* However, the tree is not represented from the root, but from the RRsets that
* need to be validated. The RRset to validate is a member of the chain_head
* struct for this. The chain_head struct has a "next" member to form a linked
* list of RRsets to validate.
*
* The chain_head struct also has a "parent" member to a linked list of
* chain_node structs (linked with the "parent" member of those chain_nodes).
* For each label in the name of the rrset in a chain_head, is a chain_node,
* all the way to the root. The last chain_node is thus always the root, for
* every chain_head.
*
* The construction functions for this datastructure make sure there is always
* a single chain_node representing the same name. They also make sure space
* for chain_head + the number of extra chain_nodes needed is allocated in a
* single region, so that on destruction one only has to free the chain_heads.
*
* A chain_node contains two RRset members, "dnskey" and "ds" which represent
* the potential client side DNSKEYs and the parent side DS records of a
* potential zonecut at this point. Whether or not there is an actual zone
* cut is determined separately. With the "dnssec_return_validation_chain"
* extension by scheduling queries, and with the getdns_validation_dnssec()
* function by provisioning the support records at the chain nodes.
*
* In the construction functions a chain_head is created for every RRset in
* the answer and authority section of a given packet (except for synthesized
* CNAMEs). Furthermore, if the queries for name/class/type is not in the
* packet, a chain_head for the non-existent rrset is created too, to that
* it will be evaluated for non-existence later in the validation process.
*
* The chain_head and chain_node structs are defined in section:
* "Validation Chain Data Structs". The functions to construct the hierarchy
* are defined in section "Validation Chain Construction". When the
* construction functions are called for the purpose of the
* "dnssec_return_validation_chain" extension, queries to provision the
* chain_nodes are scheduled. Function theretofore are in section:
* "Schedule Queries to Provision Validation Chain"
*
*
* _getdns_rrset
* ------------
* RRsets used in the structure described above are represented by the
* _getdns_rrset struct. They consist of name/rr_class and rr_type members
* plus a reference to the wireformat packet that should contain the RRset.
*
* The actual RR's in the rrset and the signatures are only accessed via
* iterators; substantiated with the rrtype_iter struct to iterate over RRs
* in a _getdns_rrset, and the rrsig_iter to iterate over the RRSIGs covering
* the RRs in the _getdns_rrset.
*
* The _getdns_rrsets are already equiped with name/rr_class and rr_type when
* constructing the linked list of chain_nodes up to the root for a chain_head.
* They are substantiated with the wireformat packets that are returned with
* the queries that were sheduled in the context of the
* "dnssec_return_validation_chain" extension.
*
* Note that the NSEC(3) RRsets proving the non-existance of a _getdns_rrset
* can be found by processing that _getdns_rrset, as it contains the pointer
* to the wireformat data that should either contain the RRset or the proof
* of non-existance.
*
* The getdns_validate_dnssec() function, after it constructed the chain_heads
* hierarchy, creates an artifical packet for the support records and equips
* all the ds and dnskey _getdns_rrsets on the chain_nodes with this packet.
*
* The _getdns_rrset + support function and data types are defined in section:
* "_getdns_rrset + Support Iterators"
*
*
* Validation
* ----------
* Validation of a constructed chain is done by the
* chain_set_netreq_dnssec_status() function when validating in stub mode.
* And with the chain_validate_dnssec() function when using the
* getdns_validate_dnssec() function. They are the same, except that
* chain_set_netreq_dnssec_status() evaluates DNSSEC status per network
* request and chain_validate_dnssec() does it for the whole chain.
*
* They both evaluate the DNSSEC status for each head in turn. The worst
* DNSSEC status determines the status of all heads evaluated. Where
2015-07-06 07:14:46 -05:00
* INSECURE is worse than SECURE, and BOGUS is worse than INSECURE.
*
2015-07-07 17:10:10 -05:00
* For each head, the closest (most labels still a parent of the head's name)
* trust anchor is tried. Without fitting trust anchors, DNSSEC_INDETERMINATE
* is returned.
*
* Security status for a head (with a specific trust anchor) is evaluated by
* first finding a authenticated keyset from the parent chain_nodes, and then
* evaluating the rrset of the head (existent or not) with that keyset.
*
* Functions that implement DNSSEC validation are in section:
* "DNSSEC Validation".
*
* Many functions are of key verification boolean return type; e.g.
* key_proves_non_existance(), ds_authenticates_keys(), a_key_signed_rrset()
* These will return the keytag identifying the key that was used to
* authenticate + 0x10000 to allow keytag 0.
*
* These returned keytag's are used later with function
* append_rrs2val_chain_list() to return a "dnssec_validation_chain" that
* enumerates a single RRSIG per RRset. This can be found in section:
* "dnssec_return_validation_chain Extension".
*/
#include "config.h"
#include "debug.h"
2014-02-19 09:56:00 -06:00
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
2015-08-19 08:19:02 -05:00
#include <openssl/sha.h>
2014-05-19 08:50:34 -05:00
#include "getdns/getdns.h"
#include "context.h"
#include "util-internal.h"
#include "types-internal.h"
2014-02-19 09:56:00 -06:00
#include "dnssec.h"
#include "rr-dict.h"
2015-03-15 15:25:38 -05:00
#include "gldns/str2wire.h"
#include "gldns/wire2str.h"
#include "gldns/keyraw.h"
#include "gldns/parseutil.h"
#include "general.h"
#include "dict.h"
#include "list.h"
2015-09-23 09:48:54 -05:00
#include "util/val_secalgo.h"
#include "anchor.h"
#define SIGNATURE_VERIFIED 0x10000
#define NSEC3_ITERATION_COUNT_HIGH 0x20000
2015-07-07 17:10:10 -05:00
#define NO_SUPPORTED_ALGORITHMS 0x40000
/******************* Frequently Used Utility Functions *********************
*****************************************************************************/
2016-06-23 08:03:46 -05:00
static inline size_t _dname_len(const uint8_t *name)
{
const uint8_t *p;
2015-07-07 17:10:10 -05:00
for (p = name; *p; p += *p + 1)
/* pass */
;
return p - name + 1;
}
2016-06-23 08:03:46 -05:00
static inline size_t _dname_label_count(const uint8_t *name)
{
size_t c;
2015-07-07 17:10:10 -05:00
for (c = 0; *name; name += *name + 1, c++)
/* pass */
;
return c;
}
2016-06-23 08:03:46 -05:00
static inline int _dname_equal(const uint8_t *left, const uint8_t *right)
{
return _getdns_dname_equal(left, right);
}
static int _dname_is_parent(
const uint8_t * const parent, const uint8_t *subdomain)
{
while (*subdomain) {
if (_dname_equal(parent, subdomain))
return 1;
subdomain += *subdomain + 1;
}
return *parent == 0;
}
2015-08-19 08:19:02 -05:00
static uint8_t *_dname_label_copy(uint8_t *dst, const uint8_t *src, size_t dst_len)
{
uint8_t *r = dst, i;
2016-12-08 07:05:58 -06:00
if (!src || (size_t)*src + 1 > dst_len)
return NULL;
2015-07-07 04:15:38 -05:00
for (i = (*dst++ = *src++); i ; i--)
*dst++ = tolower(*src++);
return r;
}
2015-07-07 04:15:38 -05:00
/* Fills the array pointed to by labels (of at least 128 uint8_t * pointers)
* with pointers to labels in given dname in reversed order. So that
* labels[0] will point to the root.
* labels[1] will point to the tld etc.
* A pointer just past the last assigned array element will be returned.
*
* So if dname would be "www.getdnsapi.net"
* labels[0] will be "."
* labels[1] will be "net."
* labels[2] will be "getdnsapi.net."
* labels[3] will be "www.getdnsapi.net."
* The returned value will be &labels[4]
*/
static const uint8_t **reverse_labels(
const uint8_t *dname, const uint8_t **labels)
{
if (*dname)
labels = reverse_labels(dname + *dname + 1, labels);
*labels = dname;
return labels + 1;
}
static const uint8_t *dname_shared_parent(
const uint8_t *left, const uint8_t *right)
{
const uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel,
**llabel, **rlabel, *l, *r;
uint8_t sz;
last_llabel = reverse_labels(left, llabels);
last_rlabel = reverse_labels(right, rlabels);
2015-07-07 04:15:38 -05:00
/* Always at least one label (the root) */
assert(last_llabel > llabels);
assert(last_rlabel > rlabels);
assert(*llabels[0] == 0);
assert(*rlabels[0] == 0);
for ( llabel = &llabels[1], rlabel = &rlabels[1]
; llabel < last_llabel
; llabel++, rlabel++ ) {
2015-07-07 04:15:38 -05:00
sz = **llabel;
if ( rlabel == last_rlabel
2015-07-07 04:15:38 -05:00
|| **llabel != **rlabel)
return llabel[-1];
for (l = *llabel+1, r = *rlabel+1; sz; l++, r++, sz-- )
if (*l != *r && tolower((unsigned char)*l) !=
tolower((unsigned char)*r))
return llabel[-1];
}
return llabel[-1];
}
static int dname_compare(const uint8_t *left, const uint8_t *right)
{
const uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel,
**llabel, **rlabel, *l, *r;
uint8_t lsz, rsz;
last_llabel = reverse_labels(left, llabels);
last_rlabel = reverse_labels(right, rlabels);
for ( llabel = llabels, rlabel = rlabels
; llabel < last_llabel
; llabel++, rlabel++ ) {
if (rlabel == last_rlabel)
return 1;
for ( l = *llabel, lsz = *l++, r = *rlabel, rsz = *r++
; lsz; l++, r++, lsz--, rsz-- ) {
2015-07-07 04:15:38 -05:00
/* No compression pointers here */
assert(lsz <= 63);
assert(rsz <= 63);
if (!rsz)
return 1;
if (*l != *r && tolower((unsigned char)*l) !=
tolower((unsigned char)*r)) {
if (tolower((unsigned char)*l) <
tolower((unsigned char)*r))
return -1;
return 1;
}
}
if (rsz)
return -1;
}
return rlabel == last_rlabel ? 0 : -1;
}
static int bitmap_has_type(_getdns_rdf_iter *bitmap, uint16_t rr_type)
{
const uint8_t *dptr, *dend;
uint8_t window = rr_type >> 8;
uint8_t subtype = rr_type & 0xFF;
if (!bitmap || (dptr = bitmap->pos) == (dend = bitmap->nxt))
return 0;
/* Type Bitmap = ( Window Block # | Bitmap Length | Bitmap ) +
* dptr[0] dptr[1] dptr[2:]
*/
while (dptr < dend && dptr[0] <= window) {
if (dptr[0] == window && subtype / 8 < dptr[1] &&
dptr + dptr[1] + 2 <= dend)
2015-07-07 04:15:38 -05:00
return dptr[2 + subtype / 8] & (0x80 >> (subtype % 8));
dptr += dptr[1] + 2; /* next window */
}
return 0;
}
2015-06-19 11:02:16 -05:00
#if defined(SEC_DEBUG) && SEC_DEBUG
2016-06-23 08:03:46 -05:00
static inline void debug_sec_print_rr(const char *msg, _getdns_rr_iter *rr)
2015-06-19 11:02:16 -05:00
{
char str_spc[8192], *str = str_spc;
size_t str_len = sizeof(str_spc);
const uint8_t *data = rr->pos;
2015-06-19 11:02:16 -05:00
size_t data_len = rr->nxt - rr->pos;
if (!rr || !rr->pos) {
DEBUG_SEC("%s<nil>\n", msg);
2015-06-19 11:02:16 -05:00
return;
}
(void) gldns_wire2str_rr_scan(
(UNCONST_UINT8_p *) &data, &data_len, &str, &str_len,
(UNCONST_UINT8_p) rr->pkt, rr->pkt_end - rr->pkt);
2015-06-19 11:02:16 -05:00
DEBUG_SEC("%s%s", msg, str_spc);
}
2016-06-23 08:03:46 -05:00
static inline void debug_sec_print_dname(const char *msg, const uint8_t *label)
{
char str[1024];
if (label && gldns_wire2str_dname_buf(
(UNCONST_UINT8_p)label, 256, str, sizeof(str)))
DEBUG_SEC("%s%s\n", msg, str);
else
DEBUG_SEC("%s<nil>\n", msg);
}
2015-06-19 11:02:16 -05:00
#else
#define debug_sec_print_rr(...) DEBUG_OFF(__VA_ARGS__)
#define debug_sec_print_dname(...) DEBUG_OFF(__VA_ARGS__)
#define debug_sec_print_pkt(...) DEBUG_OFF(__VA_ARGS__)
2015-06-19 11:02:16 -05:00
#endif
/******************* _getdns_rrset + Support Iterators **********************
*****************************************************************************/
2015-06-19 11:02:16 -05:00
#if defined(SEC_DEBUG) && SEC_DEBUG
static void debug_sec_print_rrset(const char *msg, _getdns_rrset *rrset)
2015-06-19 11:02:16 -05:00
{
char owner[1024];
char buf_space[2048];
gldns_buffer buf;
_getdns_rrtype_iter *rr, rr_space;
_getdns_rrsig_iter *rrsig, rrsig_space;
2015-06-19 11:02:16 -05:00
size_t i;
if (!rrset) {
DEBUG_SEC("<nil>");
return;
}
gldns_buffer_init_frm_data(&buf, buf_space, sizeof(buf_space));
if (gldns_wire2str_dname_buf(
(UNCONST_UINT8_p)rrset->name, 256, owner, sizeof(owner)))
2015-06-19 11:02:16 -05:00
gldns_buffer_printf(&buf, "%s ", owner);
else gldns_buffer_printf(&buf, "<nil> ");
switch (rrset->rr_class) {
case GETDNS_RRCLASS_IN : gldns_buffer_printf(&buf, "IN ") ; break;
case GETDNS_RRCLASS_CH : gldns_buffer_printf(&buf, "CH ") ; break;
case GETDNS_RRCLASS_HS : gldns_buffer_printf(&buf, "HS ") ; break;
case GETDNS_RRCLASS_NONE: gldns_buffer_printf(&buf, "NONE "); break;
case GETDNS_RRCLASS_ANY : gldns_buffer_printf(&buf, "ANY ") ; break;
default : gldns_buffer_printf(&buf, "CLASS%d "
, rrset->rr_class);
break;
}
gldns_buffer_printf(&buf, "%s", _getdns_rr_type_name(rrset->rr_type));
2015-06-19 11:02:16 -05:00
gldns_buffer_printf(&buf, ", rrs:");
for ( rr = _getdns_rrtype_iter_init(&rr_space, rrset), i = 1
2015-06-19 11:02:16 -05:00
; rr
; rr = _getdns_rrtype_iter_next(rr), i++)
2015-06-19 11:02:16 -05:00
gldns_buffer_printf(&buf, " %d", (int)i);
gldns_buffer_printf(&buf, ", rrsigs:");
for ( rrsig = _getdns_rrsig_iter_init(&rrsig_space, rrset), i = 1
2015-06-19 11:02:16 -05:00
; rrsig
; rrsig = _getdns_rrsig_iter_next(rrsig), i++)
2015-06-19 11:02:16 -05:00
gldns_buffer_printf(&buf, " %d", (int)i);
DEBUG_SEC("%s%s\n", msg, buf_space);
}
#else
#define debug_sec_print_rrset(...) DEBUG_OFF(__VA_ARGS__)
#endif
/********************* Validation Chain Data Structs ***********************
*****************************************************************************/
2015-06-19 11:02:16 -05:00
typedef struct chain_head chain_head;
typedef struct chain_node chain_node;
struct chain_head {
struct mem_funcs my_mf;
size_t lock;
chain_head *next;
chain_node *parent;
size_t node_count; /* Number of nodes attached directly
* to this head. For cleaning. */
_getdns_rrset rrset;
getdns_network_req *netreq;
int signer;
uint8_t name_spc[];
2015-06-19 11:02:16 -05:00
};
struct chain_node {
chain_node *parent;
size_t lock;
2015-06-19 11:02:16 -05:00
_getdns_rrset dnskey;
getdns_network_req *dnskey_req;
int dnskey_signer;
_getdns_rrset ds;
getdns_network_req *ds_req;
int ds_signer;
getdns_network_req *soa_req;
2015-06-19 11:02:16 -05:00
chain_head *chains;
};
2015-06-19 11:02:16 -05:00
/********************* Validation Chain Construction ***********************
*****************************************************************************/
/* When construction is done in the context of stub validation, the requests
* to equip the chain nodes with their RR sets are done alongside construction.
* Hence they need to be enumerated before the construction functions.
*/
static void val_chain_sched(chain_head *head, const uint8_t *dname);
static void val_chain_sched_ds(chain_head *head, const uint8_t *dname);
static void val_chain_sched_signer(chain_head *head, _getdns_rrsig_iter *rrsig);
static void val_chain_sched_soa(chain_head *head, const uint8_t *dname);
2015-06-27 16:28:23 -05:00
static chain_head *add_rrset2val_chain(struct mem_funcs *mf,
chain_head **chain_p, _getdns_rrset *rrset, getdns_network_req *netreq)
{
chain_head *head;
const uint8_t *labels[128], **last_label, **label;
2016-12-08 07:05:58 -06:00
ssize_t max_labels; /* max labels in common */
chain_head *max_head;
chain_node *max_node;
size_t dname_len, head_sz, node_count, n;
const uint8_t *dname;
uint8_t *region;
chain_node *node;
last_label = reverse_labels(rrset->name, labels);
/* Try to find a chain with the most overlapping labels.
* max_labels will be the number of labels in common from the root
* (so at least one; the root)
2015-07-07 04:15:38 -05:00
* max_head will be the head of the chain with max # labels in common
*/
max_head = NULL;
max_labels = 0;
for (head = *chain_p; head; head = head->next) {
/* Also, try to prevent adding double rrsets */
if ( rrset->rr_class == head->rrset.rr_class
&& rrset->rr_type == head->rrset.rr_type
&& _dname_equal(rrset->name, head->rrset.name)) {
if (rrset->pkt == head->rrset.pkt &&
rrset->pkt_len == head->rrset.pkt_len)
return NULL;
else {
/* Anticipate resubmissions due to
* roadblock avoidance */
head->rrset.pkt = rrset->pkt;
head->rrset.pkt_len = rrset->pkt_len;
return head;
}
}
if ( rrset->rr_class == head->rrset.rr_class
&& rrset->rr_type == head->rrset.rr_type
&& rrset->pkt != head->rrset.pkt
&& _dname_equal(rrset->name, head->rrset.name)) {
return NULL;
}
for (label = labels; label < last_label; label++) {
if (! _dname_is_parent(*label, head->rrset.name))
break;
}
2017-04-13 08:45:59 -05:00
if ((ssize_t)(label - labels) > max_labels) {
max_labels = label - labels;
max_head = head;
}
}
/* Chain found. Now set max_node to the point in the chain where nodes
* will be common.
*/
if (max_head) {
for ( node = max_head->parent, n = 0
; node
; node = node->parent, n++);
for ( n -= max_labels, node = max_head->parent
2016-04-29 05:00:17 -05:00
; n && node
; n--, node = node->parent);
max_node = node;
} else
max_node = NULL;
/* node_count is the amount of nodes to still allocate.
* the last one's parent has to hook into the max_node.
*/
dname_len = *labels - last_label[-1] + 1;
head_sz = (sizeof(chain_head) + dname_len + 7) / 8 * 8;
node_count = last_label - labels - max_labels;
2017-09-27 06:15:12 -05:00
DEBUG_SEC( "%"PRIsz" labels in common. %"PRIsz" labels to allocate\n"
, max_labels, node_count);
if (! (region = GETDNS_XMALLOC(*mf, uint8_t, head_sz +
node_count * sizeof(chain_node))))
return NULL;
/* Append the head on the linked list of heads */
2015-07-07 04:15:38 -05:00
for (head = *chain_p; head && head->next; head = head->next)
;
if (head)
head = head->next = (chain_head *)region;
else
head = *chain_p = (chain_head *)region;
head->my_mf = *mf;
head->lock = 1;
head->next = NULL;
head->rrset.name = head->name_spc;
memcpy(head->name_spc, rrset->name, dname_len);
head->rrset.rr_class = rrset->rr_class;
head->rrset.rr_type = rrset->rr_type;
head->rrset.pkt = rrset->pkt;
head->rrset.pkt_len = rrset->pkt_len;
head->rrset.sections = rrset->sections;
head->netreq = netreq;
head->signer = -1;
head->node_count = node_count;
if (!node_count) {
/* When this head has no nodes of itself, it must have found
* another head which has nodes for its labels (i.e. max_head)
*/
assert(max_head != NULL);
head->parent = max_head->parent;
return head;
}
/* Initialize the nodes */
2015-07-07 04:15:38 -05:00
for ( head->parent = node = (chain_node *)(region + head_sz),
dname = head->rrset.name
; node_count
; node_count--, node = node->parent =&node[1], dname += *dname + 1) {
node->lock = 0;
node->ds.name = dname;
node->dnskey.name = dname;
node->ds.rr_class = head->rrset.rr_class;
node->dnskey.rr_class = head->rrset.rr_class;
node->ds.rr_type = GETDNS_RRTYPE_DS;
node->dnskey.rr_type = GETDNS_RRTYPE_DNSKEY;
node->ds.pkt = NULL;
node->ds.pkt_len = 0;
node->ds.sections = head->rrset.sections;
node->dnskey.pkt = NULL;
node->dnskey.pkt_len = 0;
node->dnskey.sections = head->rrset.sections;
node->ds_req = NULL;
node->dnskey_req = NULL;
node->soa_req = NULL;
node->ds_signer = -1;
node->dnskey_signer = -1;
node->chains = *chain_p;
}
/* On the first chain, max_node == NULL.
* Schedule a root DNSKEY query, we always need that.
*/
if (!(node[-1].parent = max_node))
val_chain_sched(head, (uint8_t *)"\0");
return head;
}
2015-06-27 16:28:23 -05:00
static int is_synthesized_cname(_getdns_rrset *cname)
{
_getdns_rrset_iter *i, i_spc;
_getdns_rrset *dname;
_getdns_rrtype_iter rr_spc, *rr;
_getdns_rdf_iter rdf_spc, *rdf;
_getdns_rrtype_iter drr_spc, *drr;
_getdns_rdf_iter drdf_spc, *drdf;
uint8_t cname_rdata_spc[256],
dname_rdata_spc[256],
synth_name[256],
*synth_name_end = synth_name + sizeof(synth_name) - 1, *s;
const uint8_t *cname_rdata, *dname_rdata, *c;
size_t cname_rdata_len = sizeof(cname_rdata_spc),
dname_rdata_len = sizeof(dname_rdata_len),
cname_labels, dname_labels;
2015-06-27 16:28:23 -05:00
/* Synthesized CNAMEs don't have RRSIGs */
if ( cname->rr_type != GETDNS_RRTYPE_CNAME
|| _getdns_rrset_has_rrsigs(cname))
return 0;
2015-06-27 16:28:23 -05:00
/* Get canonical name rdata field */
if ( !(rr = _getdns_rrtype_iter_init(&rr_spc, cname))
|| !(rdf = _getdns_rdf_iter_init(&rdf_spc, &rr->rr_i))
|| !(cname_rdata = _getdns_rdf_if_or_as_decompressed(
rdf, cname_rdata_spc, &cname_rdata_len)))
return 0;
/* Find a matching DNAME */
for ( i = _getdns_rrset_iter_init(&i_spc, cname->pkt, cname->pkt_len
, SECTION_ANSWER)
; i
; i = _getdns_rrset_iter_next(i)) {
2015-06-27 16:28:23 -05:00
dname = _getdns_rrset_iter_value(i);
if ( dname->rr_type != GETDNS_RRTYPE_DNAME
/* DNAME->owner is parent of CNAME->owner */
|| !_dname_is_parent(dname->name, cname->name))
continue;
2015-06-27 16:28:23 -05:00
dname_labels = _dname_label_count(dname->name);
cname_labels = _dname_label_count(cname->name);
2015-06-27 16:28:23 -05:00
/* Synthesize the canonical name.
* First copy labels(cname) - labels(dname) labels from
* CNAME's owner name, then append DNAME rdata field.
* If it matches CNAME's rdata field then it was synthesized
* with this DNAME.
*/
cname_labels -= dname_labels;
for ( c = cname->name, s = synth_name
; cname_labels && s + *c + 1 < synth_name_end
; cname_labels--, c += *c + 1, s += *s + 1 ) {
2015-06-27 16:28:23 -05:00
memcpy(s, c, *c + 1);
}
if (cname_labels)
continue;
2015-06-27 16:28:23 -05:00
/* Get DNAME's rdata field */
if ( !(drr = _getdns_rrtype_iter_init(&drr_spc, dname))
|| !(drdf=_getdns_rdf_iter_init(&drdf_spc,&drr->rr_i))
|| !(dname_rdata = _getdns_rdf_if_or_as_decompressed(
drdf, dname_rdata_spc, &dname_rdata_len)))
continue;
if (s + _dname_len(dname_rdata) > synth_name_end)
continue;
memcpy(s, dname_rdata, _dname_len(dname_rdata));
debug_sec_print_dname("Synthesized name: ", synth_name);
debug_sec_print_dname(" Canonical name: ", cname_rdata);
if (_dname_equal(synth_name, cname_rdata))
return 1;
}
return 0;
}
/* Create the validation chain structure for the given packet.
* When netreq is set, queries will be scheduled for the DS
* and DNSKEY RR's for the nodes on the validation chain.
2015-07-07 07:52:32 -05:00
*
* Scheduling is as follows.
* If the RRset has a signature, signer name is followed to schedule DS/DNSKEY.
* Otherwise, if the RRSET is a SOA, owner name is followed to schedule DS
* Otherwise, if the RRset is a CNAME, a SOA query is scheduled for the parent
* Otherwise, a SOA query is scheduled for the owner name.
*
* When a SOA query was successful, a query for DS will follow for that
* owner name.
*/
static void add_pkt2val_chain(struct mem_funcs *mf,
chain_head **chain_p, uint8_t *pkt, size_t pkt_len,
getdns_network_req *netreq)
{
_getdns_rrset_iter *i, i_spc;
_getdns_rrset *rrset;
_getdns_rrsig_iter *rrsig, rrsig_spc;
size_t n_rrsigs;
chain_head *head;
assert(pkt);
assert(pkt_len >= GLDNS_HEADER_SIZE);
/* For all things with signatures, create a chain */
/* For all things without signature, find SOA (zonecut) and query DS */
for ( i = _getdns_rrset_iter_init(&i_spc, pkt, pkt_len
2016-06-07 10:08:35 -05:00
, SECTION_NO_ADDITIONAL)
; i
; i = _getdns_rrset_iter_next(i)) {
2015-06-27 16:28:23 -05:00
rrset = _getdns_rrset_iter_value(i);
debug_sec_print_rrset("rrset: ", rrset);
2015-06-27 16:28:23 -05:00
/* Schedule validation for everything, except from DNAME
* synthesized CNAME's
*/
if (is_synthesized_cname(rrset))
continue;
if (!(head = add_rrset2val_chain(mf, chain_p, rrset, netreq)))
continue;
for ( rrsig = _getdns_rrsig_iter_init(&rrsig_spc, rrset), n_rrsigs = 0
; rrsig
; rrsig = _getdns_rrsig_iter_next(rrsig), n_rrsigs++) {
2015-07-07 07:52:32 -05:00
/* Signature, so lookup DS/DNSKEY at signer's name */
val_chain_sched_signer(head, rrsig);
}
if (n_rrsigs)
continue;
2015-07-07 07:52:32 -05:00
/* No signatures found for this RRset */
if (rrset->rr_type == GETDNS_RRTYPE_SOA)
2015-07-14 03:22:42 -05:00
val_chain_sched_ds(head, rrset->name);
2015-07-07 04:15:38 -05:00
else if (rrset->rr_type == GETDNS_RRTYPE_CNAME)
val_chain_sched_soa(head, rrset->name + *rrset->name + 1);
else
val_chain_sched_soa(head, rrset->name);
}
}
/* For NOERROR/NODATA or NXDOMAIN responses add extra rrset to
* the validation chain so the denial of existence will be
* checked eventually.
* But only if we know the question of course...
*/
static void add_question2val_chain(struct mem_funcs *mf,
chain_head **chain_p, uint8_t *pkt, size_t pkt_len,
const uint8_t *qname, uint16_t qtype, uint16_t qclass,
getdns_network_req *netreq)
{
2016-06-09 08:03:51 -05:00
_getdns_rrset_spc q_rrset;
chain_head *head;
assert(pkt);
assert(pkt_len >= GLDNS_HEADER_SIZE);
assert(qname);
/* First find the canonical name for the question */
2016-06-09 08:03:51 -05:00
q_rrset.rrset.name = qname;
q_rrset.rrset.rr_type = qtype;
q_rrset.rrset.rr_class = qclass;
q_rrset.rrset.pkt = pkt;
q_rrset.rrset.pkt_len = pkt_len;
q_rrset.rrset.sections = SECTION_ANSWER;
if (_getdns_initialized_rrset_answer(&q_rrset))
return;
2016-06-09 08:03:51 -05:00
/* No answer for the question. Add a head for this rrset
* anyway, to validate proof of non-existance, or to find
* proof that the packet is insecure.
*/
debug_sec_print_rrset("Adding NX rrset: ", &q_rrset.rrset);
head = add_rrset2val_chain(mf, chain_p, &q_rrset.rrset, netreq);
2016-06-09 08:03:51 -05:00
/* On empty packet, find SOA (zonecut) for the qname */
if (head && GLDNS_ANCOUNT(pkt) == 0 && GLDNS_NSCOUNT(pkt) == 0)
val_chain_sched_soa(head, q_rrset.rrset.name);
}
/************* Schedule Queries to Provision Validation Chain ***************
*****************************************************************************/
static getdns_dict *CD_extension(getdns_dns_req *dnsreq)
{
#ifdef DNSSEC_ROADBLOCK_AVOIDANCE
return !dnsreq->dnssec_roadblock_avoidance
? dnssec_ok_checking_disabled
: !dnsreq->avoid_dnssec_roadblocks
? dnssec_ok_checking_disabled_roadblock_avoidance
: dnssec_ok_checking_disabled_avoid_roadblocks;
#else
2016-12-08 15:46:53 -06:00
(void)dnsreq;
return dnssec_ok_checking_disabled;
#endif
}
static void check_chain_complete(chain_head *chain);
static void val_chain_node_soa_cb(getdns_dns_req *dnsreq);
static void val_chain_sched_soa_node(chain_node *node)
{
getdns_context *context;
getdns_eventloop *loop;
char name[1024];
context = node->chains->netreq->owner->context;
loop = node->chains->netreq->owner->loop;
if (!gldns_wire2str_dname_buf(
(UNCONST_UINT8_p)node->ds.name, 256, name, sizeof(name)))
return;
2015-07-07 04:15:38 -05:00
DEBUG_SEC("schedule SOA lookup for %s\n", name);
node->lock++;
if (! node->soa_req &&
_getdns_general_loop(context, loop, name, GETDNS_RRTYPE_SOA,
CD_extension(node->chains->netreq->owner), node, &node->soa_req,
NULL, val_chain_node_soa_cb))
node->soa_req = NULL;
2016-06-07 10:21:10 -05:00
if (node->lock) node->lock--;
}
2015-07-07 07:52:32 -05:00
/* A SOA lookup is scheduled as a last resort. No signatures were found and
* no SOA in the authority section. If a SOA query returns an actual SOA
* answer, then a DS/DNSKEY lookup will follow the acquire the link of the
* authentication chain.
*/
static void val_chain_sched_soa(chain_head *head, const uint8_t *dname)
{
chain_node *node;
if (!head->netreq)
return;
if (!*dname)
return;
for ( node = head->parent
; node && !_dname_equal(dname, node->ds.name)
; node = node->parent);
if (node)
val_chain_sched_soa_node(node);
}
static chain_head *_dnskey_query(const chain_node *node)
{
chain_head *head;
for (head = node->chains; head; head = head->next)
if (head->rrset.rr_type == GETDNS_RRTYPE_DNSKEY &&
head->parent == node)
return head;
return NULL;
}
static void val_chain_node_cb(getdns_dns_req *dnsreq);
static void val_chain_sched_node(chain_node *node)
{
getdns_context *context;
getdns_eventloop *loop;
char name[1024];
context = node->chains->netreq->owner->context;
loop = node->chains->netreq->owner->loop;
if (!gldns_wire2str_dname_buf(
(UNCONST_UINT8_p)node->ds.name, 256, name, sizeof(name)))
return;
DEBUG_SEC("schedule DS & DNSKEY lookup for %s\n", name);
node->lock++;
if (! node->dnskey_req) {
chain_head *head;
/* Reuse the DNSKEY query if this node is scheduled in the
* context of validating a DNSKEY query, because libunbound
* does not callback from a callback for the same query.
*/
if ((head = _dnskey_query(node))) {
DEBUG_SEC("Found DNSKEY head: %p\n", (void *)head);
node->dnskey_req = head->netreq;
node->dnskey.pkt = head->netreq->response;
node->dnskey.pkt_len = head->netreq->response_len;
} else if (_getdns_general_loop(
context, loop, name, GETDNS_RRTYPE_DNSKEY,
CD_extension(node->chains->netreq->owner),
node, &node->dnskey_req, NULL, val_chain_node_cb))
node->dnskey_req = NULL;
}
if (! node->ds_req && node->parent /* not root */ &&
_getdns_general_loop(context, loop, name, GETDNS_RRTYPE_DS,
CD_extension(node->chains->netreq->owner),
node, &node->ds_req, NULL, val_chain_node_cb))
node->ds_req = NULL;
2016-06-07 10:21:10 -05:00
if (node->lock) node->lock--;
}
static void val_chain_sched(chain_head *head, const uint8_t *dname)
{
chain_node *node;
if (!head->netreq)
return;
for ( node = head->parent
; node && !_dname_equal(dname, node->ds.name)
; node = node->parent);
if (node)
val_chain_sched_node(node);
}
2015-07-14 03:22:42 -05:00
static void val_chain_sched_ds_node(chain_node *node)
{
getdns_context *context;
getdns_eventloop *loop;
char name[1024];
context = node->chains->netreq->owner->context;
loop = node->chains->netreq->owner->loop;
if (!gldns_wire2str_dname_buf(
(UNCONST_UINT8_p)node->ds.name, 256, name, sizeof(name)))
2015-07-14 03:22:42 -05:00
return;
DEBUG_SEC("schedule DS lookup for %s\n", name);
node->lock++;
2015-07-14 03:22:42 -05:00
if (! node->ds_req && node->parent /* not root */ &&
_getdns_general_loop(context, loop, name, GETDNS_RRTYPE_DS,
CD_extension(node->chains->netreq->owner),
node, &node->ds_req, NULL, val_chain_node_cb))
2015-07-14 03:22:42 -05:00
node->ds_req = NULL;
2015-07-14 03:22:42 -05:00
2016-06-07 10:21:10 -05:00
if (node->lock) node->lock--;
2015-07-14 03:22:42 -05:00
}
static void val_chain_sched_ds(chain_head *head, const uint8_t *dname)
2015-07-14 03:22:42 -05:00
{
chain_node *node;
if (!head->netreq)
return;
for ( node = head->parent
; node && !_dname_equal(dname, node->ds.name)
; node = node->parent);
if (node)
val_chain_sched_ds_node(node);
}
static void val_chain_sched_signer_node(chain_node *node, _getdns_rrsig_iter *rrsig)
{
_getdns_rdf_iter rdf_spc, *rdf;
uint8_t signer_spc[256];
const uint8_t *signer;
size_t signer_len;
if (!(rdf = _getdns_rdf_iter_init_at(&rdf_spc, &rrsig->rr_i, 7)))
return;
if (!(signer = _getdns_rdf_if_or_as_decompressed(
rdf, signer_spc, &signer_len)))
return;
while (node && !_dname_equal(signer, node->ds.name))
node = node->parent;
if (node)
val_chain_sched_node(node);
}
static void val_chain_sched_signer(chain_head *head, _getdns_rrsig_iter *rrsig)
{
if (!head->netreq)
return;
val_chain_sched_signer_node(head->parent, rrsig);
}
static void val_chain_node_cb(getdns_dns_req *dnsreq)
{
chain_node *node = (chain_node *)dnsreq->user_pointer;
getdns_network_req *netreq = dnsreq->netreqs[0];
_getdns_rrset_iter *i, i_spc;
_getdns_rrset *rrset;
_getdns_rrsig_iter *rrsig, rrsig_spc;
size_t n_signers;
switch (netreq->request_type) {
case GETDNS_RRTYPE_DS : node->ds.pkt = netreq->response;
node->ds.pkt_len = netreq->response_len;
break;
case GETDNS_RRTYPE_DNSKEY: node->dnskey.pkt = netreq->response;
node->dnskey.pkt_len = netreq->response_len;
/* fallthrough */
default : check_chain_complete(node->chains);
return;
}
node->lock++;
n_signers = 0;
for ( i = _getdns_rrset_iter_init(&i_spc, netreq->response
, netreq->response_len
, SECTION_NO_ADDITIONAL)
; i
; i = _getdns_rrset_iter_next(i)) {
rrset = _getdns_rrset_iter_value(i);
if (rrset->rr_type != GETDNS_RRTYPE_DS &&
rrset->rr_type != GETDNS_RRTYPE_NSEC &&
rrset->rr_type != GETDNS_RRTYPE_NSEC3)
continue;
for ( rrsig = _getdns_rrsig_iter_init(&rrsig_spc, rrset)
; rrsig; rrsig = _getdns_rrsig_iter_next(rrsig)) {
val_chain_sched_signer_node(node, rrsig);
n_signers++;
}
}
if (netreq->request_type == GETDNS_RRTYPE_DS && n_signers == 0)
/* No signed DS and no signed proof of non-existance.
* Search further up the tree...
*/
val_chain_sched_soa_node(node->parent);
2016-06-07 10:21:10 -05:00
if (node->lock) node->lock--;
check_chain_complete(node->chains);
}
static void val_chain_node_soa_cb(getdns_dns_req *dnsreq)
{
chain_node *node = (chain_node *)dnsreq->user_pointer;
getdns_network_req *netreq = dnsreq->netreqs[0];
_getdns_rrset_iter i_spc, *i;
_getdns_rrset *rrset;
/* A SOA query is always scheduled with a node as the user argument.
*/
assert(node != NULL);
for ( i = _getdns_rrset_iter_init(&i_spc, netreq->response
, netreq->response_len
, SECTION_ANSWER)
; i
; i = _getdns_rrset_iter_next(i)) {
rrset = _getdns_rrset_iter_value(i);
if (rrset->rr_type != GETDNS_RRTYPE_SOA)
continue;
while (node &&
! _dname_equal(node->ds.name, rrset->name))
node = node->parent;
if (node) {
node->lock++;
2015-07-14 03:22:42 -05:00
val_chain_sched_ds_node(node);
} else {
/* SOA for a different name */
node = (chain_node *)dnsreq->user_pointer;
if (node->parent) {
node->lock++;
val_chain_sched_soa_node(node->parent);
}
}
break;
}
if (!i && node->parent) {
node->lock++;
val_chain_sched_soa_node(node->parent);
}
2016-06-07 10:21:10 -05:00
if (node->lock) node->lock--;
check_chain_complete(node->chains);
}
/*************************** DNSSEC Validation *****************************
*****************************************************************************/
/* Returns whether a key in set dnskey is used to sign rrset.
* Only keytag and signer name is compared. The signature is not verified.
*/
static int key_matches_signer(_getdns_rrset *dnskey, _getdns_rrset *rrset)
{
_getdns_rrtype_iter rr_spc, *rr;
_getdns_rrsig_iter rrsig_spc, *rrsig;
uint16_t keytag;
_getdns_rdf_iter rdf_spc, *rdf;
uint8_t signer_spc[256];
const uint8_t *signer;
size_t signer_len = sizeof(signer_spc);
assert(dnskey->rr_type == GETDNS_RRTYPE_DNSKEY);
for ( rr = _getdns_rrtype_iter_init(&rr_spc, dnskey)
; rr ; rr = _getdns_rrtype_iter_next(rr) ) {
2015-07-07 07:52:32 -05:00
/* Enough space to at least read algorithm field? */
if (rr->rr_i.nxt < rr->rr_i.rr_type + 14)
continue;
/* Then we have at least 4 bytes to calculate keytag */
keytag = gldns_calc_keytag_raw(
(UNCONST_UINT8_p)rr->rr_i.rr_type + 10,
rr->rr_i.nxt - rr->rr_i.rr_type - 10);
for ( rrsig = _getdns_rrsig_iter_init(&rrsig_spc, rrset)
; rrsig ; rrsig = _getdns_rrsig_iter_next(rrsig) ) {
if (/* Space for keytag & signer in rrsig rdata? */
rrsig->rr_i.nxt >= rrsig->rr_i.rr_type + 28
2015-07-07 07:52:32 -05:00
/* Does Algorithm match */
&& rrsig->rr_i.rr_type[12] == rr->rr_i.rr_type[13]
/* Does the keytag match? */
&& gldns_read_uint16(rrsig->rr_i.rr_type + 26)
== keytag
/* Does the signer name match? */
&& (rdf = _getdns_rdf_iter_init_at(
&rdf_spc, &rrsig->rr_i, 7))
&& (signer = _getdns_rdf_if_or_as_decompressed(
rdf, signer_spc, &signer_len))
&& _dname_equal(dnskey->name, signer))
return keytag;
}
}
return 0;
}
static size_t _rr_uncompressed_rdata_size(_getdns_rrtype_iter *rr)
2015-09-21 10:13:44 -05:00
{
_getdns_rdf_iter *rdf, rdf_spc;
uint8_t decompressed[256];
size_t sz = 0, decompressed_sz;
for ( rdf = _getdns_rdf_iter_init(&rdf_spc, &rr->rr_i)
; rdf
; rdf = _getdns_rdf_iter_next(rdf)) {
if ((rdf->rdd_pos->type & GETDNS_RDF_N_C) == GETDNS_RDF_N_C) {
decompressed_sz = sizeof(decompressed);
2015-09-23 09:03:59 -05:00
if (_getdns_rdf_if_or_as_decompressed(
rdf, decompressed, &decompressed_sz))
sz += decompressed_sz;
2015-09-21 10:13:44 -05:00
} else
sz += rdf->nxt - rdf->pos;
}
return sz;
}
static size_t _rr_rdata_size(_getdns_rrtype_iter *rr)
2015-09-21 10:13:44 -05:00
{
const _getdns_rr_def *rr_def;
size_t i;
rr_def = _getdns_rr_def_lookup(gldns_read_uint16(rr->rr_i.rr_type));
for (i = 0; i < rr_def->n_rdata_fields; i++)
if ((rr_def->rdata[i].type & GETDNS_RDF_N_C) == GETDNS_RDF_N_C)
return _rr_uncompressed_rdata_size(rr);
/* assert(gldns_read_uint16(rr->rr_type+8) == rr->nxt-rr->rr_type-10);
*/
return rr->rr_i.nxt - rr->rr_i.rr_type - 10;
}
2015-09-22 05:27:17 -05:00
/* Iterate byte by byte over rdata canonicalizing dname's */
typedef struct canon_rdata_iter {
_getdns_rdf_iter rdf_spc;
_getdns_rdf_iter *rdf;
uint8_t cdname[256]; /* Canonical dname */
const uint8_t *pos;
2015-09-22 05:27:17 -05:00
size_t len;
} canon_rdata_iter;
2016-06-23 08:03:46 -05:00
static inline void canon_rdata_iter_field_init(canon_rdata_iter *i)
2015-09-22 05:27:17 -05:00
{
for (;;) {
if ((i->rdf->rdd_pos->type & GETDNS_RDF_N) == GETDNS_RDF_N) {
i->len = sizeof(i->cdname);
if ((i->pos = _getdns_rdf_if_or_as_decompressed(
i->rdf, i->cdname, &i->len))) {
_dname_canonicalize(i->pos, i->cdname);
i->pos = i->cdname;
}
2015-09-22 05:27:17 -05:00
} else {
i->pos = i->rdf->pos;
i->len = i->rdf->nxt - i->rdf->pos;
}
if (i->len || !(i->rdf = _getdns_rdf_iter_next(i->rdf)))
return;
}
}
2016-06-23 08:03:46 -05:00
static inline void canon_rdata_iter_init(canon_rdata_iter*i,_getdns_rr_iter*rr)
2015-09-22 05:27:17 -05:00
{
if ((i->rdf = _getdns_rdf_iter_init(&i->rdf_spc, rr)))
canon_rdata_iter_field_init(i);
}
2016-06-23 08:03:46 -05:00
static inline int canon_rdata_iter_data(canon_rdata_iter *i)
2015-09-22 05:27:17 -05:00
{
return i->rdf != NULL;
}
2016-06-23 08:03:46 -05:00
static inline uint8_t canon_rdata_iter_byte(canon_rdata_iter *i)
2015-09-22 05:27:17 -05:00
{
return *i->pos;
}
2016-06-23 08:03:46 -05:00
static inline void canon_rdata_iter_next(canon_rdata_iter *i)
2015-09-22 05:27:17 -05:00
{
if (--i->len == 0 && (i->rdf = _getdns_rdf_iter_next(i->rdf)))
canon_rdata_iter_field_init(i);
else
i->pos++;
}
static int _rr_iter_rdata_cmp(const void *a, const void *b)
{
_getdns_rr_iter *x = (_getdns_rr_iter *)a;
_getdns_rr_iter *y = (_getdns_rr_iter *)b;
uint16_t rr_type = gldns_read_uint16(x->rr_type);
size_t x_rdata_len, y_rdata_len;
int r;
canon_rdata_iter p, q;
assert(rr_type == gldns_read_uint16(y->rr_type));
if (!_dnssec_rdata_to_canonicalize(rr_type)) {
/* Memory compare of rdata */
x_rdata_len = x->nxt - x->rr_type - 10;
y_rdata_len = y->nxt - y->rr_type - 10;
if ((r = memcmp(x->rr_type + 10, y->rr_type + 10,
x_rdata_len < y_rdata_len ? x_rdata_len : y_rdata_len)))
return r;
return x_rdata_len < y_rdata_len ? -1 :
x_rdata_len > y_rdata_len ? 1 : 0;
}
for ( canon_rdata_iter_init(&p, x), canon_rdata_iter_init(&q, y)
; canon_rdata_iter_data(&p) && canon_rdata_iter_data(&q)
; canon_rdata_iter_next(&p) , canon_rdata_iter_next(&q) ) {
if (canon_rdata_iter_byte(&p) != canon_rdata_iter_byte(&q))
return canon_rdata_iter_byte(&p) >
canon_rdata_iter_byte(&q) ? 1 : -1;
}
return canon_rdata_iter_data(&p) ? 1
: canon_rdata_iter_data(&q) ? -1 : 0;
}
2015-09-21 10:13:44 -05:00
/* Verifies the signature rrsig for rrset rrset with key key.
* When the rrset was a wildcard expansion (rrsig labels < labels owner name),
* nc_name will be set to the next closer (within rrset->name).
*/
#define VAL_RRSET_SPC_SZ 256
static int _getdns_verify_rrsig(struct mem_funcs *mf,
_getdns_rrset *rrset, _getdns_rrsig_iter *rrsig, _getdns_rrtype_iter *key, const uint8_t **nc_name)
{
int r;
2015-07-07 07:52:32 -05:00
int to_skip;
2015-09-21 05:59:30 -05:00
_getdns_rr_iter val_rrset_spc[VAL_RRSET_SPC_SZ];
_getdns_rr_iter *val_rrset = val_rrset_spc;
_getdns_rrtype_iter rr_spc, *rr;
2015-09-23 09:03:59 -05:00
size_t n_rrs, i, valbuf_sz, owner_len;
_getdns_rdf_iter *signer, signer_spc, *rdf, rdf_spc;
uint8_t valbuf_spc[4096], *valbuf_buf = valbuf_spc;
uint8_t cdname_spc[256], owner[256];
2016-06-09 03:44:25 -05:00
const uint8_t *cdname, *owner_offset;
2015-09-23 09:03:59 -05:00
size_t cdname_len, pos;
uint32_t orig_ttl;
gldns_buffer valbuf;
2015-09-25 04:48:58 -05:00
char *reason;
/* nc_name should already have been initialized by the parent! */
assert(nc_name);
assert(!*nc_name);
2015-09-23 09:03:59 -05:00
if (!(signer = _getdns_rdf_iter_init_at(&signer_spc, &rrsig->rr_i, 7)))
return 0;
valbuf_sz = signer->nxt - rrsig->rr_i.rr_type - 10;
2016-06-09 03:44:25 -05:00
/* We were able to get the signer,
* so the labels rdata field is definitely readable
*/
assert(rrsig->rr_i.rr_type + 14 <= rrsig->rr_i.nxt);
/* If the number of labels in the owner is larger than "labels" rdata
* field, then this is a wildcard expansion.
*/
if (_dname_label_count(rrset->name) > (size_t)rrsig->rr_i.rr_type[13]) {
/* This is a valid wildcard expansion. Calculate and return the
* "Next closer" name, because we need another NSEC to cover it.
* (except for rrsigs for NSECs, but those are dealt with later)
*/
to_skip = (int)_dname_label_count(rrset->name)
- (int)rrsig->rr_i.rr_type[13];
for ( owner_offset = rrset->name
; to_skip > 0
; owner_offset += *owner_offset + 1, to_skip--);
if ((owner_len = _dname_len(owner_offset) + 2) > 255)
return 0;
owner[0] = 1; owner[1] = '*';
(void) memcpy(owner + 2, owner_offset, owner_len - 2);
} else if ((owner_len = _dname_len(rrset->name)) > 255)
2015-09-21 10:13:44 -05:00
return 0;
2016-06-09 03:44:25 -05:00
else
(void) memcpy(owner, rrset->name, owner_len);
_dname_canonicalize2(owner);
2015-09-21 10:13:44 -05:00
2015-09-22 05:27:17 -05:00
for (;;) {
for ( rr = _getdns_rrtype_iter_init(&rr_spc, rrset), n_rrs = 0
2015-09-21 05:59:30 -05:00
; rr
; rr = _getdns_rrtype_iter_next(rr), n_rrs++) {
2015-09-21 05:59:30 -05:00
if (val_rrset == val_rrset_spc) {
2015-09-21 10:13:44 -05:00
valbuf_sz += owner_len
+ 2 /* type */
+ 2 /* class */
+ 4 /* Orig TTL */
+ 2 /* Rdata len */
+ _rr_rdata_size(rr);
2015-09-23 09:03:59 -05:00
if (n_rrs < VAL_RRSET_SPC_SZ)
val_rrset[n_rrs] = rr->rr_i;
2015-09-21 05:59:30 -05:00
} else
2015-09-23 09:03:59 -05:00
val_rrset[n_rrs] = rr->rr_i;
2015-09-21 05:59:30 -05:00
}
2015-09-22 05:27:17 -05:00
/* Did everything fit? Then break */
2015-09-23 09:03:59 -05:00
if (val_rrset != val_rrset_spc || n_rrs <= VAL_RRSET_SPC_SZ)
2015-09-22 05:27:17 -05:00
break;
2015-09-21 05:59:30 -05:00
/* More space needed for val_rrset */
2015-09-23 09:03:59 -05:00
val_rrset = GETDNS_XMALLOC(*mf, _getdns_rr_iter, n_rrs);
2015-09-22 05:27:17 -05:00
}
2017-09-27 06:15:12 -05:00
DEBUG_SEC( "sizes: %"PRIsz" rrs, %"PRIsz" bytes for validation buffer\n"
2015-09-23 09:03:59 -05:00
, n_rrs, valbuf_sz);
qsort(val_rrset, n_rrs, sizeof(_getdns_rr_iter), _rr_iter_rdata_cmp);
if (valbuf_sz >= sizeof(valbuf_spc))
valbuf_buf = GETDNS_XMALLOC(*mf, uint8_t, valbuf_sz);
2015-09-21 05:59:30 -05:00
2015-09-23 09:03:59 -05:00
gldns_buffer_init_frm_data(&valbuf, valbuf_buf, valbuf_sz);
gldns_buffer_write(&valbuf,
rrsig->rr_i.rr_type + 10, signer->nxt - rrsig->rr_i.rr_type - 10);
_dname_canonicalize2(gldns_buffer_at(&valbuf, 18));
2015-09-22 05:27:17 -05:00
2015-09-23 09:03:59 -05:00
orig_ttl = gldns_read_uint32(rrsig->rr_i.rr_type + 14);
2015-09-23 09:03:59 -05:00
if (!_dnssec_rdata_to_canonicalize(rrset->rr_type))
for (i = 0; i < n_rrs; i++) {
/* Get rid of doubles */
if (i && !_rr_iter_rdata_cmp(
&val_rrset[i], &val_rrset[i-1]))
continue;
2015-09-23 09:03:59 -05:00
gldns_buffer_write(&valbuf, owner, owner_len);
gldns_buffer_write_u16(&valbuf, rrset->rr_type);
gldns_buffer_write_u16(&valbuf, rrset->rr_class);
gldns_buffer_write_u32(&valbuf, orig_ttl);
gldns_buffer_write(&valbuf, val_rrset[i].rr_type + 8,
val_rrset[i].nxt - val_rrset[i].rr_type - 8);
}
else for (i = 0; i < n_rrs; i++) {
if (i && !_rr_iter_rdata_cmp(&val_rrset[i], &val_rrset[i-1]))
continue;
2015-09-23 09:03:59 -05:00
gldns_buffer_write(&valbuf, owner, owner_len);
gldns_buffer_write_u16(&valbuf, rrset->rr_type);
gldns_buffer_write_u16(&valbuf, rrset->rr_class);
gldns_buffer_write_u32(&valbuf, orig_ttl);
pos = gldns_buffer_position(&valbuf);
gldns_buffer_skip(&valbuf, 2);
for ( rdf = _getdns_rdf_iter_init(&rdf_spc, &val_rrset[i])
; rdf
; rdf = _getdns_rdf_iter_next(rdf) ) {
if (!(rdf->rdd_pos->type & GETDNS_RDF_N)) {
gldns_buffer_write(
&valbuf, rdf->pos, rdf->nxt - rdf->pos);
continue;
}
cdname_len = sizeof(cdname);
if (!(cdname = _getdns_rdf_if_or_as_decompressed(
rdf, cdname_spc, &cdname_len)))
continue;
gldns_buffer_write(&valbuf, cdname, cdname_len);
_dname_canonicalize2(
2015-09-23 09:03:59 -05:00
gldns_buffer_current(&valbuf) - cdname_len);
}
gldns_buffer_write_u16_at(&valbuf, pos,
(uint16_t)(gldns_buffer_position(&valbuf) - pos - 2));
}
2017-09-27 06:15:12 -05:00
DEBUG_SEC( "written to valbuf: %"PRIsz" bytes\n"
2015-09-23 09:03:59 -05:00
, gldns_buffer_position(&valbuf));
assert(gldns_buffer_position(&valbuf) <= valbuf_sz);
2015-09-23 09:03:59 -05:00
gldns_buffer_flip(&valbuf);
2015-09-25 04:48:58 -05:00
r = _getdns_verify_canonrrset(&valbuf, key->rr_i.rr_type[13],
(UNCONST_UINT8_p)signer->nxt, rrsig->rr_i.nxt - signer->nxt,
(UNCONST_UINT8_p)key->rr_i.rr_type+14,
key->rr_i.nxt - key->rr_i.rr_type-14,
2015-09-25 04:48:58 -05:00
&reason);
2015-09-23 09:03:59 -05:00
2015-09-25 04:48:58 -05:00
#if defined(SEC_DEBUG) && SEC_DEBUG
if (r == 0) {
2015-09-25 04:48:58 -05:00
DEBUG_SEC("verification failed: %s\n", reason);
2016-06-09 03:44:25 -05:00
debug_sec_print_dname("verification failed for: ", owner);
debug_sec_print_rrset("verification failed for rrset: ", rrset);
debug_sec_print_rr("verification failed sig: ", &rrsig->rr_i);
debug_sec_print_rr("verification failed key: ", &key->rr_i);
}
2015-09-25 04:48:58 -05:00
#endif
2015-09-21 05:59:30 -05:00
if (val_rrset != val_rrset_spc)
GETDNS_FREE(*mf, val_rrset);
2015-09-23 09:03:59 -05:00
if (valbuf_buf != valbuf_spc)
GETDNS_FREE(*mf, valbuf_buf);
if (!r)
return 0;
/* Verification has already been done, so the labels rdata field is
* definitely readable
*/
assert(rrsig->rr_i.rr_type + 14 <= rrsig->rr_i.nxt);
/* If the number of labels in the owner name mathes the "labels" rdata
* field, then this was not a wildcard expansion, and everything is
* good.
*/
if ((size_t)rrsig->rr_i.rr_type[13] == _dname_label_count(rrset->name))
return 1;
/* This is a valid wildcard expansion. Calculate and return the
* "Next closer" name, because we need another NSEC to cover it.
* (except for rrsigs for NSECs, but those are dealt with later)
*/
2015-07-07 07:52:32 -05:00
to_skip = (int)_dname_label_count(rrset->name)
- (int)rrsig->rr_i.rr_type[13] - 1;
for ( *nc_name = rrset->name
2015-07-07 07:52:32 -05:00
; to_skip > 0
; *nc_name += **nc_name + 1, to_skip--);
return 1;
}
/* Calculates NSEC3 hash for name, and stores that into label */
static uint8_t *_getdns_nsec3_hash_label(uint8_t *label, size_t label_len,
const uint8_t *name, uint8_t algorithm,
uint16_t iterations, const uint8_t *salt)
{
2015-08-19 08:19:02 -05:00
uint8_t buf[512], *dst, *eob;
const uint8_t *src;
uint8_t md[SHA_DIGEST_LENGTH + 256];
assert(SHA_DIGEST_LENGTH + 256 < sizeof(buf));
if (algorithm != GLDNS_SHA1)
return NULL;
2015-08-19 08:19:02 -05:00
for ( src = name, dst = buf, eob = buf + sizeof(buf)
; *src && dst + *src < eob
; src += *src + 1, dst += *dst + 1 )
_dname_label_copy(dst, src, eob - dst);
if (*src || dst + *salt >= eob)
return NULL;
2015-08-19 08:19:02 -05:00
*dst++ = 0;
(void)memcpy(dst, salt + 1, *salt);
dst += *salt;
(void)SHA1(buf, dst - buf, md);
if (iterations) {
(void)memcpy(buf + SHA_DIGEST_LENGTH, salt + 1, *salt);
while (iterations--) {
(void)memcpy(buf, md, SHA_DIGEST_LENGTH);
SHA1(buf, SHA_DIGEST_LENGTH + *salt, md);
}
}
2015-08-19 08:19:02 -05:00
*label = gldns_b32_ntop_extended_hex(
md, SHA_DIGEST_LENGTH, (char *)label + 1, label_len - 1);
return label;
}
static uint8_t *name2nsec3_label(
_getdns_rrset *nsec3, const uint8_t *name, uint8_t *label, size_t label_len)
{
_getdns_rrsig_iter rrsig_spc, *rrsig;
_getdns_rdf_iter rdf_spc, *rdf;
uint8_t signer_spc[256];
const uint8_t *signer;
size_t signer_len = sizeof(signer_spc);
_getdns_rrtype_iter rr_spc, *rr;
if (/* With the "first" signature */
(rrsig = _getdns_rrsig_iter_init(&rrsig_spc, nsec3))
/* Access the signer name rdata field (7th) */
&& (rdf = _getdns_rdf_iter_init_at(
&rdf_spc, &rrsig->rr_i, 7))
/* Verify & decompress */
&& (signer = _getdns_rdf_if_or_as_decompressed(
rdf, signer_spc, &signer_len))
/* signer of the NSEC3 is direct parent for this NSEC3? */
&& _dname_equal(
signer, nsec3->name + *nsec3->name + 1)
/* signer of the NSEC3 is parent of name? */
&& _dname_is_parent(signer, name)
/* Initialize rr for getting NSEC3 rdata fields */
&& (rr = _getdns_rrtype_iter_init(&rr_spc, nsec3))
/* Check for available space to get rdata fields */
&& rr->rr_i.rr_type + 15 <= rr->rr_i.nxt
&& rr->rr_i.rr_type + 14 + rr->rr_i.rr_type[14] <= rr->rr_i.nxt)
/* Get the hashed label */
return _getdns_nsec3_hash_label(label, label_len, name,
rr->rr_i.rr_type[10],
gldns_read_uint16(rr->rr_i.rr_type + 12),
rr->rr_i.rr_type + 14);
return NULL;
}
static int nsec3_iteration_count_high(_getdns_rrtype_iter *dnskey, _getdns_rrset *nsec3)
{
_getdns_rrtype_iter rr_spc, *rr;
size_t bits;
/* No NSEC3, then iteration count is not too high */
if (nsec3->rr_type != GETDNS_RRTYPE_NSEC3)
return 0;
/* Enough space to at least read algorithm field?
* Without key data iteration count is definitely too high.
*/
if (dnskey->rr_i.nxt < dnskey->rr_i.rr_type + 14)
return 1;
if (/* Initialize rr for getting NSEC3 rdata fields */
!(rr = _getdns_rrtype_iter_init(&rr_spc, nsec3))
/* Check for available space to get rdata fields */
|| rr->rr_i.rr_type + 14 > rr->rr_i.nxt)
return 1;
2015-09-25 04:48:58 -05:00
bits = gldns_rr_dnskey_key_size_raw(dnskey->rr_i.rr_type + 10,
dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10,
dnskey->rr_i.rr_type[13]);
if (bits > 2048)
return gldns_read_uint16(rr->rr_i.rr_type + 12) > 2500;
else if (bits > 1024)
return gldns_read_uint16(rr->rr_i.rr_type + 12) > 500;
else
return gldns_read_uint16(rr->rr_i.rr_type + 12) > 150;
}
static int check_dates(time_t now, int32_t skew, int32_t exp, int32_t inc)
{
return (exp - inc > 0) && (inc - now < skew) && (now - exp < skew);
}
/* Returns whether dnskey signed rrset. If the rrset was a valid wildcard
* expansion, nc_name will point to the next closer part of the name in rrset.
*/
static int dnskey_signed_rrset(struct mem_funcs *mf, time_t now, uint32_t skew,
_getdns_rrtype_iter *dnskey, _getdns_rrset *rrset, const uint8_t **nc_name)
{
_getdns_rrsig_iter rrsig_spc, *rrsig;
_getdns_rdf_iter rdf_spc, *rdf;
uint8_t signer_spc[256];
const uint8_t *signer;
size_t signer_len = sizeof(signer_spc);
uint16_t keytag;
assert(dnskey->rrset->rr_type == GETDNS_RRTYPE_DNSKEY);
assert(nc_name);
*nc_name = NULL;
2015-07-07 07:52:32 -05:00
/* Enough space to at least read algorithm field? */
if (dnskey->rr_i.nxt < dnskey->rr_i.rr_type + 14)
return 0;
/* Then we have at least 4 bytes to calculate keytag */
keytag = gldns_calc_keytag_raw((UNCONST_UINT8_p)dnskey->rr_i.rr_type + 10,
dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10);
for ( rrsig = _getdns_rrsig_iter_init(&rrsig_spc, rrset)
; rrsig ; rrsig = _getdns_rrsig_iter_next(rrsig) ) {
if (/* Space for keytag & signer in rrsig rdata? */
rrsig->rr_i.nxt >= rrsig->rr_i.rr_type + 28
2015-07-07 07:52:32 -05:00
/* Does Algorithm match */
&& rrsig->rr_i.rr_type[12] == dnskey->rr_i.rr_type[13]
/* Does the keytag match? */
&& gldns_read_uint16(rrsig->rr_i.rr_type + 26) == keytag
/* Signature still (or already) valid? */
&& check_dates(now, skew,
gldns_read_uint32(rrsig->rr_i.rr_type + 18),
gldns_read_uint32(rrsig->rr_i.rr_type + 22))
/* Does the signer name match? */
&& (rdf = _getdns_rdf_iter_init_at(
&rdf_spc, &rrsig->rr_i, 7))
&& (signer = _getdns_rdf_if_or_as_decompressed(
rdf, signer_spc, &signer_len))
&& _dname_equal(dnskey->rrset->name, signer)
2015-07-07 07:52:32 -05:00
/* Does the signature verify? */
&& _getdns_verify_rrsig(mf, rrset,rrsig,dnskey,nc_name)) {
debug_sec_print_rr("key ", &dnskey->rr_i);
debug_sec_print_rrset("signed ", rrset);
/* Signal insecurity by too high nsec3 iteration
* count with NSEC3_ITERATION_COUNT_HIGH
* bit in return value.
*/
return ( nsec3_iteration_count_high(dnskey, rrset)
? NSEC3_ITERATION_COUNT_HIGH
: SIGNATURE_VERIFIED
) | keytag;
}
}
return 0;
}
static int find_nsec_covering_name(
struct mem_funcs *mf, time_t now, uint32_t skew, _getdns_rrset *dnskey,
_getdns_rrset *rrset, const uint8_t *name, int *opt_out);
2015-07-07 04:15:38 -05:00
/* Returns whether a dnskey for keyset signed rrset. */
static int a_key_signed_rrset(struct mem_funcs *mf, time_t now, uint32_t skew,
_getdns_rrset *keyset, _getdns_rrset *rrset)
{
_getdns_rrtype_iter dnskey_spc, *dnskey;
const uint8_t *nc_name;
int keytag;
assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY);
for ( dnskey = _getdns_rrtype_iter_init(&dnskey_spc, keyset)
; dnskey ; dnskey = _getdns_rrtype_iter_next(dnskey) ) {
if (!(keytag = dnskey_signed_rrset(mf, now, skew,
dnskey, rrset, &nc_name)))
continue;
2015-06-27 16:28:23 -05:00
if (!nc_name) /* Not a wildcard, then success! */
return keytag;
2015-06-27 16:28:23 -05:00
/* Wildcard RRSIG for a NSEC on the wildcard.
* There is no more specific!
*/
if (rrset->rr_type == GETDNS_RRTYPE_NSEC &&
rrset->name[0] == 1 && rrset->name[1] == '*')
return keytag;
2015-06-27 16:28:23 -05:00
debug_sec_print_rrset("wildcard expanded to: ", rrset);
debug_sec_print_dname("Find NSEC covering the more sepecific: "
, nc_name);
if (find_nsec_covering_name(
mf, now, skew, keyset, rrset, nc_name, NULL))
return keytag;
}
return 0;
}
/* Returns whether a DS in ds_set matches a dnskey in dnskey_set which in turn
* signed the dnskey set.
*/
static int ds_authenticates_keys(struct mem_funcs *mf,
time_t now, uint32_t skew, _getdns_rrset *ds_set, _getdns_rrset *dnskey_set)
{
_getdns_rrtype_iter dnskey_spc, *dnskey;
_getdns_rrtype_iter ds_spc, *ds;
uint16_t keytag;
const uint8_t *nc_name;
size_t valid_dsses = 0, supported_dsses = 0;
uint8_t max_supported_digest = 0;
int max_supported_result = 0;
unsigned char digest_spc[256], *digest;
unsigned char digest_buf_spc[2048], *digest_buf;
size_t digest_len, digest_buf_len, dnskey_owner_len;
assert(ds_set->rr_type == GETDNS_RRTYPE_DS);
assert(dnskey_set->rr_type == GETDNS_RRTYPE_DNSKEY);
/* The ds_set is already authenticated! */
if (!_dname_equal(ds_set->name, dnskey_set->name))
return 0;
debug_sec_print_rrset("ds_authenticates_keys DS: ", ds_set);
debug_sec_print_rrset("ds_authenticates_keys DNSKEY: ", dnskey_set);
if ((dnskey_owner_len = _dname_len(dnskey_set->name)) >= 255)
return 0;
(void) memcpy(digest_buf_spc, dnskey_set->name, dnskey_owner_len);
_dname_canonicalize2(digest_buf_spc);
for ( dnskey = _getdns_rrtype_iter_init(&dnskey_spc, dnskey_set)
; dnskey ; dnskey = _getdns_rrtype_iter_next(dnskey)) {
2015-07-07 07:52:32 -05:00
/* Enough space to at least read algorithm field? */
if (dnskey->rr_i.nxt < dnskey->rr_i.rr_type + 14)
continue;
keytag = gldns_calc_keytag_raw(
(UNCONST_UINT8_p) dnskey->rr_i.rr_type + 10,
dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10);
for ( ds = _getdns_rrtype_iter_init(&ds_spc, ds_set)
; ds ; ds = _getdns_rrtype_iter_next(ds)) {
if (/* Space for keytag, algorithm & digest type? */
ds->rr_i.nxt < ds->rr_i.rr_type + 14
2015-07-07 07:52:32 -05:00
/* Does algorithm match? */
|| ds->rr_i.rr_type[12] != dnskey->rr_i.rr_type[13]
/* Does the keytag match? */
2015-06-27 16:28:23 -05:00
|| gldns_read_uint16(ds->rr_i.rr_type+10)!=keytag)
continue;
valid_dsses++;
if (/* Algorithm is not RSAMD5 (deprecated) */
ds->rr_i.rr_type[12] == GLDNS_RSAMD5
/* Algorithm is supported */
|| !_getdns_dnskey_algo_id_is_supported(
ds->rr_i.rr_type[12])
/* Digest is supported */
|| !(digest_len = _getdns_ds_digest_size_supported(
ds->rr_i.rr_type[13])))
2015-06-27 16:28:23 -05:00
continue;
digest = digest_len <= sizeof(digest_spc) ? digest_spc
: GETDNS_XMALLOC(*mf, unsigned char, digest_len);
2015-06-27 16:28:23 -05:00
digest_buf_len = dnskey->rr_i.nxt
- dnskey->rr_i.rr_type - 10
+ dnskey_owner_len;
digest_buf = digest_buf_len <= sizeof(digest_buf_spc)
? digest_buf_spc
: GETDNS_XMALLOC(*mf, unsigned char, digest_buf_len);
if (digest_buf != digest_buf_spc)
(void) memcpy(digest_buf,
digest_buf_spc, dnskey_owner_len);
(void) memcpy(digest_buf + dnskey_owner_len,
dnskey->rr_i.rr_type + 10,
dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10);
2015-06-27 16:28:23 -05:00
if (!_getdns_secalgo_ds_digest(ds->rr_i.rr_type[13],
digest_buf, digest_buf_len, digest)) {
if (digest != digest_spc)
GETDNS_FREE(*mf, digest);
if (digest_buf != digest_buf_spc)
GETDNS_FREE(*mf, digest_buf);
continue;
}
supported_dsses++;
/* The result of the best digest type counts!
* We'll assume higher is better for now.
* So, continue with next DS if...
*/
if (/* we already had a better digest earlier */
ds->rr_i.rr_type[13] < max_supported_digest
/* or we had the same digest and it already gave
* a match (to a key in dnskey_set which
* authenticated the dnskey_set).
*/
|| ( ds->rr_i.rr_type[13] == max_supported_digest
&& max_supported_result)) {
if (digest != digest_spc)
GETDNS_FREE(*mf, digest);
if (digest_buf != digest_buf_spc)
GETDNS_FREE(*mf, digest_buf);
DEBUG_SEC("Better DS available\n");
continue;
}
max_supported_digest = ds->rr_i.rr_type[13];
max_supported_result = 0;
2016-12-08 07:05:58 -06:00
if ((int)digest_len != ds->rr_i.nxt - ds->rr_i.rr_type-14
|| memcmp(digest, ds->rr_i.rr_type+14, digest_len) != 0) {
if (digest != digest_spc)
GETDNS_FREE(*mf, digest);
if (digest_buf != digest_buf_spc)
GETDNS_FREE(*mf, digest_buf);
2017-09-27 06:15:12 -05:00
DEBUG_SEC("HASH length mismatch %"PRIsz" != %"PRIsz"\n",
digest_len, ds->rr_i.nxt - ds->rr_i.rr_type-14);
2015-06-27 16:28:23 -05:00
continue;
}
/* Match! */
if (digest != digest_spc)
GETDNS_FREE(*mf, digest);
if (digest_buf != digest_buf_spc)
GETDNS_FREE(*mf, digest_buf);
2015-06-27 16:28:23 -05:00
if (!dnskey_signed_rrset(mf, now, skew,
dnskey, dnskey_set, &nc_name)
|| nc_name /* No DNSKEY's on wildcards! */) {
2015-06-27 16:28:23 -05:00
debug_sec_print_rrset("keyset did not "
"authenticate: ", dnskey_set);
continue;
2015-06-27 16:28:23 -05:00
}
debug_sec_print_rrset(
"keyset authenticated: ", dnskey_set);
max_supported_result = SIGNATURE_VERIFIED | keytag;
}
}
2017-09-27 06:15:12 -05:00
DEBUG_SEC("valid_dsses: %"PRIsz", supported_dsses: %"PRIsz"\n",
valid_dsses, supported_dsses);
if (valid_dsses && !supported_dsses)
return NO_SUPPORTED_ALGORITHMS;
else
return max_supported_result;
}
static int nsec_covers_name(
_getdns_rrset *nsec, const uint8_t *name, const uint8_t **ce_name)
{
uint8_t owner_spc[256], next_spc[256];
const uint8_t *owner, *next;
size_t owner_len = sizeof(owner_spc), next_len = sizeof(next_spc);
_getdns_rrtype_iter rr_spc, *rr;
_getdns_rdf_iter rdf_spc, *rdf;
int nsec_cmp;
const uint8_t *common1, *common2;
2015-07-03 15:50:29 -05:00
if (/* Get owner and next, nicely decompressed */
!(rr = _getdns_rrtype_iter_init(&rr_spc, nsec))
|| !(rdf = _getdns_rdf_iter_init(&rdf_spc, &rr->rr_i))
|| !(owner = _getdns_owner_if_or_as_decompressed(
&rr->rr_i, owner_spc, &owner_len))
|| !(next = _getdns_rdf_if_or_as_decompressed(
rdf, next_spc, &next_len)))
return 0;
debug_sec_print_dname("nsec owner: ", owner);
debug_sec_print_dname("name : ", name);
debug_sec_print_dname("nsec next : ", next);
2015-06-27 16:28:23 -05:00
if (ce_name) {
common1 = dname_shared_parent(name, owner);
common2 = dname_shared_parent(name, next);
*ce_name = _dname_label_count(common1)
> _dname_label_count(common2) ? common1 : common2;
debug_sec_print_dname("nsec closest encloser: ", *ce_name);
}
nsec_cmp = dname_compare(owner, next);
if (nsec_cmp < 0) {
/* Regular NSEC
* >= so it can match the wildcard
* (for wildcard NODATA proofs).
*/
return dname_compare(name, owner) >= 0
&& dname_compare(name, next) < 0;
2015-07-03 15:50:29 -05:00
} else if (nsec_cmp > 0) {
2015-07-03 15:50:29 -05:00
/* The wrap around nsec. So NSEC->nxt == zone.name.
* qname must be a subdomain of that.
*/
return dname_compare(name, owner) >= 0
&& _dname_is_parent(next, name) && dname_compare(next, name);
2015-07-03 15:50:29 -05:00
} else {
2015-07-03 15:50:29 -05:00
/* This nsec is the only nsec.
* zone.name NSEC zone.name, disproves everything else,
* but only for subdomains of that zone.
* (also no zone.name == qname of course)
*/
return _dname_is_parent(owner, name) && dname_compare(owner, name);
}
}
static int nsec3_matches_name(_getdns_rrset *nsec3, const uint8_t *name)
{
uint8_t label[64], owner[64];
if (name2nsec3_label(nsec3, name, label, sizeof(label))
&& _dname_label_copy(owner, nsec3->name, sizeof(owner)))
return *nsec3->name == label[0] /* Labels same size? */
&& memcmp(owner + 1, label + 1, label[0]) == 0;
return 0;
}
static int nsec3_covers_name(
_getdns_rrset *nsec3, const uint8_t *name, int *opt_out)
{
uint8_t label[65], next[65], owner[65];
_getdns_rrtype_iter rr_spc, *rr;
_getdns_rdf_iter rdf_spc, *rdf;
int nsz = 0, nsec_cmp;
if (!name2nsec3_label(nsec3, name, label, sizeof(label)-1))
return 0;
label[label[0]+1] = 0;
if ( !(rr = _getdns_rrtype_iter_init(&rr_spc, nsec3))
|| !(rdf = _getdns_rdf_iter_init_at(&rdf_spc, &rr->rr_i, 4))
|| rdf->pos + *rdf->pos + 1 > rdf->nxt
|| (nsz = gldns_b32_ntop_extended_hex(rdf->pos + 1, *rdf->pos,
(char *)next + 1, sizeof(next)-2)) < 0
|| *nsec3->name > sizeof(owner) - 2
|| !_dname_label_copy(owner, nsec3->name, sizeof(owner)-1)) {
2015-06-27 16:28:23 -05:00
DEBUG_SEC("Error getting NSEC3 owner & next labels\n");
return 0;
}
owner[owner[0]+1] = 0;
next[(next[0] = (uint8_t)nsz)+1] = 0;
2015-06-28 06:41:48 -05:00
if (opt_out)
*opt_out = (rr->rr_i.rr_type[11] & 1) != 0;
debug_sec_print_dname("NSEC3 for: ", name);
debug_sec_print_dname(" is: ", label);
debug_sec_print_dname("inbetween: ", owner);
debug_sec_print_dname(" and: ", next);
nsec_cmp = dname_compare(owner, next);
if (nsec_cmp >= 0) {
/* The wrap around and apex-only nsec case */
return dname_compare(label, owner) > 0
|| dname_compare(label, next) < 0;
} else {
assert(nsec_cmp < 0);
/* The normal case
* >= so it can match the wildcard
* (for wildcard NODATA proofs).
*/
return dname_compare(label, owner) >= 0
&& dname_compare(label, next) < 0;
}
}
static int find_nsec_covering_name(
struct mem_funcs *mf, time_t now, uint32_t skew, _getdns_rrset *dnskey,
_getdns_rrset *rrset, const uint8_t *name, int *opt_out)
{
_getdns_rrset_iter i_spc, *i;
_getdns_rrset *n;
_getdns_rrtype_iter nsec_spc, *nsec_rr;
_getdns_rdf_iter bitmap_spc, *bitmap;
int keytag;
2015-06-28 06:41:48 -05:00
if (opt_out)
*opt_out = 0;
for ( i = _getdns_rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len
, SECTION_NO_ADDITIONAL)
; i ; i = _getdns_rrset_iter_next(i)) {
if ((n = _getdns_rrset_iter_value(i))
&& n->rr_type == GETDNS_RRTYPE_NSEC3
/* Get the bitmap rdata field */
&& (nsec_rr = _getdns_rrtype_iter_init(&nsec_spc, n))
&& (bitmap = _getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i, 5))
&& (keytag = a_key_signed_rrset(mf, now, skew, dnskey, n))
&& ( keytag & NSEC3_ITERATION_COUNT_HIGH
|| ( nsec3_covers_name(n, name, opt_out)
/* NSEC should cover, but not match name...
* Unless it is wildcard match, but then we have to
* check that rrset->rr_type is not enlisted,
* because otherwise it should have matched the
* wildcard.
*
* Also no CNAME... cause that should have matched too.
*/
&& ( !nsec3_matches_name(n, name)
|| ( name[0] == 1 && name[1] == (uint8_t)'*'
&& !bitmap_has_type(bitmap, rrset->rr_type)
&& !bitmap_has_type(bitmap,
GETDNS_RRTYPE_CNAME)
)
)
)
)
) {
debug_sec_print_rrset("NSEC3: ", n);
debug_sec_print_dname("covered: ", name);
return keytag;
2015-06-27 16:28:23 -05:00
}
if ((n = _getdns_rrset_iter_value(i))
&& n->rr_type == GETDNS_RRTYPE_NSEC
2015-06-27 16:28:23 -05:00
&& nsec_covers_name(n, name, NULL)
2015-07-03 15:50:29 -05:00
/* Get the bitmap rdata field */
&& (nsec_rr = _getdns_rrtype_iter_init(&nsec_spc, n))
&& (bitmap = _getdns_rdf_iter_init_at(
2015-07-03 15:50:29 -05:00
&bitmap_spc, &nsec_rr->rr_i, 1))
/* NSEC should cover, but not match name...
* Unless it is wildcard match, but then we have to check
* that rrset->rr_type is not enlisted, because otherwise
* it should have matched the wildcard.
*
* Also no CNAME... cause that should have matched too.
*/
&& ( !_dname_equal(n->name, name)
2015-07-03 15:50:29 -05:00
|| ( name[0] == 1 && name[1] == (uint8_t)'*'
&& !bitmap_has_type(bitmap, rrset->rr_type)
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_CNAME)
)
)
/* When qname is a subdomain of the NSEC owner, make
* sure there is no DNAME, and no delegation point
* there.
*/
&& ( !_dname_is_parent(n->name, name)
2015-07-03 15:50:29 -05:00
|| ( !bitmap_has_type(bitmap, GETDNS_RRTYPE_DNAME)
&& ( !bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
)
)
)
&& (keytag = a_key_signed_rrset(mf,now,skew, dnskey, n))) {
2015-06-27 16:28:23 -05:00
debug_sec_print_rrset("NSEC: ", n);
debug_sec_print_dname("covered: ", name);
return keytag;
}
}
return 0;
}
static int nsec3_find_next_closer(
struct mem_funcs *mf, time_t now, uint32_t skew,
_getdns_rrset *dnskey, _getdns_rrset *rrset,
const uint8_t *nc_name, int *opt_out)
{
uint8_t wc_name[256] = { 1, (uint8_t)'*' };
int my_opt_out, keytag;
if (opt_out)
*opt_out = 0;
if (!(keytag = find_nsec_covering_name(
mf, now, skew, dnskey, rrset, nc_name, &my_opt_out))) {
2015-07-04 06:14:16 -05:00
/* TODO: At least google doesn't return next_closer on wildcard
* nodata for DS query. And in fact returns even bogus for,
* for example bladiebla.xavier.nlnet.nl DS.
*/
return 0;
2015-07-04 06:14:16 -05:00
}
if (opt_out)
*opt_out = my_opt_out;
2015-06-28 06:41:48 -05:00
/* Wild card not needed on a "covering" NODATA response,
2015-06-27 16:28:23 -05:00
* because of opt-out?
2015-06-28 06:41:48 -05:00
*
* We check for opt-out bit, because rcode is unreliable...
* ... the checked packet might be artificially constructed
* (if we came here via getdns_validate_dnssec) in which case
* rcode is always NOERROR.
2015-06-27 16:28:23 -05:00
*/
if (my_opt_out || keytag & NSEC3_ITERATION_COUNT_HIGH)
return keytag;
nc_name += *nc_name + 1;
if (_dname_len(nc_name) > sizeof(wc_name) - 2)
return 0;
else
(void) memcpy(wc_name + 2, nc_name, _dname_len(nc_name));
return find_nsec_covering_name(
mf, now, skew, dnskey, rrset, wc_name, opt_out);
}
/*
* Does a key from keyset dnskey prove the nonexistence of the (name, type)
* tuple in rrset?
*
* On success returns the keytag + SIGNATURE_VERIFIED (0x10000) of the key
* that signed the proof.
* Or in case there were NSEC3's with too high iteration count for the
* verifying key: it returns keytag + NSEC3_ITERATION_COUNT_HIGH (0x20000)
*/
static int key_proves_nonexistance(
struct mem_funcs *mf, time_t now, uint32_t skew,
_getdns_rrset *keyset, _getdns_rrset *rrset, int *opt_out)
{
_getdns_rrset nsec_rrset, *cover, *ce;
_getdns_rrtype_iter nsec_spc, *nsec_rr;
_getdns_rdf_iter bitmap_spc, *bitmap;
_getdns_rrset_iter i_spc, *i;
const uint8_t *ce_name, *nc_name;
2015-06-27 16:28:23 -05:00
uint8_t wc_name[256] = { 1, (uint8_t)'*' };
int keytag;
assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY);
if (opt_out)
*opt_out = 0;
/* The NSEC NODATA case
* ====================
* NSEC has same ownername as the rrset to deny.
* Only the rr_type is missing from the bitmap.
*/
nsec_rrset = *rrset;
nsec_rrset.rr_type = GETDNS_RRTYPE_NSEC;
nsec_rrset.sections = SECTION_NO_ADDITIONAL;
if (/* A NSEC RR exists at the owner name of rrset */
(nsec_rr = _getdns_rrtype_iter_init(&nsec_spc, &nsec_rrset))
/* Get the bitmap rdata field */
&& (bitmap = _getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i, 1))
/* At least the rr_type of rrset should be missing from it */
&& !bitmap_has_type(bitmap, rrset->rr_type)
/* If the name is a CNAME, then we should have gotten the CNAME,
* So no CNAME bit either.
*/
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_CNAME)
/* In case of a DS query, make sure we have the parent side NSEC
* and not the child (so no SOA).
* Except for the root that is checked by itself.
*/
&& ( rrset->rr_type != GETDNS_RRTYPE_DS
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|| *rrset->name == 0
)
/* If not a DS query, then make sure the NSEC does not contain NS,
* or if it does, then also contains SOA, otherwise we have a parent
* side delegation point NSEC where we should have gotten a child
* side NSEC!
*/
&& ( rrset->rr_type == GETDNS_RRTYPE_DS
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA))
/* And a valid signature please */
&& (keytag = a_key_signed_rrset(mf,now,skew,keyset,&nsec_rrset))) {
debug_sec_print_rrset("NSEC NODATA proof for: ", rrset);
return keytag;
}
2015-07-03 15:50:29 -05:00
/* More NSEC NODATA cases
* ======================
* There are a few NSEC NODATA cases where qname doesn't match
* NSEC->name:
*
* - An empty non terminal (ENT) will result in a NSEC covering the
2015-09-18 02:53:27 -05:00
* qname, where qname > NSEC->name and ce(qname) is parent of NXT.
* This case is handled below after the covering NSEC is found.
2015-07-03 15:50:29 -05:00
*
* - Or a wildcard match without the type. The wildcard owner name
* match has special handing in the find_nsec_covering_name function.
* We still expect a NSEC covering the name though.
2015-07-03 15:50:29 -05:00
*/
2015-06-27 16:28:23 -05:00
/* The NSEC Name error case
* ========================
* - First find the NSEC that covers the owner name.
*/
for ( i = _getdns_rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len
, SECTION_NO_ADDITIONAL)
; i ; i = _getdns_rrset_iter_next(i)) {
cover = _getdns_rrset_iter_value(i);
2015-07-03 15:50:29 -05:00
if (/* Is cover an NSEC rrset? */
cover->rr_type != GETDNS_RRTYPE_NSEC
/* Does it cover the name */
2015-06-27 16:28:23 -05:00
|| !nsec_covers_name(cover, rrset->name, &ce_name)
2015-07-03 15:50:29 -05:00
/* But not a match (because that would be NODATA case) */
|| _dname_equal(cover->name, rrset->name)
/* Get the bitmap rdata field */
|| !(nsec_rr = _getdns_rrtype_iter_init(&nsec_spc, cover))
|| !(bitmap = _getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i, 1))
/* When qname is a subdomain of the NSEC owner, make
* sure there is no DNAME, and no delegation point
* there.
*/
|| ( _dname_is_parent(cover->name, rrset->name)
&& ( bitmap_has_type(bitmap, GETDNS_RRTYPE_DNAME)
|| ( bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
)
)
)
/* And a valid signature please (as always) */
|| !(keytag = a_key_signed_rrset(
mf, now, skew, keyset, cover)))
continue;
2015-07-03 15:50:29 -05:00
/* We could have found a NSEC covering an Empty Non Terminal.
* In that case no NSEC covering the wildcard is needed.
* Because it was actually a NODATA proof.
*
* Empty NON terminals can be identified, by
* qname > NSEC->name && NSEC->nxt is subdomain of qname.
*
* nsec_covers_name() will set ce_name to qname when NSEC->nxt
* is a subdomain of qname.
*/
if ( dname_compare(rrset->name, cover->name) > 0
&& dname_compare(rrset->name, ce_name) == 0) {
debug_sec_print_dname("Empty Non Terminal: ", ce_name);
return keytag;
}
2015-06-27 16:28:23 -05:00
debug_sec_print_dname("Closest Encloser: ", ce_name);
if (_dname_len(ce_name) > sizeof(wc_name) - 2)
return 0;
else
(void) memcpy(wc_name+2, ce_name, _dname_len(ce_name));
2015-06-27 16:28:23 -05:00
debug_sec_print_dname(" Wildcard: ", wc_name);
return find_nsec_covering_name(
mf, now, skew, keyset, rrset, wc_name, NULL);
}
/* The NSEC3 NODATA case
* =====================
* NSEC3 has same (hashed) ownername as the rrset to deny.
*/
for ( i = _getdns_rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len
, SECTION_NO_ADDITIONAL)
; i ; i = _getdns_rrset_iter_next(i)) {
/* ce is potentially the NSEC3 that matches complete qname
* (so is also the closest encloser)
*/
ce = _getdns_rrset_iter_value(i);
if ( ce->rr_type == GETDNS_RRTYPE_NSEC3
/* A NSEC3 RR exists at the owner name of rrset
* (this is always true)
*/
&& (nsec_rr = _getdns_rrtype_iter_init(&nsec_spc, ce))
/* Get the bitmap rdata field */
&& (bitmap = _getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i, 5))
/* At least the rr_type of rrset should be missing */
&& !bitmap_has_type(bitmap, rrset->rr_type)
/* If the name is a CNAME, then we should have gotten it,
* So no CNAME bit either.
*/
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_CNAME)
/* In case of a DS query, make sure we have the parent side
* NSEC and not the child (so no SOA).
* (except for the root...)
*/
&& ( rrset->rr_type != GETDNS_RRTYPE_DS
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|| *rrset->name == 0
)
/* If not a DS query, then make sure the NSEC does not
* contain NS, or if it does, then also contains SOA,
* otherwise we have a parent side delegation point NSEC
* where we should have gotten a child side NSEC!
*/
&& ( rrset->rr_type == GETDNS_RRTYPE_DS
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA))
/* It must have a valid signature */
&& (keytag = a_key_signed_rrset(mf, now, skew, keyset, ce))
/* The qname must match the NSEC3 */
&& ( keytag & NSEC3_ITERATION_COUNT_HIGH
|| nsec3_matches_name(ce, rrset->name))) {
debug_sec_print_rrset("NSEC3 No Data for: ", rrset);
return keytag;
}
}
/* More NSEC3 NODATA cases
* ======================
* There are a few NSEC NODATA cases where qname doesn't match
* NSEC->name:
*
* - NSEC3 ownername match for qtype == NSEC3 (TODO?)
* - Wildcard NODATA (wildcard owner name match has special handing
* find_nsec_covering_name())
*/
2015-06-27 16:28:23 -05:00
/* The NSEC3 Name error case
* ========================+
* First find the closest encloser.
*/
if (*rrset->name)
for ( nc_name = rrset->name, ce_name = rrset->name + *rrset->name + 1
; *ce_name ; nc_name = ce_name, ce_name += *ce_name + 1) {
for ( i = _getdns_rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len
, SECTION_NO_ADDITIONAL)
; i ; i = _getdns_rrset_iter_next(i)) {
if ( !(ce = _getdns_rrset_iter_value(i))
2016-04-29 05:00:17 -05:00
|| ce->rr_type != GETDNS_RRTYPE_NSEC3
/* Get the bitmap rdata field */
|| !(nsec_rr = _getdns_rrtype_iter_init(&nsec_spc, ce))
|| !(bitmap = _getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i, 1))
/* No DNAME or delegation point at the closest
* encloser.
*
* TODO: Ask Wouter
* Unbound val_nsec3:1024 finishes insecurely
* here (instead of bogus) when DS is also missing.
* Should we not have followed the delegation then
* too?
* The NSEC could come from a parent zone!
*
*/
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_DNAME)
|| ( bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
)
|| !(keytag = a_key_signed_rrset(
mf, now, skew, keyset, ce))
|| ( !(keytag & NSEC3_ITERATION_COUNT_HIGH)
&& !nsec3_matches_name(ce, ce_name)))
2015-06-27 16:28:23 -05:00
continue;
2015-06-27 16:28:23 -05:00
debug_sec_print_rrset("Closest Encloser: ", ce);
debug_sec_print_dname("Closest Encloser: ", ce_name);
debug_sec_print_dname(" Next closer: ", nc_name);
if ( keytag & NSEC3_ITERATION_COUNT_HIGH
|| (keytag = nsec3_find_next_closer(mf, now, skew,
keyset, rrset, nc_name, opt_out)))
return keytag;
}
}
return 0;
}
/* Ascend up to the root along chain_nodes. Try to find a keyset
* authenticated by a key in ta rrset (trust anchor). When we found one,
* descend back down, authenticating more specific keysets along the chain.
*
* The most specific keyset is returned in keys. Also a DNSSEC status is
* returned. BOGUS if no keyset could be found. INSECURE if the
* non-existence of a DS along the path is proofed, and SECURE otherwise.
*/
static int chain_node_get_trusted_keys(
struct mem_funcs *mf, time_t now, uint32_t skew,
chain_node *node, _getdns_rrset *ta, _getdns_rrset **keys)
{
int s, keytag;
/* Ascend up to the root */
if (! node)
return GETDNS_DNSSEC_BOGUS;
else if (ta->rr_type == GETDNS_RRTYPE_DS) {
if ((keytag = ds_authenticates_keys(
mf, now, skew, ta, &node->dnskey))) {
*keys = &node->dnskey;
node->dnskey_signer = keytag;
2015-07-07 17:10:10 -05:00
return keytag & NO_SUPPORTED_ALGORITHMS
? GETDNS_DNSSEC_INSECURE
: GETDNS_DNSSEC_SECURE;
}
} else if (ta->rr_type == GETDNS_RRTYPE_DNSKEY) {
/* ta is KSK */
if ((keytag = a_key_signed_rrset(
mf, now, skew, ta, &node->dnskey))) {
*keys = &node->dnskey;
node->dnskey_signer = keytag;
return GETDNS_DNSSEC_SECURE;
}
/* ta is the DNSKEY for this name? */
if (_dname_equal(ta->name, node->dnskey.name)) {
*keys = ta;
return GETDNS_DNSSEC_SECURE;
}
/* ta is parent's ZSK */
if ((keytag = key_proves_nonexistance(
mf, now, skew, ta, &node->ds, NULL))) {
node->ds_signer = keytag;
return GETDNS_DNSSEC_INSECURE;
}
if ((keytag = a_key_signed_rrset(mf,now,skew,ta,&node->ds))) {
node->ds_signer = keytag;
2015-07-07 17:10:10 -05:00
if ((keytag = ds_authenticates_keys(
mf, now, skew, &node->ds, &node->dnskey))) {
*keys = &node->dnskey;
node->dnskey_signer = keytag;
2015-07-07 17:10:10 -05:00
return keytag & NO_SUPPORTED_ALGORITHMS
? GETDNS_DNSSEC_INSECURE
: GETDNS_DNSSEC_SECURE;
}
return GETDNS_DNSSEC_BOGUS;
}
} else
return GETDNS_DNSSEC_BOGUS;
s = chain_node_get_trusted_keys(mf, now, skew, node->parent, ta, keys);
/* Set dnssec status on root DNSKEY request (for TA management) */
if (!node->parent && node->dnskey_req &&
node->dnskey.name && *node->dnskey.name == 0)
node->dnskey_req->dnssec_status = s;
if (s != GETDNS_DNSSEC_SECURE)
return s;
/* keys is an authenticated dnskey rrset always now (i.e. ZSK) */
ta = *keys;
/* Back down to the head */
if ((keytag = key_proves_nonexistance(
mf, now, skew, ta, &node->ds, NULL))) {
node->ds_signer = keytag;
return GETDNS_DNSSEC_INSECURE;
}
if (key_matches_signer(ta, &node->ds)) {
if ((node->ds_signer = a_key_signed_rrset(
mf, now, skew, ta, &node->ds))
&& (keytag = ds_authenticates_keys(
mf, now, skew, &node->ds, &node->dnskey))){
*keys = &node->dnskey;
node->dnskey_signer = keytag;
2015-07-07 17:10:10 -05:00
return keytag & NO_SUPPORTED_ALGORITHMS
? GETDNS_DNSSEC_INSECURE
: GETDNS_DNSSEC_SECURE;
}
return GETDNS_DNSSEC_BOGUS;
}
/* If we are on a zone cut, we must return BOGUS, because there should
* have been a more specific DS set. We can be sure of a zone cut if
* a request for the DSset was sent (because they are done only for
* signer names and when there was a SOA) or if we do have a DS,
* but not signed with a current trusted key.
*
* For the getdns_validate_dnssec case, we must make sure to insert
* an empty DS for this name in the validation chain... so it can
* be used for the support_records parameter.
*/
if (node->ds_req || _getdns_rrset_has_rrs(&node->ds))
return GETDNS_DNSSEC_BOGUS;
/* Not at a zone cut, the trusted keyset must be authenticating
* something below (closer to head) this node.
*/
return GETDNS_DNSSEC_SECURE;
}
/* The DNSSEC status of the rrset of head is evaluated with trust anchor ta.
* For this first a secure keyset is looked up, with which the keyset is
* evaluated.
*/
static int chain_head_validate_with_ta(struct mem_funcs *mf,
time_t now, uint32_t skew, chain_head *head, _getdns_rrset *ta)
{
_getdns_rrset *keys;
int s, keytag, opt_out;
debug_sec_print_rrset("validating ", &head->rrset);
debug_sec_print_rrset("with trust anchor ", ta);
if ((s = chain_node_get_trusted_keys(
mf, now, skew, head->parent, ta, &keys)) != GETDNS_DNSSEC_SECURE)
return s;
if (_getdns_rrset_has_rrs(&head->rrset)) {
if ((keytag = a_key_signed_rrset(
mf, now, skew, keys, &head->rrset))) {
head->signer = keytag;
return GETDNS_DNSSEC_SECURE;
} else if (!_getdns_rrset_has_rrsigs(&head->rrset)
&& (keytag = key_proves_nonexistance(mf, now,
skew, keys, &head->rrset, &opt_out))
&& opt_out) {
head->signer = keytag;
return GETDNS_DNSSEC_INSECURE;
}
} else if ((keytag = key_proves_nonexistance(mf, now, skew,
keys, &head->rrset, &opt_out))) {
head->signer = keytag;
return opt_out || (keytag & NSEC3_ITERATION_COUNT_HIGH)
? GETDNS_DNSSEC_INSECURE : GETDNS_DNSSEC_SECURE;
}
return GETDNS_DNSSEC_BOGUS;
}
/* The DNSSEC status of the rrset in head is evaluated by trying the trust
* anchors in tas in turn. The best outcome counts.
*/
static int chain_head_validate(struct mem_funcs *mf, time_t now, uint32_t skew,
chain_head *head, _getdns_rrset_iter *tas)
{
_getdns_rrset_iter *i;
_getdns_rrset *ta, dnskey_ta, ds_ta;
_getdns_rrset_iter closest_ta;
2015-07-07 17:10:10 -05:00
int closest_labels, s = GETDNS_DNSSEC_INDETERMINATE;
size_t ta_labels, supported_algorithms;
_getdns_rrtype_iter rr_spc, *rr;
2015-07-07 17:10:10 -05:00
/* Find the TA closest to the head's RRset name */
closest_labels = -1;
for (i = _getdns_rrset_iter_rewind(tas); i ;i = _getdns_rrset_iter_next(i)) {
ta = _getdns_rrset_iter_value(i);
2015-07-07 17:10:10 -05:00
if ((ta->rr_type == GETDNS_RRTYPE_DNSKEY ||
ta->rr_type == GETDNS_RRTYPE_DS)
&& _dname_is_parent(ta->name, head->rrset.name)
&& (int)(ta_labels = _dname_label_count(ta->name))
> closest_labels ) {
2015-07-07 17:10:10 -05:00
closest_labels = (int)ta_labels;
2015-07-07 17:10:10 -05:00
closest_ta = *i;
if (i->rrset.name == i->name_spc)
closest_ta.rrset.name = closest_ta.name_spc;
}
}
DEBUG_SEC("closest labels for TA: %d\n", closest_labels);
2015-07-07 17:10:10 -05:00
if (closest_labels == -1)
return GETDNS_DNSSEC_INDETERMINATE;
ta = _getdns_rrset_iter_value(&closest_ta);
2015-07-07 17:10:10 -05:00
dnskey_ta = *ta;
dnskey_ta.rr_type = GETDNS_RRTYPE_DNSKEY;
ds_ta = *ta;
ds_ta.rr_type = GETDNS_RRTYPE_DS;
if (!_getdns_rrset_has_rrs(&dnskey_ta))
return chain_head_validate_with_ta(mf,now,skew,head,&ds_ta);
2015-07-07 17:10:10 -05:00
/* Does the selected DNSKEY set have supported algorithms? */
supported_algorithms = 0;
for ( rr = _getdns_rrtype_iter_init(&rr_spc, ta)
; rr; rr = _getdns_rrtype_iter_next(rr)) {
2015-07-07 17:10:10 -05:00
if ( rr->rr_i.rr_type + 14 <= rr->rr_i.nxt
&& _getdns_dnskey_algo_id_is_supported(
rr->rr_i.rr_type[13]))
2015-07-07 17:10:10 -05:00
supported_algorithms++;
}
if (!supported_algorithms) {
if (_getdns_rrset_has_rrs(&ds_ta))
return chain_head_validate_with_ta(
mf, now, skew, head, &ds_ta);
2015-07-07 17:10:10 -05:00
return GETDNS_DNSSEC_INSECURE;
}
s = chain_head_validate_with_ta(mf, now, skew, head, &dnskey_ta);
if (_getdns_rrset_has_rrs(&ds_ta)) {
switch (chain_head_validate_with_ta(mf,now,skew,head,&ds_ta)) {
case GETDNS_DNSSEC_SECURE : s = GETDNS_DNSSEC_SECURE;
/* fallthrough */
case GETDNS_DNSSEC_INSECURE: if (s != GETDNS_DNSSEC_SECURE)
s = GETDNS_DNSSEC_INSECURE;
2015-07-07 17:10:10 -05:00
break;
case GETDNS_DNSSEC_BOGUS : if (s != GETDNS_DNSSEC_SECURE &&
2015-07-07 17:10:10 -05:00
s != GETDNS_DNSSEC_INSECURE)
s = GETDNS_DNSSEC_BOGUS;
break;
default : break;
}
}
return s;
}
/* The DNSSEC status of the network requests which constructed the chain is
* evaluated by processing each head in turn. The worst outcome per network request
* is the dnssec status for that network request.
*/
#ifdef STUB_NATIVE_DNSSEC
static void chain_set_netreq_dnssec_status(chain_head *chain, _getdns_rrset_iter *tas)
{
chain_head *head;
/* The netreq status is the worst for any head */
for (head = chain; head; head = head->next) {
if (!head->netreq)
continue;
switch (chain_head_validate(priv_getdns_context_mf(
head->netreq->owner->context), time(NULL),
head->netreq->owner->context->dnssec_allowed_skew,
head, tas)) {
case GETDNS_DNSSEC_SECURE:
if (head->netreq->dnssec_status ==
GETDNS_DNSSEC_INDETERMINATE)
head->netreq->dnssec_status =
GETDNS_DNSSEC_SECURE;
break;
case GETDNS_DNSSEC_INSECURE:
if (head->netreq->dnssec_status != GETDNS_DNSSEC_BOGUS)
head->netreq->dnssec_status =
GETDNS_DNSSEC_INSECURE;
break;
case GETDNS_DNSSEC_BOGUS :
head->netreq->dnssec_status = GETDNS_DNSSEC_BOGUS;
break;
default:
break;
}
}
}
static void chain_clear_netreq_dnssec_status(chain_head *chain)
{
chain_head *head;
size_t node_count;
chain_node *node;
/* The netreq status is the worst for any head */
for (head = chain; head; head = head->next) {
if (!head->netreq)
continue;
head->netreq->dnssec_status = GETDNS_DNSSEC_INDETERMINATE;
for ( node_count = head->node_count, node = head->parent
2017-09-20 05:44:14 -05:00
; node && node_count ; node_count--, node = node->parent ) {
node->ds_signer = -1;
node->dnskey_signer = -1;
if ( ! node->parent && node->dnskey_req
&& node->dnskey.name && !*node->dnskey.name) {
node->dnskey_req->dnssec_status =
GETDNS_DNSSEC_INDETERMINATE;
}
}
}
}
#endif
/* The DNSSEC status of all heads for a chain structure is evaluated by
* processing each head in turn. The worst outcome is the dnssec status for
* the whole.
*/
static int chain_validate_dnssec(struct mem_funcs *mf,
time_t now, uint32_t skew, chain_head *chain, _getdns_rrset_iter *tas)
{
int s = GETDNS_DNSSEC_INDETERMINATE, t;
chain_head *head;
2015-06-29 15:23:01 -05:00
/* The netreq status is the worst for any head */
for (head = chain; head; head = head->next) {
t = chain_head_validate(mf, now, skew, head, tas);
switch (t) {
2015-06-29 15:23:01 -05:00
case GETDNS_DNSSEC_SECURE:
if (s == GETDNS_DNSSEC_INDETERMINATE)
s = GETDNS_DNSSEC_SECURE;
2015-06-29 15:23:01 -05:00
break;
case GETDNS_DNSSEC_INSECURE:
if (s != GETDNS_DNSSEC_BOGUS)
s = GETDNS_DNSSEC_INSECURE;
2015-06-29 15:23:01 -05:00
break;
case GETDNS_DNSSEC_BOGUS :
s = GETDNS_DNSSEC_BOGUS;
2015-06-29 15:23:01 -05:00
break;
default:
break;
}
}
DEBUG_SEC("chain_validate_dnssec() returning %d\n", s);
return s;
}
/**************** dnssec_return_validation_chain Extension ******************
*****************************************************************************/
static size_t count_outstanding_requests(chain_head *head)
2015-06-19 11:02:16 -05:00
{
size_t count;
chain_node *node;
2015-06-19 11:02:16 -05:00
if (!head)
return 0;
2015-06-19 11:02:16 -05:00
for ( node = head->parent, count = head->lock
; node
; node = node->parent) {
2015-06-19 11:02:16 -05:00
count += node->lock;
if (!_getdns_netreq_finished(node->dnskey_req))
count++;
2015-06-19 11:02:16 -05:00
if (!_getdns_netreq_finished(node->ds_req))
count++;
2015-06-19 11:02:16 -05:00
if (!_getdns_netreq_finished(node->soa_req))
count++;
2015-06-19 11:02:16 -05:00
}
return count + count_outstanding_requests(head->next);
2015-06-19 11:02:16 -05:00
}
static int rrset_in_list(_getdns_rrset *rrset, getdns_list *list)
{
size_t i;
getdns_dict *rr_dict;
uint32_t rr_type;
uint32_t rr_class;
getdns_bindata *name;
for (i = 0; !getdns_list_get_dict(list, i, &rr_dict); i++) {
if (!getdns_dict_get_int(rr_dict, "type", &rr_type) &&
rrset->rr_type == rr_type &&
!getdns_dict_get_int(rr_dict, "class", &rr_class) &&
rrset->rr_class == rr_class &&
!getdns_dict_get_bindata(rr_dict, "name", &name) &&
dname_compare(rrset->name, name->data) == 0)
return 1;
}
return 0;
}
static void append_rrset2val_chain_list(
getdns_list *val_chain_list, _getdns_rrset *rrset, int signer)
{
_getdns_rr_iter val_rrset_spc[VAL_RRSET_SPC_SZ];
_getdns_rr_iter *val_rrset = val_rrset_spc;
_getdns_rrtype_iter rr_spc, *rr;
size_t n_rrs, i;
uint32_t orig_ttl;
getdns_dict *rr_dict;
_getdns_rrsig_iter *rrsig, rrsig_spc;
assert(val_chain_list && rrset);
if (signer < 0)
return;
for ( rrsig = _getdns_rrsig_iter_init(&rrsig_spc, rrset)
; rrsig &&
( rrsig->rr_i.nxt < rrsig->rr_i.rr_type + 28
|| gldns_read_uint16(rrsig->rr_i.rr_type + 26)
!= (signer & 0xFFFF))
; rrsig = _getdns_rrsig_iter_next(rrsig))
; /* pass */
if (!rrsig)
return;
/* keytag was already read, so orig_ttl should cause no problem */
assert(rrsig->rr_i.nxt >= rrsig->rr_i.rr_type + 18);
orig_ttl = gldns_read_uint32(rrsig->rr_i.rr_type + 14);
for (;;) {
for ( rr = _getdns_rrtype_iter_init(&rr_spc, rrset), n_rrs = 0
; rr
; rr = _getdns_rrtype_iter_next(rr), n_rrs++) {
if (n_rrs < VAL_RRSET_SPC_SZ ||
val_rrset != val_rrset_spc)
val_rrset[n_rrs] = rr->rr_i;
}
/* Did everything fit? Then break */
if (val_rrset != val_rrset_spc || n_rrs <= VAL_RRSET_SPC_SZ)
break;
/* More space needed for val_rrset */
val_rrset = GETDNS_XMALLOC(
val_chain_list->mf, _getdns_rr_iter, n_rrs);
}
qsort(val_rrset, n_rrs, sizeof(_getdns_rr_iter), _rr_iter_rdata_cmp);
for (i = 0; i < n_rrs; i++) {
/* Get rid of doubles */
if (i && !_rr_iter_rdata_cmp(&val_rrset[i], &val_rrset[i-1]))
continue;
if (!(rr_dict = _getdns_rr_iter2rr_dict_canonical(
&val_chain_list->mf, &val_rrset[i], &orig_ttl)))
continue;
if (_getdns_list_append_this_dict(val_chain_list, rr_dict))
getdns_dict_destroy(rr_dict);
}
if ((rr_dict = _getdns_rr_iter2rr_dict_canonical(
&val_chain_list->mf, &rrsig->rr_i, &orig_ttl)) &&
_getdns_list_append_this_dict(val_chain_list, rr_dict))
getdns_dict_destroy(rr_dict);
if (val_rrset != val_rrset_spc)
GETDNS_FREE(val_chain_list->mf, val_rrset);
}
static void append_rrs2val_chain_list(getdns_context *ctxt,
getdns_list *val_chain_list, getdns_network_req *netreq, int signer)
2015-06-19 11:02:16 -05:00
{
_getdns_rrset_iter *i, i_spc;
_getdns_rrset *rrset;
_getdns_rrtype_iter *rr, rr_spc;
_getdns_rrsig_iter *rrsig, rrsig_spc;
getdns_dict *rr_dict;
2015-06-19 11:02:16 -05:00
for ( i = _getdns_rrset_iter_init(&i_spc, netreq->response
, netreq->response_len
, SECTION_NO_ADDITIONAL)
; i
; i = _getdns_rrset_iter_next(i)) {
rrset = _getdns_rrset_iter_value(i);
2015-06-19 11:02:16 -05:00
if (rrset->rr_type == GETDNS_RRTYPE_NSEC ||
rrset->rr_type == GETDNS_RRTYPE_NSEC3) {
if (rrset_in_list(rrset, val_chain_list))
continue;
} else if (rrset->rr_type != GETDNS_RRTYPE_DNSKEY &&
rrset->rr_type != GETDNS_RRTYPE_DS)
continue;
2015-06-19 11:02:16 -05:00
if (signer > 0) {
/* We have a signer! Return RRset in canonical
* form and order with only the RRSIG that signed
* the RRset.
*/
append_rrset2val_chain_list(
val_chain_list, rrset, signer);
continue;
}
for ( rr = _getdns_rrtype_iter_init(&rr_spc, rrset)
; rr; rr = _getdns_rrtype_iter_next(rr)) {
if (!(rr_dict = _getdns_rr_iter2rr_dict(
2015-06-30 07:43:52 -05:00
&ctxt->mf, &rr->rr_i)))
continue;
2016-03-21 08:56:09 -05:00
if (_getdns_list_append_this_dict(val_chain_list, rr_dict))
getdns_dict_destroy(rr_dict);
}
for ( rrsig = _getdns_rrsig_iter_init(&rrsig_spc, rrset)
; rrsig; rrsig = _getdns_rrsig_iter_next(rrsig)) {
if (!(rr_dict = _getdns_rr_iter2rr_dict(
&ctxt->mf, &rrsig->rr_i)))
2015-06-30 07:43:52 -05:00
continue;
2016-03-21 08:56:09 -05:00
if (_getdns_list_append_this_dict(val_chain_list, rr_dict))
getdns_dict_destroy(rr_dict);
}
}
}
static void append_empty_ds2val_chain_list(
getdns_context *context, getdns_list *val_chain_list, _getdns_rrset *ds)
{
getdns_dict *rr_dict;
getdns_bindata bindata;
getdns_dict *rdata_dict;
if (!(rr_dict = getdns_dict_create_with_context(context)))
return;
bindata.size = _dname_len(ds->name);
bindata.data = (UNCONST_UINT8_p)ds->name;
(void) getdns_dict_set_bindata(rr_dict, "name", &bindata);
(void) getdns_dict_set_int(rr_dict, "class", ds->rr_class);
(void) getdns_dict_set_int(rr_dict, "type", ds->rr_type);
(void) getdns_dict_set_int(rr_dict, "ttl", 0);
if (!(rdata_dict = getdns_dict_create_with_context(context))) {
getdns_dict_destroy(rr_dict);
return;
}
bindata.size = 0;
bindata.data = NULL;
(void) getdns_dict_set_bindata(rdata_dict, "rdata_raw", &bindata);
getdns_dict_destroy(rdata_dict);
2016-03-21 08:56:09 -05:00
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;
}
int _getdns_bogus(getdns_dns_req *dnsreq)
{
getdns_network_req **netreq_p, *netreq;
for (netreq_p = dnsreq->netreqs; (netreq = *netreq_p) ; netreq_p++) {
if (netreq->dnssec_status == GETDNS_DNSSEC_BOGUS)
return 1;
}
return 0;
}
static void check_chain_complete(chain_head *chain)
{
getdns_dns_req *dnsreq;
getdns_context *context;
size_t o, node_count;
chain_head *head, *next, *same_chain;
chain_node *node;
getdns_list *val_chain_list;
getdns_dict *response_dict;
_getdns_rrset_iter tas_iter;
if ((o = count_outstanding_requests(chain)) > 0) {
2017-09-27 06:15:12 -05:00
DEBUG_SEC("%"PRIsz" outstanding requests\n", o);
return;
}
DEBUG_SEC("Chain done!\n");
dnsreq = chain->netreq->owner;
context = dnsreq->context;
if (dnsreq->waiting_for_ta) {
getdns_dns_req **d;
for (d = &context->ta_notify; *d; d = &(*d)->ta_notify) {
if (*d == dnsreq) {
*d = dnsreq->ta_notify;
dnsreq->ta_notify = NULL;
break;
}
}
} else {
if (context->trust_anchors_source == GETDNS_TASRC_FETCHING) {
dnsreq->waiting_for_ta = 1;
dnsreq->ta_notify = context->ta_notify;
context->ta_notify = dnsreq;
return;
}
}
#ifdef STUB_NATIVE_DNSSEC
if (context->trust_anchors)
chain_set_netreq_dnssec_status(chain,_getdns_rrset_iter_init(&tas_iter,
context->trust_anchors, context->trust_anchors_len,
SECTION_ANSWER));
#else
if (context->trust_anchors)
(void) chain_validate_dnssec(priv_getdns_context_mf(context),
time(NULL), context->dnssec_allowed_skew,
chain, _getdns_rrset_iter_init( &tas_iter
, context->trust_anchors
, context->trust_anchors_len
, SECTION_ANSWER));
2015-10-31 07:04:08 -05:00
#endif
if (context->trust_anchors_source == GETDNS_TASRC_XML) {
if ((head = chain) && (node = _to_the_root(head->parent)) &&
node->dnskey.name && *node->dnskey.name == 0)
_getdns_context_update_root_ksk(context,&node->dnskey);
} else if (_getdns_bogus(dnsreq)) {
DEBUG_ANCHOR("Request was bogus!\n");
if ((head = chain) && (node = _to_the_root(head->parent))
&& node->dnskey.name && *node->dnskey.name == 0
&& node->dnskey_req->dnssec_status == GETDNS_DNSSEC_BOGUS){
DEBUG_ANCHOR("root DNSKEY set was bogus!\n");
if (!dnsreq->waiting_for_ta) {
uint64_t now = 0;
dnsreq->waiting_for_ta = 1;
_getdns_context_equip_with_anchor(
context, &now);
if (context->trust_anchors_source
== GETDNS_TASRC_XML) {
chain_clear_netreq_dnssec_status(chain);
check_chain_complete(chain);
return;
}
_getdns_start_fetching_ta(
context, dnsreq->loop);
if (dnsreq->waiting_for_ta &&
context->trust_anchors_source
== GETDNS_TASRC_FETCHING) {
chain_clear_netreq_dnssec_status(chain);
dnsreq->ta_notify = context->ta_notify;
context->ta_notify = dnsreq;
return;
}
}
}
}
2015-10-31 07:04:08 -05:00
#ifdef DNSSEC_ROADBLOCK_AVOIDANCE
if ( dnsreq->dnssec_roadblock_avoidance
&& !dnsreq->avoid_dnssec_roadblocks
&& _getdns_bogus(dnsreq)) {
2015-10-31 07:04:08 -05:00
getdns_network_req **netreq_p, *netreq;
uint64_t now_ms = 0;
2015-10-31 07:04:08 -05:00
dnsreq->avoid_dnssec_roadblocks = 1;
dnsreq->chain->lock += 1;
2015-10-31 07:04:08 -05:00
for ( netreq_p = dnsreq->netreqs
; (netreq = *netreq_p)
2015-10-31 07:04:08 -05:00
; netreq_p++) {
2017-03-14 11:17:56 -05:00
_getdns_netreq_change_state(netreq, NET_REQ_NOT_SENT);
netreq->dnssec_status =
GETDNS_DNSSEC_INDETERMINATE;
netreq->owner = dnsreq;
2017-07-15 11:38:31 -05:00
(void) _getdns_submit_netreq(netreq, &now_ms);
2015-10-31 07:04:08 -05:00
}
if (!dnsreq->dnssec_return_validation_chain)
return;
for ( head = chain; head ; head = next ) {
next = head->next;
for ( node_count = head->node_count
, node = head->parent
; node_count
; node_count--, node = node->parent ) {
if (node->dnskey_req) {
_getdns_netreq_change_state(
node->dnskey_req,
NET_REQ_NOT_SENT);
node->dnskey_req->owner->
avoid_dnssec_roadblocks = 1;
2017-07-15 11:38:31 -05:00
(void) _getdns_submit_netreq(
node->dnskey_req, &now_ms);
}
if (node->ds_req) {
_getdns_netreq_change_state(
node->ds_req, NET_REQ_NOT_SENT);
node->ds_req->owner->
avoid_dnssec_roadblocks = 1;
2017-07-15 11:38:31 -05:00
(void) _getdns_submit_netreq(
node->ds_req, &now_ms);
}
}
2015-10-31 07:04:08 -05:00
}
return;
}
#endif
dnsreq->waiting_for_ta = 0;
val_chain_list = dnsreq->dnssec_return_validation_chain
? getdns_list_create_with_context(context) : NULL;
/* Walk chain to add values to val_chain_list. We do not cleanup yet.
* The chain will eventually be freed when the dns request is descheduled
* with getdns_context_clear_outbound_request().
*/
for ( head = chain; head ; head = next ) {
next = head->next;
if (dnsreq->dnssec_return_full_validation_chain &&
head->node_count && head->signer > 0) {
append_rrset2val_chain_list(
val_chain_list, &head->rrset, head->signer);
for ( same_chain = next
; same_chain && same_chain->signer == head->signer
; same_chain = same_chain->next) {
append_rrset2val_chain_list(val_chain_list,
&same_chain->rrset, same_chain->signer);
same_chain->signer = -1;
}
}
for ( node_count = head->node_count, node = head->parent
; node_count
; node_count--, node = node->parent ) {
if (node->dnskey_req) {
if (val_chain_list)
append_rrs2val_chain_list(
context, val_chain_list,
node->dnskey_req,
node->dnskey_signer);
}
if (node->ds_req) {
if (val_chain_list)
append_rrs2val_chain_list(
context, val_chain_list,
node->ds_req, node->ds_signer);
if (val_chain_list && node->ds_signer == -1 &&
!_getdns_rrset_has_rrs(&node->ds)) {
/* Add empty DS, to prevent less
* specific to be able to authenticate
* below a zone cut (closer to head)
*/
append_empty_ds2val_chain_list(
context, val_chain_list,
&node->ds);
}
}
}
}
response_dict = _getdns_create_getdns_response(dnsreq);
if (val_chain_list) {
2016-03-21 06:50:43 -05:00
if (_getdns_dict_set_this_list(
response_dict, "validation_chain", val_chain_list))
getdns_list_destroy(val_chain_list);
}
/* Final user callback */
dnsreq->validating = 0;
_getdns_call_user_callback(dnsreq, response_dict);
}
void _getdns_ta_notify_dnsreqs(getdns_context *context)
{
getdns_dns_req **dnsreq_p, *dnsreq = NULL;
uint64_t now_ms = 0;
assert(context);
if (context->trust_anchors_source == GETDNS_TASRC_NONE ||
context->trust_anchors_source == GETDNS_TASRC_FETCHING)
return;
dnsreq_p = &context->ta_notify;
while ((dnsreq = *dnsreq_p)) {
assert(dnsreq->waiting_for_ta);
if (dnsreq->chain)
check_chain_complete(dnsreq->chain);
else {
getdns_network_req *netreq, **netreq_p;
int r = GETDNS_RETURN_GOOD;
2017-09-28 15:17:36 -05:00
(void) _getdns_context_prepare_for_resolution(context);
*dnsreq_p = dnsreq->ta_notify;
for ( netreq_p = dnsreq->netreqs
; !r && (netreq = *netreq_p)
; netreq_p++ ) {
if (!(r = _getdns_submit_netreq(netreq, &now_ms)))
continue;
if (r == DNS_REQ_FINISHED)
break;
_getdns_netreq_change_state(netreq, NET_REQ_ERRORED);
}
}
assert(*dnsreq_p != dnsreq);
}
}
void _getdns_validation_chain_timeout(getdns_dns_req *dnsreq)
{
chain_head *head = dnsreq->chain, *next;
chain_node *node;
size_t node_count;
while (head) {
next = head->next;
for ( node_count = head->node_count, node = head->parent
; node_count
; node_count--, node = node->parent ) {
if (!_getdns_netreq_finished(node->dnskey_req)) {
_getdns_context_cancel_request(
node->dnskey_req->owner);
node->dnskey_req = NULL;
}
if (!_getdns_netreq_finished(node->ds_req)) {
_getdns_context_cancel_request(
node->ds_req->owner);
node->ds_req = NULL;
}
if (!_getdns_netreq_finished(node->soa_req)) {
_getdns_context_cancel_request(
node->soa_req->owner);
node->soa_req = NULL;
}
}
head = next;
}
dnsreq->request_timed_out = 1;
check_chain_complete(dnsreq->chain);
}
void _getdns_cancel_validation_chain(getdns_dns_req *dnsreq)
{
chain_head *head = dnsreq->chain, *next, *dnskey_head;
chain_node *node;
size_t node_count;
dnsreq->chain = NULL;
while (head) {
next = head->next;
for ( node_count = head->node_count, node = head->parent
; node_count
; node_count--, node = node->parent ) {
if (node->dnskey_req &&
!( (dnskey_head = _dnskey_query(node))
&& dnskey_head->netreq == node->dnskey_req))
_getdns_context_cancel_request(
node->dnskey_req->owner);
if (node->ds_req)
_getdns_context_cancel_request(
node->ds_req->owner);
if (node->soa_req)
_getdns_context_cancel_request(
node->soa_req->owner);
}
GETDNS_FREE(head->my_mf, head);
head = next;
}
}
void _getdns_get_validation_chain(getdns_dns_req *dnsreq)
{
getdns_network_req *netreq, **netreq_p;
chain_head *chain = NULL, *chain_p;
if (dnsreq->avoid_dnssec_roadblocks) {
chain = dnsreq->chain;
} else if (dnsreq->validating)
return;
dnsreq->validating = 1;
if (dnsreq->avoid_dnssec_roadblocks && chain->lock == 0)
; /* pass */
else for (netreq_p = dnsreq->netreqs; (netreq = *netreq_p) ; netreq_p++) {
2015-07-02 05:59:28 -05:00
if (! netreq->response
|| netreq->response_len < GLDNS_HEADER_SIZE
|| ( GLDNS_RCODE_WIRE(netreq->response)
!= GETDNS_RCODE_NOERROR &&
GLDNS_RCODE_WIRE(netreq->response)
!= GETDNS_RCODE_NXDOMAIN) ) {
netreq->dnssec_status = GETDNS_DNSSEC_INSECURE;
continue;
} else if (netreq->unbound_id != -1)
netreq->dnssec_status = GETDNS_DNSSEC_INDETERMINATE;
add_pkt2val_chain( &dnsreq->my_mf, &chain
, netreq->response, netreq->response_len
, netreq
);
add_question2val_chain( &dnsreq->my_mf, &chain
, netreq->response, netreq->response_len
, netreq->owner->name
, netreq->request_type
2015-12-29 10:34:14 -06:00
, netreq->owner->request_class
, netreq
);
}
if (chain) {
2016-06-07 10:21:10 -05:00
for (chain_p = chain; chain_p; chain_p = chain_p->next) {
if (chain_p->lock) chain_p->lock--;
}
dnsreq->chain = chain;
if (dnsreq->avoid_dnssec_roadblocks && chain->lock)
chain->lock -= 1;
check_chain_complete(chain);
} else {
dnsreq->validating = 0;
_getdns_call_user_callback(dnsreq,
_getdns_create_getdns_response(dnsreq));
}
}
/******************* getdns_validate_dnssec() Function *********************
*****************************************************************************/
static int wire_validate_dnssec(struct mem_funcs *mf,
time_t now, uint32_t skew, uint8_t *to_val, size_t to_val_len,
uint8_t *support, size_t support_len, uint8_t *tas, size_t tas_len)
{
chain_head *chain, *head, *next_head;
chain_node *node;
uint8_t qname_spc[256];
const uint8_t *qname = NULL;
size_t qname_len = sizeof(qname_spc);
uint16_t qtype = 0, qclass = GETDNS_RRCLASS_IN;
_getdns_rr_iter rr_spc, *rr;
_getdns_rrset_iter tas_iter;
int s;
if (to_val_len < GLDNS_HEADER_SIZE)
return GETDNS_RETURN_GENERIC_ERROR;
#if defined(SEC_DEBUG) && SEC_DEBUG
char *str = gldns_wire2str_pkt(to_val, to_val_len);
DEBUG_SEC("to validate: %s\n", str);
free(str);
#endif
if (GLDNS_RCODE_WIRE(to_val) != GETDNS_RCODE_NOERROR &&
GLDNS_RCODE_WIRE(to_val) != GETDNS_RCODE_NXDOMAIN)
return GETDNS_DNSSEC_INSECURE;
if (GLDNS_QDCOUNT(to_val) == 0 && GLDNS_ANCOUNT(to_val) == 0)
return GETDNS_RETURN_GENERIC_ERROR;
chain = NULL;
/* First create a chain (head + nodes) for each rr in the answer and
* authority section of the fake to_val packet.
*/
add_pkt2val_chain(mf, &chain, to_val, to_val_len, NULL);
/* For each question in the question section add a chain head.
*/
if ( (rr = _getdns_rr_iter_init(&rr_spc, to_val, to_val_len))
&& _getdns_rr_iter_section(rr) == SECTION_QUESTION
&& (qname = _getdns_owner_if_or_as_decompressed(
rr, qname_spc, &qname_len))
&& rr->nxt >= rr->rr_type + 4) {
qtype = gldns_read_uint16(rr->rr_type);
qclass = gldns_read_uint16(rr->rr_type + 2);
add_question2val_chain(mf, &chain, to_val, to_val_len,
qname, qtype, qclass, NULL);
}
/* Now equip the nodes with the support records wireformat */
for (head = chain; head; head = head->next) {
for (node = head->parent; node; node = node->parent) {
node->dnskey.pkt = support;
node->dnskey.pkt_len = support_len;
node->ds.pkt = support;
node->ds.pkt_len = support_len;
}
}
s = chain_validate_dnssec(mf, now, skew, chain,
_getdns_rrset_iter_init(
&tas_iter, tas, tas_len, SECTION_ANSWER));
/* Cleanup the chain */
for (head = chain; head; head = next_head) {
next_head = head->next;
GETDNS_FREE(*mf, head);
}
return s;
}
/*
* getdns_validate_dnssec
*
*/
getdns_return_t
getdns_validate_dnssec2(getdns_list *records_to_validate,
getdns_list *support_records,
getdns_list *trust_anchors,
time_t now, uint32_t skew)
2014-02-19 09:56:00 -06:00
{
uint8_t to_val_buf[4096], *to_val,
support_buf[4096], *support,
tas_buf[4096], *tas;
2014-02-19 09:56:00 -06:00
size_t to_val_len = sizeof(to_val_buf),
support_len = sizeof(support_buf),
tas_len = sizeof(tas_buf);
2014-02-19 09:56:00 -06:00
int r = GETDNS_RETURN_MEMORY_ERROR;
struct mem_funcs *mf;
size_t i;
getdns_dict *reply;
#if defined(SEC_DEBUG) && SEC_DEBUG
fflush(stdout);
2015-03-18 15:43:41 -05:00
#endif
if (!records_to_validate || !trust_anchors)
return GETDNS_RETURN_INVALID_PARAMETER;
mf = &records_to_validate->mf;
2014-02-19 09:56:00 -06:00
/* First convert everything to wire format
*/
if (!support_records)
(void) memset((support = support_buf), 0, GLDNS_HEADER_SIZE);
else if (!(support = _getdns_list2wire(support_records,
support_buf, &support_len, mf)))
return GETDNS_RETURN_MEMORY_ERROR;
2014-02-19 09:56:00 -06:00
if (!(tas = _getdns_list2wire(trust_anchors,
tas_buf, &tas_len, mf)))
goto exit_free_support;
2014-02-19 09:56:00 -06:00
if (!(to_val = _getdns_list2wire(records_to_validate,
to_val_buf, &to_val_len, mf)))
goto exit_free_tas;
if ((r = wire_validate_dnssec(mf, now, skew, to_val, to_val_len,
support,support_len, tas,tas_len)) != GETDNS_RETURN_GENERIC_ERROR)
goto exit_free_to_val;
for (i = 0; !getdns_list_get_dict(records_to_validate,i,&reply); i++) {
2014-02-19 09:56:00 -06:00
2017-09-27 06:15:12 -05:00
DEBUG_SEC("REPLY %"PRIsz", r: %d\n", i, r);
if (to_val != to_val_buf)
GETDNS_FREE(*mf, to_val);
to_val_len = sizeof(to_val_buf);
if (!(to_val = _getdns_reply2wire(
reply, to_val_buf, &to_val_len, mf)))
continue;
2014-02-19 09:56:00 -06:00
r = GETDNS_DNSSEC_INDETERMINATE;
switch (wire_validate_dnssec(mf, now, skew,
to_val, to_val_len, support, support_len, tas, tas_len)) {
case GETDNS_DNSSEC_SECURE:
if (r == GETDNS_DNSSEC_INDETERMINATE)
r = GETDNS_DNSSEC_SECURE;
break;
case GETDNS_DNSSEC_INSECURE:
if (r != GETDNS_DNSSEC_BOGUS)
r = GETDNS_DNSSEC_INSECURE;
break;
case GETDNS_DNSSEC_BOGUS:
r = GETDNS_DNSSEC_BOGUS;
break;
default:
break;
2014-02-19 09:56:00 -06:00
}
}
2017-09-27 06:15:12 -05:00
DEBUG_SEC("REPLY %"PRIsz", r: %d\n", i, r);
2014-02-19 09:56:00 -06:00
exit_free_to_val:
if (to_val != to_val_buf)
GETDNS_FREE(*mf, to_val);
exit_free_tas:
if (tas != tas_buf)
GETDNS_FREE(*mf, tas);
exit_free_support:
if (support != support_buf)
GETDNS_FREE(*mf, support);
2014-02-19 09:56:00 -06:00
return r;
}
2014-02-19 09:56:00 -06:00
getdns_return_t
getdns_validate_dnssec(getdns_list *records_to_validate,
getdns_list *support_records,
getdns_list *trust_anchors)
{
return getdns_validate_dnssec2(records_to_validate, support_records,
trust_anchors, time(NULL), 0);
}
/****************** getdns_root_trust_anchor() Function ********************
*****************************************************************************/
2015-06-30 07:43:52 -05:00
uint16_t
_getdns_parse_ta_file(time_t *ta_mtime, gldns_buffer *gbuf)
2014-02-19 09:56:00 -06:00
{
2015-03-15 15:25:38 -05:00
struct gldns_file_parse_state pst;
2014-02-19 09:56:00 -06:00
struct stat st;
2015-06-30 07:43:52 -05:00
uint8_t rr[8192]; /* Reasonable size for a single DNSKEY or DS RR */
2015-03-15 15:25:38 -05:00
size_t len, dname_len;
2014-02-19 09:56:00 -06:00
FILE *in;
2015-06-30 07:43:52 -05:00
uint16_t ta_count = 0;
size_t pkt_start;
2014-02-19 09:56:00 -06:00
if (stat(TRUST_ANCHOR_FILE, &st) != 0)
return 0;
if (ta_mtime)
*ta_mtime = st.st_mtime;
2015-03-15 15:25:38 -05:00
if (!(in = fopen(TRUST_ANCHOR_FILE, "r")))
2014-02-19 09:56:00 -06:00
return 0;
2015-03-15 15:25:38 -05:00
memset(&pst, 0, sizeof(pst));
pst.default_ttl = 3600;
pst.lineno = 1;
2014-02-19 09:56:00 -06:00
2015-06-30 07:43:52 -05:00
pkt_start = gldns_buffer_position(gbuf);
/* Empty header */
gldns_buffer_write_u32(gbuf, 0);
gldns_buffer_write_u32(gbuf, 0);
gldns_buffer_write_u32(gbuf, 0);
2015-03-15 15:25:38 -05:00
while (!feof(in)) {
2015-06-30 07:43:52 -05:00
len = sizeof(rr);
2015-03-15 15:25:38 -05:00
dname_len = 0;
2015-06-30 07:43:52 -05:00
if (gldns_fp2wire_rr_buf(in, rr, &len, &dname_len, &pst))
2015-03-15 15:25:38 -05:00
break;
if (len == 0) /* empty, $TTL, $ORIGIN */
continue;
2015-06-30 07:43:52 -05:00
if (gldns_wirerr_get_type(rr, len, dname_len)
!= GLDNS_RR_TYPE_DS &&
2015-06-30 07:43:52 -05:00
gldns_wirerr_get_type(rr, len, dname_len)
!= GLDNS_RR_TYPE_DNSKEY)
2015-03-15 15:25:38 -05:00
continue;
2015-06-30 07:43:52 -05:00
gldns_buffer_write(gbuf, rr, len);
2015-03-15 15:25:38 -05:00
ta_count++;
2014-02-19 09:56:00 -06:00
}
fclose(in);
2015-06-30 07:43:52 -05:00
gldns_buffer_write_u16_at(gbuf, pkt_start+GLDNS_ANCOUNT_OFF, ta_count);
2015-03-15 15:25:38 -05:00
return ta_count;
2014-02-19 09:56:00 -06:00
}
getdns_list *
getdns_root_trust_anchor(time_t *utc_date_of_anchor)
{
2015-06-30 07:43:52 -05:00
gldns_buffer *gbuf;
getdns_list *ta_rrs;
if (!(ta_rrs = getdns_list_create()))
return NULL;
if (!(gbuf = gldns_buffer_new(4096)))
goto error_free_ta_rrs;
if (!_getdns_parse_ta_file(utc_date_of_anchor, gbuf))
goto error_free_gbuf;
2015-07-08 04:07:44 -05:00
_getdns_wire2list( gldns_buffer_begin(gbuf)
2015-06-30 07:43:52 -05:00
, gldns_buffer_position(gbuf), ta_rrs);
gldns_buffer_free(gbuf);
2015-03-15 15:25:38 -05:00
return ta_rrs;
2015-06-30 07:43:52 -05:00
error_free_gbuf:
gldns_buffer_free(gbuf);
error_free_ta_rrs:
getdns_list_destroy(ta_rrs);
return NULL;
2014-02-19 09:56:00 -06:00
}
/* dnssec.c */