2014-02-03 16:34:55 -06:00
|
|
|
/**
|
|
|
|
*
|
2015-07-04 06:01:16 -05:00
|
|
|
* /brief functions for DNSSEC
|
2014-02-03 16:34:55 -06:00
|
|
|
*
|
2015-07-04 06:01:16 -05:00
|
|
|
* In this file, the "dnssec_return_validation_chain" extension is implemented
|
2015-08-19 09:22:38 -05:00
|
|
|
* (with the _getdns_get_validation_chain() function)
|
2015-07-04 06:01:16 -05:00
|
|
|
* 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-03 16:34:55 -06:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2014-02-25 07:12:33 -06:00
|
|
|
* Copyright (c) 2013, NLnet Labs, Verisign, Inc.
|
2014-02-03 16:34:55 -06:00
|
|
|
* 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.
|
2014-02-25 07:23:19 -06:00
|
|
|
* * Neither the names of the copyright holders nor the
|
2014-02-03 16:34:55 -06:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/*
|
|
|
|
* 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.
|
2015-07-04 06:01:16 -05:00
|
|
|
*
|
2015-07-06 04:57:16 -05:00
|
|
|
* 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.
|
|
|
|
*
|
2015-07-07 04:15:38 -05:00
|
|
|
* Note that the NSEC(3) RRsets proving the non-existance of a getdns_rrset
|
2015-07-06 04:57:16 -05:00
|
|
|
* 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-06 04:57:16 -05:00
|
|
|
*
|
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.
|
2015-07-06 04:57:16 -05:00
|
|
|
*
|
|
|
|
* 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".
|
2015-07-04 06:01:16 -05:00
|
|
|
*/
|
|
|
|
|
2014-02-19 09:56:00 -06:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
2014-02-03 16:34:55 -06:00
|
|
|
#include <ldns/ldns.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"
|
2014-02-19 09:56:00 -06:00
|
|
|
#include "config.h"
|
2014-02-03 16:34:55 -06:00
|
|
|
#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"
|
2015-03-16 11:05:03 -05:00
|
|
|
#include "gldns/wire2str.h"
|
2015-06-25 03:04:19 -05:00
|
|
|
#include "gldns/keyraw.h"
|
2015-06-25 17:26:40 -05:00
|
|
|
#include "gldns/parseutil.h"
|
2015-03-18 17:45:26 -05:00
|
|
|
#include "general.h"
|
2015-06-12 06:51:36 -05:00
|
|
|
#include "dict.h"
|
2015-07-01 14:50:47 -05:00
|
|
|
#include "list.h"
|
2014-02-03 16:34:55 -06:00
|
|
|
|
2015-07-07 04:15:38 -05:00
|
|
|
/* Maximum number of canonical name redirections for one name */
|
|
|
|
#define MAX_CNAMES 100
|
2015-07-04 06:01:16 -05:00
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
#define SIGNATURE_VERIFIED 0x10000
|
|
|
|
#define NSEC3_ITERATION_COUNT_HIGH 0x20000
|
2015-07-07 17:10:10 -05:00
|
|
|
#define NO_SUPPORTED_ALGORITHMS 0x40000
|
2015-07-07 15:33:53 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/******************* Frequently Used Utility Functions *********************
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
inline static size_t _dname_len(uint8_t *name)
|
|
|
|
{
|
|
|
|
uint8_t *p;
|
2015-07-07 17:10:10 -05:00
|
|
|
for (p = name; *p; p += *p + 1)
|
|
|
|
/* pass */
|
|
|
|
;
|
2015-07-04 06:01:16 -05:00
|
|
|
return p - name + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static size_t _dname_label_count(uint8_t *name)
|
|
|
|
{
|
|
|
|
size_t c;
|
2015-07-07 17:10:10 -05:00
|
|
|
for (c = 0; *name; name += *name + 1, c++)
|
|
|
|
/* pass */
|
|
|
|
;
|
2015-07-04 06:01:16 -05:00
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static int _dname_equal(const uint8_t *left, const uint8_t *right)
|
|
|
|
{
|
2015-08-19 09:22:38 -05:00
|
|
|
return _getdns_dname_equal(left, right);
|
2015-07-04 06:01:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static int _dname_is_parent(
|
2015-06-29 02:18:53 -05:00
|
|
|
const uint8_t * const parent, const uint8_t *subdomain)
|
2015-06-25 03:04:19 -05:00
|
|
|
{
|
2015-06-29 02:18:53 -05:00
|
|
|
while (*subdomain) {
|
2015-07-04 06:01:16 -05:00
|
|
|
if (_dname_equal(parent, subdomain))
|
2015-06-25 03:04:19 -05:00
|
|
|
return 1;
|
|
|
|
|
2015-06-29 02:18:53 -05:00
|
|
|
subdomain += *subdomain + 1;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
2015-06-29 02:18:53 -05:00
|
|
|
return *parent == 0;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
|
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)
|
2015-07-06 04:57:16 -05:00
|
|
|
{
|
|
|
|
uint8_t *r = dst, i;
|
|
|
|
|
|
|
|
if (!src || *src + 1 > dst_len)
|
|
|
|
return NULL;
|
|
|
|
|
2015-07-07 04:15:38 -05:00
|
|
|
for (i = (*dst++ = *src++); i ; i--)
|
2015-07-06 04:57:16 -05:00
|
|
|
*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]
|
|
|
|
*/
|
2015-07-04 06:01:16 -05:00
|
|
|
static uint8_t **reverse_labels(uint8_t *dname, uint8_t **labels)
|
|
|
|
{
|
|
|
|
if (*dname)
|
|
|
|
labels = reverse_labels(dname + *dname + 1, labels);
|
|
|
|
*labels = dname;
|
|
|
|
return labels + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t *dname_shared_parent(uint8_t *left, uint8_t *right)
|
|
|
|
{
|
|
|
|
uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel,
|
|
|
|
**llabel, **rlabel, *l, *r, 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]
|
2015-07-04 06:01:16 -05:00
|
|
|
; llabel < last_llabel
|
|
|
|
; llabel++, rlabel++ ) {
|
|
|
|
|
2015-07-07 04:15:38 -05:00
|
|
|
sz = **llabel;
|
2015-07-04 06:01:16 -05:00
|
|
|
if ( rlabel == last_rlabel
|
2015-07-07 04:15:38 -05:00
|
|
|
|| **llabel != **rlabel)
|
2015-07-04 06:01:16 -05:00
|
|
|
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(uint8_t *left, uint8_t *right)
|
|
|
|
{
|
|
|
|
uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel,
|
|
|
|
**llabel, **rlabel, *l, *r, 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);
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
static int bitmap_has_type(_getdns_rdf_iter *bitmap, uint16_t rr_type)
|
2015-07-04 06:01:16 -05:00
|
|
|
{
|
|
|
|
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));
|
2015-07-04 06:01:16 -05:00
|
|
|
dptr += dptr[1] + 2; /* next window */
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-06-19 11:02:16 -05:00
|
|
|
#if defined(SEC_DEBUG) && SEC_DEBUG
|
2015-08-19 09:22:38 -05:00
|
|
|
inline static 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);
|
|
|
|
uint8_t *data = rr->pos;
|
|
|
|
size_t data_len = rr->nxt - rr->pos;
|
|
|
|
|
|
|
|
if (!rr || !rr->pos) {
|
2015-06-22 17:00:20 -05:00
|
|
|
DEBUG_SEC("%s<nil>\n", msg);
|
2015-06-19 11:02:16 -05:00
|
|
|
return;
|
|
|
|
}
|
2015-06-22 17:00:20 -05:00
|
|
|
(void) gldns_wire2str_rr_scan(
|
|
|
|
&data, &data_len, &str, &str_len, rr->pkt, rr->pkt_end - rr->pkt);
|
2015-06-19 11:02:16 -05:00
|
|
|
DEBUG_SEC("%s%s", msg, str_spc);
|
|
|
|
}
|
2015-06-22 17:00:20 -05:00
|
|
|
inline static void debug_sec_print_dname(const char *msg, uint8_t *label)
|
|
|
|
{
|
|
|
|
char str[1024];
|
|
|
|
|
2015-06-27 16:28:23 -05:00
|
|
|
if (label && gldns_wire2str_dname_buf(label, 256, str, sizeof(str)))
|
2015-06-22 17:00:20 -05:00
|
|
|
DEBUG_SEC("%s%s\n", msg, str);
|
|
|
|
else
|
|
|
|
DEBUG_SEC("%s<nil>\n", msg);
|
|
|
|
}
|
2015-07-04 06:01:16 -05:00
|
|
|
inline static void debug_sec_print_pkt(
|
|
|
|
const char *msg, uint8_t *pkt, size_t pkt_len)
|
2015-06-25 03:04:19 -05:00
|
|
|
{
|
|
|
|
char *str;
|
|
|
|
DEBUG_SEC("%s%s\n", msg, (str = gldns_wire2str_pkt(pkt, pkt_len)));
|
|
|
|
if (str) free(str);
|
|
|
|
}
|
2015-06-19 11:02:16 -05:00
|
|
|
#else
|
|
|
|
#define debug_sec_print_rr(...) DEBUG_OFF(__VA_ARGS__)
|
2015-06-22 17:00:20 -05:00
|
|
|
#define debug_sec_print_dname(...) DEBUG_OFF(__VA_ARGS__)
|
2015-06-25 03:04:19 -05:00
|
|
|
#define debug_sec_print_pkt(...) DEBUG_OFF(__VA_ARGS__)
|
2015-06-19 11:02:16 -05:00
|
|
|
#endif
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
|
|
|
|
/******************* getdns_rrset + Support Iterators **********************
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* Utility functions to read rr_type and rr_class from a rr iterator */
|
2015-08-19 09:22:38 -05:00
|
|
|
static inline uint16_t rr_iter_type(_getdns_rr_iter *rr)
|
2015-06-19 11:02:16 -05:00
|
|
|
{ return rr->rr_type + 2 <= rr->nxt ? gldns_read_uint16(rr->rr_type) : 0; }
|
2015-08-19 09:22:38 -05:00
|
|
|
static inline uint16_t rr_iter_class(_getdns_rr_iter *rr)
|
2015-06-19 11:02:16 -05:00
|
|
|
{ return rr->rr_type + 4 <= rr->nxt ? gldns_read_uint16(rr->rr_type + 2) : 0; }
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* Utility function to compare owner name of rr with name */
|
2015-08-19 09:22:38 -05:00
|
|
|
static int rr_owner_equal(_getdns_rr_iter *rr, uint8_t *name)
|
2015-07-06 04:57:16 -05:00
|
|
|
{
|
|
|
|
uint8_t owner_spc[256], *owner;
|
|
|
|
size_t owner_len = sizeof(owner_spc);
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
return (owner = _getdns_owner_if_or_as_decompressed(rr, owner_spc
|
2015-07-06 04:57:16 -05:00
|
|
|
, &owner_len))
|
|
|
|
&& _dname_equal(owner, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First a few filter functions that filter a RR iterator to point only
|
|
|
|
* to RRs with certain constraints (and moves on otherwise).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Filter that only iterates over the ANSWER and AUTHORITY section */
|
2015-08-19 09:22:38 -05:00
|
|
|
static _getdns_rr_iter *rr_iter_ansauth(_getdns_rr_iter *rr)
|
2015-06-19 11:02:16 -05:00
|
|
|
{
|
|
|
|
while (rr && rr->pos && !(
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter_section(rr) == GLDNS_SECTION_ANSWER ||
|
|
|
|
_getdns_rr_iter_section(rr) == GLDNS_SECTION_AUTHORITY))
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
rr = _getdns_rr_iter_next(rr);
|
2015-06-19 11:02:16 -05:00
|
|
|
|
|
|
|
return rr && rr->pos ? rr : NULL;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* Filter that only iterates over RRs with a certain name/class/type */
|
2015-08-19 09:22:38 -05:00
|
|
|
static _getdns_rr_iter *rr_iter_name_class_type(_getdns_rr_iter *rr,
|
2015-06-19 11:02:16 -05:00
|
|
|
uint8_t *name, uint16_t rr_class, uint16_t rr_type)
|
|
|
|
{
|
|
|
|
while (rr_iter_ansauth(rr) && !(
|
|
|
|
rr_iter_type(rr) == rr_type &&
|
|
|
|
rr_iter_class(rr) == rr_class &&
|
|
|
|
rr_owner_equal(rr, name)))
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
rr = _getdns_rr_iter_next(rr);
|
2015-06-19 11:02:16 -05:00
|
|
|
|
|
|
|
return rr && rr->pos ? rr : NULL;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* Filter that only iterates over RRs that do not have a name/class/type */
|
2015-08-19 09:22:38 -05:00
|
|
|
static _getdns_rr_iter *rr_iter_not_name_class_type(_getdns_rr_iter *rr,
|
2015-06-19 11:02:16 -05:00
|
|
|
uint8_t *name, uint16_t rr_class, uint16_t rr_type)
|
|
|
|
{
|
|
|
|
while (rr_iter_ansauth(rr) && (
|
|
|
|
rr_iter_type(rr) == GETDNS_RRTYPE_RRSIG || (
|
|
|
|
rr_iter_type(rr) == rr_type &&
|
|
|
|
rr_iter_class(rr) == rr_class &&
|
|
|
|
rr_owner_equal(rr, name))))
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
rr = _getdns_rr_iter_next(rr);
|
2015-06-19 11:02:16 -05:00
|
|
|
|
|
|
|
return rr && rr->pos ? rr : NULL;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* Filter that only iterates over RRs that are of type RRSIG, that cover
|
|
|
|
* a RRset with a certain name/class/type
|
|
|
|
*/
|
2015-08-19 09:22:38 -05:00
|
|
|
static _getdns_rr_iter *rr_iter_rrsig_covering(_getdns_rr_iter *rr,
|
2015-06-19 11:02:16 -05:00
|
|
|
uint8_t *name, uint16_t rr_class, uint16_t rr_type)
|
|
|
|
{
|
|
|
|
while (rr_iter_ansauth(rr) && !(
|
|
|
|
rr_iter_type(rr) == GETDNS_RRTYPE_RRSIG &&
|
|
|
|
rr_iter_class(rr) == rr_class &&
|
|
|
|
rr->rr_type + 12 <= rr->nxt &&
|
|
|
|
gldns_read_uint16(rr->rr_type + 10) == rr_type &&
|
|
|
|
rr_owner_equal(rr, name)))
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
rr = _getdns_rr_iter_next(rr);
|
2015-06-19 11:02:16 -05:00
|
|
|
|
|
|
|
return rr && rr->pos ? rr : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct getdns_rrset {
|
2015-06-23 09:41:34 -05:00
|
|
|
uint8_t *name;
|
|
|
|
uint16_t rr_class;
|
|
|
|
uint16_t rr_type;
|
|
|
|
uint8_t *pkt;
|
|
|
|
size_t pkt_len;
|
2015-06-19 11:02:16 -05:00
|
|
|
} getdns_rrset;
|
|
|
|
|
|
|
|
typedef struct rrtype_iter {
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter rr_i;
|
2015-06-19 11:02:16 -05:00
|
|
|
getdns_rrset *rrset;
|
|
|
|
} rrtype_iter;
|
|
|
|
|
|
|
|
typedef struct rrsig_iter {
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter rr_i;
|
2015-06-19 11:02:16 -05:00
|
|
|
getdns_rrset *rrset;
|
|
|
|
} rrsig_iter;
|
|
|
|
|
|
|
|
static rrtype_iter *rrtype_iter_next(rrtype_iter *i)
|
|
|
|
{
|
|
|
|
return (rrtype_iter *) rr_iter_name_class_type(
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter_next(&i->rr_i),
|
2015-06-19 11:02:16 -05:00
|
|
|
i->rrset->name, i->rrset->rr_class, i->rrset->rr_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static rrtype_iter *rrtype_iter_init(rrtype_iter *i, getdns_rrset *rrset)
|
|
|
|
{
|
|
|
|
i->rrset = rrset;
|
|
|
|
return (rrtype_iter *) rr_iter_name_class_type(
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter_init(&i->rr_i, rrset->pkt, rrset->pkt_len ),
|
2015-06-19 11:02:16 -05:00
|
|
|
i->rrset->name, i->rrset->rr_class, i->rrset->rr_type);
|
|
|
|
}
|
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
inline static int rrset_has_rrs(getdns_rrset *rrset)
|
|
|
|
{
|
|
|
|
rrtype_iter rr_spc;
|
|
|
|
return rrtype_iter_init(&rr_spc, rrset) != NULL;
|
|
|
|
}
|
|
|
|
|
2015-06-19 11:02:16 -05:00
|
|
|
static rrsig_iter *rrsig_iter_next(rrsig_iter *i)
|
|
|
|
{
|
|
|
|
return (rrsig_iter *) rr_iter_rrsig_covering(
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter_next(&i->rr_i),
|
2015-06-19 11:02:16 -05:00
|
|
|
i->rrset->name, i->rrset->rr_class, i->rrset->rr_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static rrsig_iter *rrsig_iter_init(rrsig_iter *i, getdns_rrset *rrset)
|
|
|
|
{
|
|
|
|
i->rrset = rrset;
|
|
|
|
return (rrsig_iter *) rr_iter_rrsig_covering(
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter_init(&i->rr_i, rrset->pkt, rrset->pkt_len),
|
2015-06-19 11:02:16 -05:00
|
|
|
i->rrset->name, i->rrset->rr_class, i->rrset->rr_type);
|
|
|
|
}
|
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
inline static int rrset_has_rrsigs(getdns_rrset *rrset)
|
2015-06-19 11:02:16 -05:00
|
|
|
{
|
|
|
|
rrsig_iter rrsig;
|
|
|
|
return rrsig_iter_init(&rrsig, rrset) != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(SEC_DEBUG) && SEC_DEBUG
|
|
|
|
static void debug_sec_print_rrset(const char *msg, getdns_rrset *rrset)
|
|
|
|
{
|
|
|
|
char owner[1024];
|
|
|
|
char buf_space[2048];
|
|
|
|
gldns_buffer buf;
|
|
|
|
rrtype_iter *rr, rr_space;
|
|
|
|
rrsig_iter *rrsig, rrsig_space;
|
|
|
|
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(rrset->name, 256, owner, sizeof(owner)))
|
|
|
|
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;
|
|
|
|
}
|
2015-08-19 09:22:38 -05:00
|
|
|
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 = rrtype_iter_init(&rr_space, rrset), i = 1
|
|
|
|
; rr
|
|
|
|
; rr = rrtype_iter_next(rr), i++)
|
|
|
|
gldns_buffer_printf(&buf, " %d", (int)i);
|
|
|
|
|
|
|
|
gldns_buffer_printf(&buf, ", rrsigs:");
|
|
|
|
for ( rrsig = rrsig_iter_init(&rrsig_space, rrset), i = 1
|
|
|
|
; rrsig
|
|
|
|
; rrsig = rrsig_iter_next(rrsig), i++)
|
|
|
|
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
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* The rrset_iter manifests an iterator of a wireformat packet that will return
|
|
|
|
* all unique rrsets within that packet in turn.
|
|
|
|
*/
|
2015-06-19 11:02:16 -05:00
|
|
|
typedef struct rrset_iter rrset_iter;
|
|
|
|
struct rrset_iter {
|
|
|
|
getdns_rrset rrset;
|
|
|
|
uint8_t name_spc[256];
|
|
|
|
size_t name_len;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter rr_i;
|
2015-06-19 11:02:16 -05:00
|
|
|
};
|
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
static rrset_iter *rrset_iter_init(rrset_iter *i, uint8_t *pkt, size_t pkt_len)
|
2015-06-19 11:02:16 -05:00
|
|
|
{
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter *rr;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
|
|
|
i->rrset.name = i->name_spc;
|
2015-06-23 09:41:34 -05:00
|
|
|
i->rrset.pkt = pkt;
|
|
|
|
i->rrset.pkt_len = pkt_len;
|
2015-06-19 11:02:16 -05:00
|
|
|
i->name_len = 0;
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
for ( rr = _getdns_rr_iter_init(&i->rr_i, pkt, pkt_len)
|
2015-06-19 11:02:16 -05:00
|
|
|
;(rr = rr_iter_ansauth(rr))
|
2015-08-19 09:22:38 -05:00
|
|
|
; rr = _getdns_rr_iter_next(rr)) {
|
2015-06-19 11:02:16 -05:00
|
|
|
|
|
|
|
if ((i->rrset.rr_type = rr_iter_type(rr))
|
|
|
|
== GETDNS_RRTYPE_RRSIG)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
i->rrset.rr_class = rr_iter_class(rr);
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
if (!(i->rrset.name = _getdns_owner_if_or_as_decompressed(
|
2015-06-19 11:02:16 -05:00
|
|
|
rr, i->name_spc, &i->name_len)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-06-25 03:04:19 -05:00
|
|
|
static rrset_iter *rrset_iter_rewind(rrset_iter *i)
|
|
|
|
{
|
|
|
|
return rrset_iter_init(i, i->rrset.pkt, i->rrset.pkt_len);
|
|
|
|
}
|
|
|
|
|
2015-06-19 11:02:16 -05:00
|
|
|
static rrset_iter *rrset_iter_next(rrset_iter *i)
|
|
|
|
{
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter *rr;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
|
|
|
if (!(rr = i && i->rr_i.pos ? &i->rr_i : NULL))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!(rr = rr_iter_not_name_class_type(rr,
|
|
|
|
i->rrset.name, i->rrset.rr_class, i->rrset.rr_type)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
i->rrset.rr_type = rr_iter_type(rr);
|
|
|
|
i->rrset.rr_class = rr_iter_class(rr);
|
2015-08-19 09:22:38 -05:00
|
|
|
if (!(i->rrset.name = _getdns_owner_if_or_as_decompressed(
|
2015-06-19 11:02:16 -05:00
|
|
|
rr, i->name_spc, &i->name_len)))
|
2015-07-07 04:15:38 -05:00
|
|
|
|
|
|
|
/* This is safe, because rr_iter_not_name_class_type will shift
|
|
|
|
* the iterator forward because at least name does not match.
|
|
|
|
* Goal is to skip broken compression pointer issues but keep
|
|
|
|
* processing the packet.
|
|
|
|
*/
|
2015-06-19 11:02:16 -05:00
|
|
|
return rrset_iter_next(i);
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static getdns_rrset *rrset_iter_value(rrset_iter *i)
|
|
|
|
{
|
|
|
|
if (!i)
|
|
|
|
return NULL;
|
|
|
|
if (!i->rr_i.pos)
|
|
|
|
return NULL;
|
|
|
|
return &i->rrset;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
static getdns_rrset *rrset_by_type(
|
|
|
|
rrset_iter *i_spc, getdns_network_req *netreq, uint16_t rr_type)
|
|
|
|
{
|
|
|
|
rrset_iter *i;
|
|
|
|
getdns_rrset *rrset;
|
|
|
|
|
|
|
|
for ( i = rrset_iter_init(i_spc,netreq->response,netreq->response_len)
|
|
|
|
; i
|
|
|
|
; i = rrset_iter_next(i)) {
|
|
|
|
|
|
|
|
rrset = rrset_iter_value(i);
|
|
|
|
if (rrset->rr_type == rr_type) /* Check class too? */
|
|
|
|
return rrset;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/********************* Validation Chain Data Structs ***********************
|
|
|
|
*****************************************************************************/
|
2015-06-23 09:41:34 -05:00
|
|
|
|
2015-06-19 11:02:16 -05:00
|
|
|
typedef struct chain_head chain_head;
|
|
|
|
typedef struct chain_node chain_node;
|
|
|
|
|
|
|
|
struct chain_head {
|
2015-06-22 17:00:20 -05:00
|
|
|
struct mem_funcs my_mf;
|
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
chain_head *next;
|
|
|
|
chain_node *parent;
|
|
|
|
size_t node_count; /* Number of nodes attached directly
|
|
|
|
* to this head. For cleaning. */
|
|
|
|
getdns_rrset rrset;
|
2015-07-01 14:50:47 -05:00
|
|
|
getdns_network_req *netreq;
|
2015-07-02 10:30:37 -05:00
|
|
|
int signer;
|
2015-07-01 14:50:47 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
uint8_t name_spc[];
|
2015-06-19 11:02:16 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
struct chain_node {
|
|
|
|
chain_node *parent;
|
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
getdns_rrset dnskey;
|
2015-07-01 14:50:47 -05:00
|
|
|
getdns_network_req *dnskey_req;
|
2015-07-02 10:30:37 -05:00
|
|
|
int dnskey_signer;
|
2015-07-01 14:50:47 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
getdns_rrset ds;
|
2015-07-01 14:50:47 -05:00
|
|
|
getdns_network_req *ds_req;
|
2015-07-02 10:30:37 -05:00
|
|
|
int ds_signer;
|
2015-07-01 14:50:47 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
getdns_network_req *soa_req;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
chain_head *chains;
|
|
|
|
};
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/********************* Validation Chain Construction ***********************
|
|
|
|
*****************************************************************************/
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* 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, uint8_t *dname);
|
2015-07-14 03:22:42 -05:00
|
|
|
static void val_chain_sched_ds(chain_head *head, uint8_t *dname);
|
2015-07-04 06:01:16 -05:00
|
|
|
static void val_chain_sched_signer(chain_head *head, rrsig_iter *rrsig);
|
|
|
|
static void val_chain_sched_soa(chain_head *head, uint8_t *dname);
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
static chain_head *add_rrset2val_chain(struct mem_funcs *mf,
|
|
|
|
chain_head **chain_p, getdns_rrset *rrset, getdns_network_req *netreq)
|
2015-06-25 03:04:19 -05:00
|
|
|
{
|
2015-07-04 06:01:16 -05:00
|
|
|
chain_head *head;
|
|
|
|
uint8_t *labels[128], **last_label, **label;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
size_t max_labels; /* max labels in common */
|
|
|
|
chain_head *max_head;
|
|
|
|
chain_node *max_node;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
size_t dname_len, head_sz, node_count, n;
|
|
|
|
uint8_t *dname, *region;
|
|
|
|
chain_node *node;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
last_label = reverse_labels(rrset->name, labels);
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* 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
|
2015-07-04 06:01:16 -05:00
|
|
|
*/
|
|
|
|
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
|
|
|
|
&& rrset->pkt == head->rrset.pkt
|
|
|
|
&& rrset->pkt_len == head->rrset.pkt_len
|
|
|
|
&& _dname_equal(rrset->name, head->rrset.name))
|
|
|
|
return NULL;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
for (label = labels; label < last_label; label++) {
|
|
|
|
if (! _dname_is_parent(*label, head->rrset.name))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (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++);
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
for ( n -= max_labels, node = max_head->parent
|
|
|
|
; n
|
|
|
|
; n--, node = node->parent);
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
max_node = node;
|
|
|
|
} else
|
|
|
|
max_node = NULL;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* 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;
|
|
|
|
DEBUG_SEC( "%zu labels in common. %zu labels to allocate\n"
|
|
|
|
, max_labels, node_count);
|
2015-06-27 16:47:47 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
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)
|
|
|
|
;
|
2015-07-04 06:01:16 -05:00
|
|
|
if (head)
|
|
|
|
head = head->next = (chain_head *)region;
|
|
|
|
else
|
|
|
|
head = *chain_p = (chain_head *)region;
|
2015-06-27 16:47:47 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
head->my_mf = *mf;
|
|
|
|
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->netreq = netreq;
|
|
|
|
head->signer = 0;
|
|
|
|
head->node_count = node_count;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
if (!node_count) {
|
|
|
|
head->parent = max_head->parent;
|
|
|
|
return head;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Initialize the nodes */
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-07 04:15:38 -05:00
|
|
|
for ( head->parent = node = (chain_node *)(region + head_sz),
|
|
|
|
dname = head->rrset.name
|
2015-07-04 06:01:16 -05:00
|
|
|
; node_count
|
|
|
|
; node_count--, node = node->parent =&node[1], dname += *dname + 1) {
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
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->dnskey.pkt = NULL;
|
|
|
|
node->dnskey.pkt_len = 0;
|
|
|
|
node->ds_req = NULL;
|
|
|
|
node->dnskey_req = NULL;
|
|
|
|
node->soa_req = NULL;
|
|
|
|
node->ds_signer = 0;
|
|
|
|
node->dnskey_signer = 0;
|
|
|
|
|
|
|
|
node->chains = *chain_p;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
2015-07-04 06:01:16 -05:00
|
|
|
/* 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");
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
return head;
|
|
|
|
}
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
static int is_synthesized_cname(getdns_rrset *cname)
|
2015-06-25 03:04:19 -05:00
|
|
|
{
|
2015-07-04 06:01:16 -05:00
|
|
|
rrset_iter *i, i_spc;
|
|
|
|
getdns_rrset *dname;
|
|
|
|
rrtype_iter rr_spc, *rr;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-07-04 06:01:16 -05:00
|
|
|
rrtype_iter drr_spc, *drr;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter drdf_spc, *drdf;
|
2015-07-04 06:01:16 -05:00
|
|
|
uint8_t cname_rdata_spc[256], *cname_rdata,
|
|
|
|
dname_rdata_spc[256], *dname_rdata,
|
|
|
|
synth_name[256],
|
2015-07-07 04:15:38 -05:00
|
|
|
*synth_name_end = synth_name + sizeof(synth_name) - 1, *s, *c;
|
2015-07-04 06:01:16 -05:00
|
|
|
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
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Synthesized CNAMEs don't have RRSIGs */
|
|
|
|
if ( cname->rr_type != GETDNS_RRTYPE_CNAME
|
|
|
|
|| rrset_has_rrsigs(cname))
|
|
|
|
return 0;
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Get canonical name rdata field */
|
|
|
|
if ( !(rr = rrtype_iter_init(&rr_spc, cname))
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(rdf = _getdns_rdf_iter_init(&rdf_spc, &rr->rr_i))
|
|
|
|
|| !(cname_rdata = _getdns_rdf_if_or_as_decompressed(
|
2015-07-04 06:01:16 -05:00
|
|
|
rdf, cname_rdata_spc, &cname_rdata_len)))
|
|
|
|
return 0;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Find a matching DNAME */
|
|
|
|
for ( i = rrset_iter_init(&i_spc, cname->pkt, cname->pkt_len)
|
|
|
|
; i
|
|
|
|
; i = rrset_iter_next(i)) {
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
dname = 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
|
|
|
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
dname_labels = _dname_label_count(dname->name);
|
|
|
|
cname_labels = _dname_label_count(cname->name);
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -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
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
memcpy(s, c, *c + 1);
|
|
|
|
}
|
|
|
|
if (cname_labels)
|
|
|
|
continue;
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Get DNAME's rdata field */
|
|
|
|
if ( !(drr = rrtype_iter_init(&drr_spc, dname))
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(drdf=_getdns_rdf_iter_init(&drdf_spc,&drr->rr_i))
|
|
|
|
|| !(dname_rdata = _getdns_rdf_if_or_as_decompressed(
|
2015-07-04 06:01:16 -05:00
|
|
|
drdf, dname_rdata_spc, &dname_rdata_len)))
|
|
|
|
continue;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
if (s + _dname_len(dname_rdata) > synth_name_end)
|
|
|
|
continue;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
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;
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
2015-07-04 06:01:16 -05:00
|
|
|
return 0;
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* 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.
|
2015-07-04 06:01:16 -05:00
|
|
|
*/
|
|
|
|
static void add_pkt2val_chain(struct mem_funcs *mf,
|
|
|
|
chain_head **chain_p, uint8_t *pkt, size_t pkt_len,
|
|
|
|
getdns_network_req *netreq)
|
2015-06-25 03:04:19 -05:00
|
|
|
{
|
2015-07-04 06:01:16 -05:00
|
|
|
rrset_iter *i, i_spc;
|
|
|
|
getdns_rrset *rrset;
|
|
|
|
rrsig_iter *rrsig, rrsig_spc;
|
|
|
|
size_t n_rrsigs;
|
|
|
|
chain_head *head;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
assert(pkt);
|
|
|
|
assert(pkt_len >= GLDNS_HEADER_SIZE);
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* For all things with signatures, create a chain */
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* For all things without signature, find SOA (zonecut) and query DS */
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
for ( i = rrset_iter_init(&i_spc, pkt, pkt_len)
|
|
|
|
; i
|
|
|
|
; i = rrset_iter_next(i)) {
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
rrset = rrset_iter_value(i);
|
|
|
|
debug_sec_print_rrset("rrset: ", rrset);
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Schedule validation for everything, except from DNAME
|
|
|
|
* synthesized CNAME's
|
|
|
|
*/
|
|
|
|
if (is_synthesized_cname(rrset))
|
|
|
|
continue;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
if (!(head = add_rrset2val_chain(mf, chain_p, rrset, netreq)))
|
|
|
|
continue;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset), n_rrsigs = 0
|
|
|
|
; rrsig
|
|
|
|
; rrsig = rrsig_iter_next(rrsig), n_rrsigs++) {
|
|
|
|
|
2015-07-07 07:52:32 -05:00
|
|
|
/* Signature, so lookup DS/DNSKEY at signer's name */
|
2015-07-04 06:01:16 -05:00
|
|
|
val_chain_sched_signer(head, rrsig);
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
2015-07-04 06:01:16 -05:00
|
|
|
if (n_rrsigs)
|
|
|
|
continue;
|
|
|
|
|
2015-07-07 07:52:32 -05:00
|
|
|
/* No signatures found for this RRset */
|
2015-07-04 06:01:16 -05:00
|
|
|
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)
|
2015-07-04 06:01:16 -05:00
|
|
|
val_chain_sched_soa(head, rrset->name + *rrset->name + 1);
|
|
|
|
else
|
|
|
|
val_chain_sched_soa(head, rrset->name);
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* 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,
|
|
|
|
uint8_t *qname, uint16_t qtype, uint16_t qclass,
|
|
|
|
getdns_network_req *netreq)
|
2015-06-25 03:04:19 -05:00
|
|
|
{
|
2015-07-04 06:01:16 -05:00
|
|
|
getdns_rrset q_rrset;
|
|
|
|
uint8_t cname_spc[256];
|
|
|
|
size_t cname_len = sizeof(cname_spc);
|
|
|
|
size_t anti_loop;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-07-04 06:01:16 -05:00
|
|
|
rrtype_iter *rr, rr_spc;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
chain_head *head;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
assert(pkt);
|
|
|
|
assert(pkt_len >= GLDNS_HEADER_SIZE);
|
|
|
|
assert(qname);
|
|
|
|
|
|
|
|
/* First find the canonical name for the question */
|
|
|
|
q_rrset.name = qname;
|
|
|
|
q_rrset.rr_type = GETDNS_RRTYPE_CNAME;
|
|
|
|
q_rrset.rr_class = qclass;
|
|
|
|
q_rrset.pkt = pkt;
|
|
|
|
q_rrset.pkt_len = pkt_len;
|
|
|
|
|
2015-07-07 04:15:38 -05:00
|
|
|
for (anti_loop = MAX_CNAMES; anti_loop; anti_loop--) {
|
2015-07-04 06:01:16 -05:00
|
|
|
if (!(rr = rrtype_iter_init(&rr_spc, &q_rrset)))
|
|
|
|
break;
|
2015-08-19 09:22:38 -05:00
|
|
|
if (!(rdf = _getdns_rdf_iter_init(&rdf_spc, &rr->rr_i)))
|
2015-07-04 06:01:16 -05:00
|
|
|
break;
|
2015-08-19 09:22:38 -05:00
|
|
|
q_rrset.name = _getdns_rdf_if_or_as_decompressed(
|
2015-07-04 06:01:16 -05:00
|
|
|
rdf, cname_spc, &cname_len);
|
|
|
|
}
|
2015-09-08 03:52:04 -05:00
|
|
|
|
|
|
|
/* If the qtype was a CNAME, and we got one, we'r done.
|
|
|
|
* We asked for it directly, so no redirection applies.
|
2015-09-08 04:24:45 -05:00
|
|
|
* Otherwise we have to check the referred to name/qtype.
|
2015-09-08 03:52:04 -05:00
|
|
|
*/
|
|
|
|
if (qtype == GETDNS_RRTYPE_CNAME && q_rrset.name != qname)
|
|
|
|
return;
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
q_rrset.rr_type = qtype;
|
|
|
|
if (!(rr = rrtype_iter_init(&rr_spc, &q_rrset))) {
|
|
|
|
/* 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);
|
|
|
|
head = add_rrset2val_chain(mf, chain_p, &q_rrset, netreq);
|
|
|
|
|
|
|
|
/* 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.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/************* Schedule Queries to Provision Validation Chain ***************
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
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;
|
|
|
|
getdns_dns_req *dnsreq;
|
|
|
|
char name[1024];
|
|
|
|
|
|
|
|
context = node->chains->netreq->owner->context;
|
|
|
|
loop = node->chains->netreq->owner->loop;
|
|
|
|
|
|
|
|
if (!gldns_wire2str_dname_buf(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);
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
if (! node->soa_req &&
|
2015-08-19 09:22:38 -05:00
|
|
|
! _getdns_general_loop(context, loop, name, GETDNS_RRTYPE_SOA,
|
2015-07-06 04:57:16 -05:00
|
|
|
dnssec_ok_checking_disabled, node, &dnsreq, NULL,
|
|
|
|
val_chain_node_soa_cb))
|
|
|
|
|
|
|
|
node->soa_req = dnsreq->netreqs[0];
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
*/
|
2015-07-06 04:57:16 -05:00
|
|
|
static void val_chain_sched_soa(chain_head *head, 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 void val_chain_node_cb(getdns_dns_req *dnsreq);
|
|
|
|
static void val_chain_sched_node(chain_node *node)
|
|
|
|
{
|
|
|
|
getdns_context *context;
|
|
|
|
getdns_eventloop *loop;
|
|
|
|
getdns_dns_req *dnsreq;
|
|
|
|
char name[1024];
|
|
|
|
|
|
|
|
context = node->chains->netreq->owner->context;
|
|
|
|
loop = node->chains->netreq->owner->loop;
|
|
|
|
|
|
|
|
if (!gldns_wire2str_dname_buf(node->ds.name, 256, name, sizeof(name)))
|
|
|
|
return;
|
|
|
|
|
|
|
|
DEBUG_SEC("schedule DS & DNSKEY lookup for %s\n", name);
|
|
|
|
|
|
|
|
if (! node->dnskey_req /* not scheduled */ &&
|
2015-08-19 09:22:38 -05:00
|
|
|
! _getdns_general_loop(context, loop, name, GETDNS_RRTYPE_DNSKEY,
|
2015-07-06 04:57:16 -05:00
|
|
|
dnssec_ok_checking_disabled, node, &dnsreq, NULL, val_chain_node_cb))
|
|
|
|
|
|
|
|
node->dnskey_req = dnsreq->netreqs[0];
|
|
|
|
|
|
|
|
if (! node->ds_req && node->parent /* not root */ &&
|
2015-08-19 09:22:38 -05:00
|
|
|
! _getdns_general_loop(context, loop, name, GETDNS_RRTYPE_DS,
|
2015-07-06 04:57:16 -05:00
|
|
|
dnssec_ok_checking_disabled, node, &dnsreq, NULL, val_chain_node_cb))
|
|
|
|
|
|
|
|
node->ds_req = dnsreq->netreqs[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void val_chain_sched(chain_head *head, 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;
|
|
|
|
getdns_dns_req *ds_req;
|
|
|
|
char name[1024];
|
|
|
|
|
|
|
|
context = node->chains->netreq->owner->context;
|
|
|
|
loop = node->chains->netreq->owner->loop;
|
|
|
|
|
|
|
|
if (!gldns_wire2str_dname_buf(node->ds.name, 256, name, sizeof(name)))
|
|
|
|
return;
|
|
|
|
|
|
|
|
DEBUG_SEC("schedule DS lookup for %s\n", name);
|
|
|
|
|
|
|
|
if (! node->ds_req && node->parent /* not root */ &&
|
2015-08-19 09:22:38 -05:00
|
|
|
! _getdns_general_loop(context, loop, name, GETDNS_RRTYPE_DS,
|
2015-07-14 03:22:42 -05:00
|
|
|
dnssec_ok_checking_disabled, node, &ds_req, NULL, val_chain_node_cb))
|
|
|
|
|
|
|
|
node->ds_req = ds_req->netreqs[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void val_chain_sched_ds(chain_head *head, 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_ds_node(node);
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
static void val_chain_sched_signer_node(chain_node *node, rrsig_iter *rrsig)
|
|
|
|
{
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-07-06 04:57:16 -05:00
|
|
|
uint8_t signer_spc[256], *signer;
|
|
|
|
size_t signer_len;
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
if (!(rdf = _getdns_rdf_iter_init_at(&rdf_spc, &rrsig->rr_i, 7)))
|
2015-07-06 04:57:16 -05:00
|
|
|
return;
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
if (!(signer = _getdns_rdf_if_or_as_decompressed(
|
2015-07-06 04:57:16 -05:00
|
|
|
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, 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];
|
|
|
|
rrset_iter *i, i_spc;
|
|
|
|
getdns_rrset *rrset;
|
|
|
|
rrsig_iter *rrsig, rrsig_spc;
|
|
|
|
|
2015-08-19 09:30:15 -05:00
|
|
|
_getdns_context_clear_outbound_request(dnsreq);
|
2015-07-06 04:57:16 -05:00
|
|
|
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;
|
|
|
|
default : check_chain_complete(node->chains);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for ( i = rrset_iter_init(&i_spc,netreq->response,netreq->response_len)
|
|
|
|
; i
|
|
|
|
; i = rrset_iter_next(i)) {
|
|
|
|
|
|
|
|
rrset = 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 = rrsig_iter_init(&rrsig_spc, rrset)
|
|
|
|
; rrsig; rrsig = rrsig_iter_next(rrsig))
|
|
|
|
|
|
|
|
val_chain_sched_signer_node(node, rrsig);
|
|
|
|
}
|
|
|
|
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];
|
|
|
|
rrset_iter i_spc;
|
|
|
|
getdns_rrset *rrset;
|
|
|
|
|
2015-08-19 09:30:15 -05:00
|
|
|
_getdns_context_clear_outbound_request(dnsreq);
|
2015-07-06 04:57:16 -05:00
|
|
|
|
|
|
|
if ((rrset = rrset_by_type(&i_spc, netreq, GETDNS_RRTYPE_SOA))) {
|
|
|
|
|
|
|
|
while (node &&
|
|
|
|
! _dname_equal(node->ds.name, rrset->name))
|
|
|
|
node = node->parent;
|
|
|
|
|
|
|
|
if (node)
|
2015-07-14 03:22:42 -05:00
|
|
|
val_chain_sched_ds_node(node);
|
|
|
|
|
|
|
|
} else if (node->parent)
|
2015-07-06 04:57:16 -05:00
|
|
|
val_chain_sched_soa_node(node->parent);
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2015-07-04 06:01:16 -05:00
|
|
|
static int key_matches_signer(getdns_rrset *dnskey, getdns_rrset *rrset)
|
|
|
|
{
|
|
|
|
rrtype_iter rr_spc, *rr;
|
|
|
|
rrsig_iter rrsig_spc, *rrsig;
|
|
|
|
uint16_t keytag;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-07-04 06:01:16 -05:00
|
|
|
uint8_t signer_spc[256], *signer;
|
|
|
|
size_t signer_len = sizeof(signer_spc);
|
|
|
|
|
|
|
|
assert(dnskey->rr_type == GETDNS_RRTYPE_DNSKEY);
|
|
|
|
|
|
|
|
|
|
|
|
for ( rr = rrtype_iter_init(&rr_spc, dnskey)
|
|
|
|
; rr ; rr = 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 */
|
2015-07-04 06:01:16 -05:00
|
|
|
keytag = gldns_calc_keytag_raw(rr->rr_i.rr_type + 10,
|
|
|
|
rr->rr_i.nxt - rr->rr_i.rr_type - 10);
|
|
|
|
|
|
|
|
for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset)
|
|
|
|
; rrsig ; rrsig = 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]
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Does the keytag match? */
|
|
|
|
&& gldns_read_uint16(rrsig->rr_i.rr_type + 26)
|
|
|
|
== keytag
|
|
|
|
|
|
|
|
/* Does the signer name match? */
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (rdf = _getdns_rdf_iter_init_at(
|
2015-07-04 06:01:16 -05:00
|
|
|
&rdf_spc, &rrsig->rr_i, 7))
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (signer = _getdns_rdf_if_or_as_decompressed(
|
2015-07-04 06:01:16 -05:00
|
|
|
rdf, signer_spc, &signer_len))
|
|
|
|
|
|
|
|
&& _dname_equal(dnskey->name, signer))
|
|
|
|
|
|
|
|
return keytag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
static ldns_rr *rr2ldns_rr(_getdns_rr_iter *rr)
|
2015-07-04 06:01:16 -05:00
|
|
|
{
|
|
|
|
ldns_rr *rr_l;
|
|
|
|
size_t pos = rr->pos - rr->pkt;
|
|
|
|
|
|
|
|
if (ldns_wire2rr(&rr_l, rr->pkt, rr->pkt_end - rr->pkt, &pos,
|
2015-08-19 09:22:38 -05:00
|
|
|
(ldns_pkt_section)_getdns_rr_iter_section(rr)))
|
2015-07-04 06:01:16 -05:00
|
|
|
return NULL;
|
2015-07-08 06:07:24 -05:00
|
|
|
else
|
|
|
|
return rr_l;
|
2015-07-04 06:01:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static ldns_rr_list *rrset2ldns_rr_list(getdns_rrset *rrset)
|
|
|
|
{
|
|
|
|
rrtype_iter rr_spc, *rr;
|
|
|
|
ldns_rr_list *rr_list = ldns_rr_list_new();
|
|
|
|
ldns_rr *rr_l;
|
|
|
|
|
|
|
|
if (rr_list) {
|
|
|
|
for ( rr = rrtype_iter_init(&rr_spc, rrset)
|
|
|
|
; rr ; rr = rrtype_iter_next(rr) )
|
|
|
|
if ((rr_l = rr2ldns_rr(&rr->rr_i)))
|
2015-07-07 07:52:32 -05:00
|
|
|
(void)ldns_rr_list_push_rr(rr_list, rr_l);
|
2015-07-04 06:01:16 -05:00
|
|
|
}
|
|
|
|
return rr_list;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 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).
|
|
|
|
*/
|
|
|
|
static int _getdns_verify_rrsig(
|
|
|
|
getdns_rrset *rrset, rrsig_iter *rrsig, rrtype_iter *key, uint8_t **nc_name)
|
|
|
|
{
|
|
|
|
ldns_rr_list *rrset_l = rrset2ldns_rr_list(rrset);
|
|
|
|
ldns_rr *rrsig_l = rr2ldns_rr(&rrsig->rr_i);
|
|
|
|
ldns_rr *key_l = rr2ldns_rr(&key->rr_i);
|
|
|
|
int r;
|
2015-07-07 07:52:32 -05:00
|
|
|
int to_skip;
|
2015-07-04 06:01:16 -05:00
|
|
|
|
|
|
|
/* nc_name should already have been initialized by the parent! */
|
|
|
|
assert(nc_name);
|
|
|
|
assert(!*nc_name);
|
|
|
|
|
|
|
|
r = rrset_l && rrsig_l && key_l &&
|
|
|
|
ldns_verify_rrsig(rrset_l, rrsig_l, key_l) == LDNS_STATUS_OK;
|
|
|
|
|
|
|
|
ldns_rr_list_deep_free(rrset_l);
|
|
|
|
ldns_rr_free(rrsig_l);
|
|
|
|
ldns_rr_free(key_l);
|
|
|
|
|
|
|
|
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;
|
2015-07-04 06:01:16 -05:00
|
|
|
|
|
|
|
for ( *nc_name = rrset->name
|
2015-07-07 07:52:32 -05:00
|
|
|
; to_skip > 0
|
2015-07-04 06:01:16 -05:00
|
|
|
; *nc_name += **nc_name + 1, to_skip--);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* Calculates NSEC3 hash for name, and stores that into label */
|
2015-07-04 06:01:16 -05:00
|
|
|
static uint8_t *_getdns_nsec3_hash_label(uint8_t *label, size_t label_len,
|
|
|
|
uint8_t *name, uint8_t algorithm, uint16_t iterations, 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)
|
2015-07-04 06:01:16 -05:00
|
|
|
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)
|
2015-07-04 06:01:16 -05:00
|
|
|
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-07-04 06:01:16 -05:00
|
|
|
}
|
2015-08-19 08:19:02 -05:00
|
|
|
*label = gldns_b32_ntop_extended_hex(
|
|
|
|
md, SHA_DIGEST_LENGTH, (char *)label + 1, label_len - 1);
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
static uint8_t *name2nsec3_label(
|
|
|
|
getdns_rrset *nsec3, uint8_t *name, uint8_t *label, size_t label_len)
|
|
|
|
{
|
|
|
|
rrsig_iter rrsig_spc, *rrsig;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-07-06 04:57:16 -05:00
|
|
|
uint8_t signer_spc[256], *signer;
|
|
|
|
size_t signer_len = sizeof(signer_spc);
|
|
|
|
rrtype_iter rr_spc, *rr;
|
|
|
|
|
|
|
|
if (/* With the "first" signature */
|
|
|
|
(rrsig = rrsig_iter_init(&rrsig_spc, nsec3))
|
|
|
|
|
|
|
|
/* Access the signer name rdata field (7th) */
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (rdf = _getdns_rdf_iter_init_at(
|
2015-07-06 04:57:16 -05:00
|
|
|
&rdf_spc, &rrsig->rr_i, 7))
|
|
|
|
|
|
|
|
/* Verify & decompress */
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (signer = _getdns_rdf_if_or_as_decompressed(
|
2015-07-06 04:57:16 -05:00
|
|
|
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 = 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;
|
|
|
|
}
|
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
|
|
|
|
static int nsec3_iteration_count_high(rrtype_iter *dnskey, getdns_rrset *nsec3)
|
|
|
|
{
|
|
|
|
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 = 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;
|
|
|
|
|
|
|
|
bits = ldns_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;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* 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.
|
|
|
|
*/
|
2015-07-04 06:01:16 -05:00
|
|
|
static int dnskey_signed_rrset(
|
|
|
|
rrtype_iter *dnskey, getdns_rrset *rrset, uint8_t **nc_name)
|
|
|
|
{
|
|
|
|
rrsig_iter rrsig_spc, *rrsig;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-07-04 06:01:16 -05:00
|
|
|
uint8_t signer_spc[256], *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 */
|
2015-07-04 06:01:16 -05:00
|
|
|
keytag = gldns_calc_keytag_raw(dnskey->rr_i.rr_type + 10,
|
|
|
|
dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10);
|
|
|
|
|
|
|
|
for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset)
|
|
|
|
; rrsig ; rrsig = 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]
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Does the keytag match? */
|
|
|
|
&& gldns_read_uint16(rrsig->rr_i.rr_type + 26) == keytag
|
|
|
|
|
|
|
|
/* Does the signer name match? */
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (rdf = _getdns_rdf_iter_init_at(
|
2015-07-04 06:01:16 -05:00
|
|
|
&rdf_spc, &rrsig->rr_i, 7))
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (signer = _getdns_rdf_if_or_as_decompressed(
|
2015-07-04 06:01:16 -05:00
|
|
|
rdf, signer_spc, &signer_len))
|
|
|
|
|
|
|
|
&& _dname_equal(dnskey->rrset->name, signer)
|
2015-07-07 07:52:32 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
/* Does the signature verify? */
|
|
|
|
&& _getdns_verify_rrsig(rrset, rrsig, dnskey, nc_name)) {
|
|
|
|
|
|
|
|
debug_sec_print_rr("key ", &dnskey->rr_i);
|
|
|
|
debug_sec_print_rrset("signed ", rrset);
|
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
/* 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;
|
2015-07-04 06:01:16 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int find_nsec_covering_name(
|
|
|
|
getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *name, int *opt_out);
|
|
|
|
|
2015-07-07 04:15:38 -05:00
|
|
|
/* Returns whether a dnskey for keyset signed rrset. */
|
2015-07-04 06:01:16 -05:00
|
|
|
static int a_key_signed_rrset(getdns_rrset *keyset, getdns_rrset *rrset)
|
|
|
|
{
|
|
|
|
rrtype_iter dnskey_spc, *dnskey;
|
|
|
|
uint8_t *nc_name;
|
|
|
|
int keytag;
|
|
|
|
|
|
|
|
assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY);
|
|
|
|
|
|
|
|
for ( dnskey = rrtype_iter_init(&dnskey_spc, keyset)
|
|
|
|
; dnskey ; dnskey = rrtype_iter_next(dnskey) ) {
|
|
|
|
|
|
|
|
if (!(keytag = dnskey_signed_rrset(dnskey, rrset, &nc_name)))
|
|
|
|
continue;
|
2015-06-27 16:28:23 -05:00
|
|
|
|
|
|
|
if (!nc_name) /* Not a wildcard, then success! */
|
2015-07-02 10:30:37 -05:00
|
|
|
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] == '*')
|
2015-07-02 10:30:37 -05:00
|
|
|
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);
|
|
|
|
|
2015-06-28 06:41:48 -05:00
|
|
|
if (find_nsec_covering_name(keyset, rrset, nc_name, NULL))
|
2015-07-02 10:30:37 -05:00
|
|
|
return keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* Returns whether a DS in ds_set matches a dnskey in dnskey_set which in turn
|
|
|
|
* signed the dnskey set.
|
|
|
|
*/
|
2015-06-25 03:04:19 -05:00
|
|
|
static int ds_authenticates_keys(getdns_rrset *ds_set, getdns_rrset *dnskey_set)
|
|
|
|
{
|
|
|
|
rrtype_iter dnskey_spc, *dnskey;
|
|
|
|
rrtype_iter ds_spc, *ds;
|
|
|
|
uint16_t keytag;
|
2015-06-27 16:28:23 -05:00
|
|
|
uint8_t *nc_name;
|
2015-07-08 05:21:04 -05:00
|
|
|
ldns_rr *dnskey_l, *ds_gen_l, *ds_l;
|
|
|
|
size_t valid_dsses = 0, supported_dsses = 0;
|
|
|
|
uint8_t max_supported_digest = 0;
|
|
|
|
int max_supported_result = 0;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
|
|
|
assert(ds_set->rr_type == GETDNS_RRTYPE_DS);
|
|
|
|
assert(dnskey_set->rr_type == GETDNS_RRTYPE_DNSKEY);
|
2015-07-08 05:21:04 -05:00
|
|
|
|
|
|
|
/* The ds_set is already authenticated! */
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
if (!_dname_equal(ds_set->name, dnskey_set->name))
|
2015-06-25 03:04:19 -05:00
|
|
|
return 0;
|
|
|
|
|
2015-07-09 16:11:56 -05:00
|
|
|
debug_sec_print_rrset("ds_authenticates_keys DS: ", ds_set);
|
|
|
|
debug_sec_print_rrset("ds_authenticates_keys DNSKEY: ", dnskey_set);
|
|
|
|
|
2015-06-25 03:04:19 -05:00
|
|
|
for ( dnskey = rrtype_iter_init(&dnskey_spc, dnskey_set)
|
|
|
|
; dnskey ; dnskey = 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;
|
|
|
|
|
2015-06-25 03:04:19 -05:00
|
|
|
keytag = gldns_calc_keytag_raw(dnskey->rr_i.rr_type + 10,
|
|
|
|
dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10);
|
|
|
|
|
|
|
|
dnskey_l = NULL;
|
|
|
|
|
|
|
|
for ( ds = rrtype_iter_init(&ds_spc, ds_set)
|
|
|
|
; ds ; ds = rrtype_iter_next(ds)) {
|
|
|
|
|
2015-07-08 05:21:04 -05:00
|
|
|
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]
|
2015-06-25 03:04:19 -05:00
|
|
|
|
|
|
|
/* Does the keytag match? */
|
2015-06-27 16:28:23 -05:00
|
|
|
|| gldns_read_uint16(ds->rr_i.rr_type+10)!=keytag)
|
2015-07-08 05:21:04 -05:00
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
valid_dsses++;
|
|
|
|
|
|
|
|
if (/* Algorithm is not RSAMD5 (deprecated) */
|
|
|
|
ds->rr_i.rr_type[12] == GLDNS_RSAMD5
|
|
|
|
|
|
|
|
/* Algorithm is supported */
|
|
|
|
|| !ldns_key_algo_supported(ds->rr_i.rr_type[12]))
|
|
|
|
|
2015-06-27 16:28:23 -05:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!dnskey_l)
|
|
|
|
if (!(dnskey_l = rr2ldns_rr(&dnskey->rr_i)))
|
|
|
|
continue;
|
|
|
|
|
2015-07-08 05:21:04 -05:00
|
|
|
/* Unfortunately there is no ldns_ds_digest_supported()
|
|
|
|
* function. The only way to check if a digest type is
|
|
|
|
* supported, is by trying to hashing the key with the
|
|
|
|
* given digest type.
|
|
|
|
*/
|
|
|
|
if (!(ds_gen_l = ldns_key_rr2ds(dnskey_l,
|
|
|
|
ds->rr_i.rr_type[13])))
|
|
|
|
|
|
|
|
/* Hash algorithm not supported */
|
2015-06-27 16:28:23 -05:00
|
|
|
continue;
|
|
|
|
|
2015-07-09 16:11:56 -05:00
|
|
|
if (ldns_rr_rd_count(ds_gen_l) < 4) {
|
|
|
|
ldns_rr_free(ds_gen_l);
|
|
|
|
/* Hash algorithm not supported */
|
|
|
|
continue;
|
|
|
|
}
|
2015-07-08 05:21:04 -05:00
|
|
|
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)) {
|
|
|
|
ldns_rr_free(ds_gen_l);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
max_supported_digest = ds->rr_i.rr_type[13];
|
|
|
|
max_supported_result = 0;
|
|
|
|
|
|
|
|
if (!(ds_l = rr2ldns_rr(&ds->rr_i))) {
|
|
|
|
ldns_rr_free(ds_gen_l);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ldns_rr_compare(ds_l, ds_gen_l) != 0) {
|
|
|
|
/* No match */
|
2015-06-25 03:04:19 -05:00
|
|
|
ldns_rr_free(ds_l);
|
2015-07-08 05:21:04 -05:00
|
|
|
ldns_rr_free(ds_gen_l);
|
2015-06-27 16:28:23 -05:00
|
|
|
continue;
|
|
|
|
}
|
2015-07-08 05:21:04 -05:00
|
|
|
/* Match! */
|
|
|
|
ldns_rr_free(ds_l);
|
|
|
|
ldns_rr_free(ds_gen_l);
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-08 05:21:04 -05:00
|
|
|
if (!dnskey_signed_rrset(dnskey, dnskey_set, &nc_name)
|
|
|
|
|| nc_name /* No DNSKEY's on wildcards! */) {
|
2015-06-27 16:28:23 -05:00
|
|
|
|
2015-07-08 05:21:04 -05:00
|
|
|
debug_sec_print_rrset("keyset did not "
|
|
|
|
"authenticate: ", dnskey_set);
|
|
|
|
continue;
|
2015-06-27 16:28:23 -05:00
|
|
|
}
|
2015-06-27 16:47:47 -05:00
|
|
|
debug_sec_print_rrset(
|
2015-07-08 05:21:04 -05:00
|
|
|
"keyset authenticated: ", dnskey_set);
|
|
|
|
max_supported_result = SIGNATURE_VERIFIED | keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
ldns_rr_free(dnskey_l);
|
|
|
|
}
|
2015-07-09 16:11:56 -05:00
|
|
|
DEBUG_SEC("valid_dsses: %zu, supported_dsses: %zu\n",
|
|
|
|
valid_dsses, supported_dsses);
|
2015-07-08 05:21:04 -05:00
|
|
|
if (valid_dsses && !supported_dsses)
|
|
|
|
return NO_SUPPORTED_ALGORITHMS;
|
|
|
|
else
|
|
|
|
return max_supported_result;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
|
2015-07-04 06:01:16 -05:00
|
|
|
static int nsec_covers_name(
|
|
|
|
getdns_rrset *nsec, uint8_t *name, uint8_t **ce_name)
|
2015-06-23 09:41:34 -05:00
|
|
|
{
|
2015-07-04 06:01:16 -05:00
|
|
|
uint8_t owner_spc[256], *owner;
|
|
|
|
size_t owner_len = sizeof(owner_spc);
|
|
|
|
uint8_t next_spc[256], *next;
|
|
|
|
size_t next_len = sizeof(next_spc);
|
|
|
|
rrtype_iter rr_spc, *rr;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-07-04 06:01:16 -05:00
|
|
|
int nsec_cmp;
|
|
|
|
uint8_t *common1, *common2;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-07-03 15:50:29 -05:00
|
|
|
if (/* Get owner and next, nicely decompressed */
|
|
|
|
!(rr = rrtype_iter_init(&rr_spc, nsec))
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(rdf = _getdns_rdf_iter_init(&rdf_spc, &rr->rr_i))
|
|
|
|
|| !(owner = _getdns_owner_if_or_as_decompressed(
|
2015-06-25 17:26:40 -05:00
|
|
|
&rr->rr_i, owner_spc, &owner_len))
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(next = _getdns_rdf_if_or_as_decompressed(
|
2015-06-25 17:26:40 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
nsec_cmp = dname_compare(owner, next);
|
|
|
|
if (nsec_cmp < 0) {
|
2015-07-04 03:23:02 -05:00
|
|
|
/* Regular NSEC
|
|
|
|
* >= so it can match the wildcard
|
|
|
|
* (for wildcard NODATA proofs).
|
|
|
|
*/
|
2015-06-25 17:26:40 -05:00
|
|
|
return dname_compare(name, owner) >= 0
|
|
|
|
&& dname_compare(name, next) < 0;
|
2015-07-03 15:50:29 -05:00
|
|
|
|
2015-06-25 17:26:40 -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
|
2015-07-04 06:01:16 -05:00
|
|
|
&& _dname_is_parent(next, name) && dname_compare(next, name);
|
2015-07-03 15:50:29 -05:00
|
|
|
|
2015-06-25 17:26:40 -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.
|
2015-07-06 04:57:16 -05:00
|
|
|
* (also no zone.name == qname of course)
|
|
|
|
*/
|
|
|
|
return _dname_is_parent(owner, name) && dname_compare(owner, name);
|
|
|
|
}
|
2015-07-01 17:25:41 -05:00
|
|
|
}
|
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
static int nsec3_matches_name(getdns_rrset *nsec3, uint8_t *name)
|
|
|
|
{
|
2015-07-01 17:25:41 -05:00
|
|
|
uint8_t label[64], owner[64];
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-07-01 17:25:41 -05:00
|
|
|
if (name2nsec3_label(nsec3, name, label, sizeof(label))
|
|
|
|
&& _dname_label_copy(owner, nsec3->name, sizeof(owner)))
|
2015-06-25 17:26:40 -05:00
|
|
|
|
|
|
|
return *nsec3->name == label[0] /* Labels same size? */
|
2015-07-01 17:25:41 -05:00
|
|
|
&& memcmp(owner + 1, label + 1, label[0]) == 0;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-06-28 06:41:48 -05:00
|
|
|
static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name, int *opt_out)
|
2015-06-25 17:26:40 -05:00
|
|
|
{
|
|
|
|
uint8_t label[65], next[65], owner[65];
|
|
|
|
rrtype_iter rr_spc, *rr;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter rdf_spc, *rdf;
|
2015-06-25 17:26:40 -05:00
|
|
|
int nsz = 0, nsec_cmp;
|
|
|
|
|
|
|
|
if (!name2nsec3_label(nsec3, name, label, sizeof(label)-1))
|
|
|
|
return 0;
|
2015-07-04 03:23:02 -05:00
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
label[label[0]+1] = 0;
|
|
|
|
|
|
|
|
if ( !(rr = rrtype_iter_init(&rr_spc, nsec3))
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(rdf = _getdns_rdf_iter_init_at(&rdf_spc, &rr->rr_i, 4))
|
2015-06-25 17:26:40 -05:00
|
|
|
|| 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
|
2015-07-01 17:25:41 -05:00
|
|
|
|| !_dname_label_copy(owner, nsec3->name, sizeof(owner)-1)) {
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-06-27 16:28:23 -05:00
|
|
|
DEBUG_SEC("Error getting NSEC3 owner & next labels\n");
|
|
|
|
return 0;
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
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);
|
2015-07-04 03:23:02 -05:00
|
|
|
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).
|
|
|
|
*/
|
2015-06-25 17:26:40 -05:00
|
|
|
return dname_compare(label, owner) >= 0
|
|
|
|
&& dname_compare(label, next) < 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-27 16:28:23 -05:00
|
|
|
static int find_nsec_covering_name(
|
2015-06-28 06:41:48 -05:00
|
|
|
getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *name, int *opt_out)
|
2015-06-25 17:26:40 -05:00
|
|
|
{
|
|
|
|
rrset_iter i_spc, *i;
|
|
|
|
getdns_rrset *n;
|
2015-07-03 15:50:29 -05:00
|
|
|
rrtype_iter nsec_spc, *nsec_rr;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter bitmap_spc, *bitmap;
|
2015-07-02 10:30:37 -05:00
|
|
|
int keytag;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-06-28 06:41:48 -05:00
|
|
|
if (opt_out)
|
|
|
|
*opt_out = 0;
|
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
|
|
|
|
; i ; i = rrset_iter_next(i)) {
|
|
|
|
|
|
|
|
if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3
|
2015-07-04 03:23:02 -05:00
|
|
|
|
|
|
|
/* Get the bitmap rdata field */
|
|
|
|
&& (nsec_rr = rrtype_iter_init(&nsec_spc, n))
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (bitmap = _getdns_rdf_iter_init_at(
|
2015-07-04 03:23:02 -05:00
|
|
|
&bitmap_spc, &nsec_rr->rr_i, 5))
|
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
&& (keytag = a_key_signed_rrset(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)
|
|
|
|
)
|
|
|
|
)
|
2015-07-04 03:23:02 -05:00
|
|
|
)
|
|
|
|
)
|
2015-07-07 15:33:53 -05:00
|
|
|
) {
|
2015-06-25 17:26:40 -05:00
|
|
|
|
|
|
|
debug_sec_print_rrset("NSEC3: ", n);
|
|
|
|
debug_sec_print_dname("covered: ", name);
|
|
|
|
|
2015-07-02 10:30:37 -05:00
|
|
|
return keytag;
|
2015-06-27 16:28:23 -05:00
|
|
|
}
|
|
|
|
if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC
|
|
|
|
&& nsec_covers_name(n, name, NULL)
|
2015-07-03 15:50:29 -05:00
|
|
|
|
|
|
|
/* Get the bitmap rdata field */
|
|
|
|
&& (nsec_rr = rrtype_iter_init(&nsec_spc, n))
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (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.
|
|
|
|
*/
|
2015-07-04 06:01:16 -05:00
|
|
|
&& ( !_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.
|
|
|
|
*/
|
2015-07-04 06:01:16 -05:00
|
|
|
&& ( !_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)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2015-07-02 10:30:37 -05:00
|
|
|
&& (keytag = a_key_signed_rrset(dnskey, n))) {
|
2015-06-27 16:28:23 -05:00
|
|
|
|
|
|
|
debug_sec_print_rrset("NSEC: ", n);
|
|
|
|
debug_sec_print_dname("covered: ", name);
|
|
|
|
|
2015-07-02 10:30:37 -05:00
|
|
|
return keytag;
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-06-27 16:28:23 -05:00
|
|
|
static int nsec3_find_next_closer(
|
2015-07-04 03:23:02 -05:00
|
|
|
getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *nc_name, int *opt_out)
|
2015-06-25 17:26:40 -05:00
|
|
|
{
|
|
|
|
uint8_t wc_name[256] = { 1, (uint8_t)'*' };
|
2015-07-04 03:23:02 -05:00
|
|
|
int my_opt_out, keytag;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-07-04 03:23:02 -05:00
|
|
|
if (opt_out)
|
|
|
|
*opt_out = 0;
|
|
|
|
|
|
|
|
if (!(keytag = find_nsec_covering_name(
|
2015-07-04 06:14:16 -05:00
|
|
|
dnskey, rrset, nc_name, &my_opt_out))) {
|
|
|
|
/* 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.
|
|
|
|
*/
|
2015-06-25 17:26:40 -05:00
|
|
|
return 0;
|
2015-07-04 06:14:16 -05:00
|
|
|
}
|
2015-07-04 03:23:02 -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
|
|
|
*/
|
2015-07-07 15:33:53 -05:00
|
|
|
if (my_opt_out || keytag & NSEC3_ITERATION_COUNT_HIGH)
|
2015-07-02 10:30:37 -05:00
|
|
|
return keytag;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
|
|
|
nc_name += *nc_name + 1;
|
2015-07-07 15:33:53 -05:00
|
|
|
if (_dname_len(nc_name) > sizeof(wc_name) - 2)
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
(void) memcpy(wc_name + 2, nc_name, _dname_len(nc_name));
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-07-04 03:23:02 -05:00
|
|
|
return find_nsec_covering_name(dnskey, rrset, wc_name, opt_out);
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
|
|
|
|
2015-07-02 17:44:53 -05:00
|
|
|
/*
|
|
|
|
* Does a key from keyset dnskey prove the nonexistence of the (name, type)
|
|
|
|
* tuple in rrset?
|
|
|
|
*
|
2015-07-07 15:33:53 -05:00
|
|
|
* 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)
|
2015-07-02 17:44:53 -05:00
|
|
|
*/
|
2015-07-04 03:23:02 -05:00
|
|
|
static int key_proves_nonexistance(
|
|
|
|
getdns_rrset *keyset, getdns_rrset *rrset, int *opt_out)
|
2015-06-25 17:26:40 -05:00
|
|
|
{
|
2015-06-27 16:28:23 -05:00
|
|
|
getdns_rrset nsec_rrset, *cover, *ce;
|
2015-07-03 15:50:29 -05:00
|
|
|
rrtype_iter nsec_spc, *nsec_rr;
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rdf_iter bitmap_spc, *bitmap;
|
2015-06-25 17:26:40 -05:00
|
|
|
rrset_iter i_spc, *i;
|
|
|
|
uint8_t *ce_name, *nc_name;
|
2015-06-27 16:28:23 -05:00
|
|
|
uint8_t wc_name[256] = { 1, (uint8_t)'*' };
|
2015-07-02 10:30:37 -05:00
|
|
|
int keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-02 17:44:53 -05:00
|
|
|
assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY);
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 03:23:02 -05:00
|
|
|
if (opt_out)
|
2015-07-04 06:01:16 -05:00
|
|
|
*opt_out = 0;
|
2015-07-04 03:23:02 -05:00
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
/* 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;
|
2015-07-02 17:44:53 -05:00
|
|
|
|
|
|
|
if (/* A NSEC RR exists at the owner name of rrset */
|
2015-07-03 15:50:29 -05:00
|
|
|
(nsec_rr = rrtype_iter_init(&nsec_spc, &nsec_rrset))
|
2015-07-02 17:44:53 -05:00
|
|
|
|
|
|
|
/* Get the bitmap rdata field */
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (bitmap = _getdns_rdf_iter_init_at(
|
2015-07-02 17:44:53 -05:00
|
|
|
&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).
|
2015-07-04 03:23:02 -05:00
|
|
|
* Except for the root that is checked by itself.
|
2015-07-02 17:44:53 -05:00
|
|
|
*/
|
|
|
|
&& ( rrset->rr_type != GETDNS_RRTYPE_DS
|
2015-07-04 03:23:02 -05:00
|
|
|
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|
|
|
|
|| *rrset->name == 0
|
|
|
|
)
|
2015-07-02 17:44:53 -05:00
|
|
|
|
|
|
|
/* 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(keyset, &nsec_rrset))) {
|
2015-06-25 17:26:40 -05:00
|
|
|
|
|
|
|
debug_sec_print_rrset("NSEC NODATA proof for: ", rrset);
|
2015-07-02 10:30:37 -05:00
|
|
|
return keytag;
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
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
|
|
|
|
* qname, where qname > NSEC->name and ce(qname) is a subdomain
|
|
|
|
* of NXT. This case is handled below after the covering NSEC is
|
|
|
|
* found.
|
|
|
|
*
|
|
|
|
* - Or a wildcard match without the type. The wildcard owner name
|
|
|
|
* match has special handing in the find_nsec_covering_name function.
|
2015-07-07 15:33:53 -05:00
|
|
|
* We still expect a NSEC covering the name though.
|
2015-07-03 15:50:29 -05:00
|
|
|
*/
|
2015-06-25 17:26:40 -05:00
|
|
|
|
2015-06-27 16:28:23 -05:00
|
|
|
/* The NSEC Name error case
|
|
|
|
* ========================
|
2015-06-25 17:26:40 -05:00
|
|
|
* - First find the NSEC that covers the owner name.
|
|
|
|
*/
|
2015-06-25 03:04:19 -05:00
|
|
|
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
|
2015-06-25 17:26:40 -05:00
|
|
|
; i ; i = rrset_iter_next(i)) {
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-02 17:44:53 -05:00
|
|
|
cover = rrset_iter_value(i);
|
2015-07-03 15:50:29 -05:00
|
|
|
|
|
|
|
if (/* Is cover an NSEC rrset? */
|
2015-07-02 17:44:53 -05:00
|
|
|
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-02 17:44:53 -05:00
|
|
|
|
2015-07-03 15:50:29 -05:00
|
|
|
/* But not a match (because that would be NODATA case) */
|
2015-07-04 06:01:16 -05:00
|
|
|
|| _dname_equal(cover->name, rrset->name)
|
2015-07-02 17:44:53 -05:00
|
|
|
|
|
|
|
/* Get the bitmap rdata field */
|
2015-07-03 15:50:29 -05:00
|
|
|
|| !(nsec_rr = rrtype_iter_init(&nsec_spc, cover))
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(bitmap = _getdns_rdf_iter_init_at(
|
2015-07-02 17:44:53 -05:00
|
|
|
&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.
|
|
|
|
*/
|
2015-07-04 06:01:16 -05:00
|
|
|
|| ( _dname_is_parent(cover->name, rrset->name)
|
2015-07-02 17:44:53 -05:00
|
|
|
&& ( 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) */
|
2015-07-03 15:50:29 -05:00
|
|
|
|| !(keytag = a_key_signed_rrset(keyset, cover)))
|
2015-06-25 17:26:40 -05:00
|
|
|
continue;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
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);
|
2015-07-07 15:33:53 -05:00
|
|
|
|
|
|
|
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);
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-02 17:44:53 -05:00
|
|
|
return find_nsec_covering_name(keyset, rrset, wc_name, NULL);
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
/* The NSEC3 NODATA case
|
|
|
|
* =====================
|
|
|
|
* NSEC3 has same (hashed) ownername as the rrset to deny.
|
|
|
|
*/
|
|
|
|
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
|
|
|
|
; i ; i = rrset_iter_next(i)) {
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-04 03:23:02 -05:00
|
|
|
/* ce is potentially the NSEC3 that matches complete qname
|
|
|
|
* (so is also the closest encloser)
|
|
|
|
*/
|
|
|
|
ce = 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 = rrtype_iter_init(&nsec_spc, ce))
|
|
|
|
|
|
|
|
/* Get the bitmap rdata field */
|
2015-08-19 09:22:38 -05:00
|
|
|
&& (bitmap = _getdns_rdf_iter_init_at(
|
2015-07-04 03:23:02 -05:00
|
|
|
&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))
|
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
/* It must have a valid signature */
|
|
|
|
&& (keytag = a_key_signed_rrset(keyset, ce))
|
2015-07-04 03:23:02 -05:00
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
/* The qname must match the NSEC3 */
|
|
|
|
&& ( keytag & NSEC3_ITERATION_COUNT_HIGH
|
|
|
|
|| nsec3_matches_name(ce, rrset->name))) {
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
debug_sec_print_rrset("NSEC3 No Data for: ", rrset);
|
2015-07-02 10:30:37 -05:00
|
|
|
return keytag;
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
|
|
|
}
|
2015-07-04 03:23:02 -05:00
|
|
|
/* More NSEC3 NODATA cases
|
|
|
|
* ======================
|
|
|
|
* There are a few NSEC NODATA cases where qname doesn't match
|
|
|
|
* NSEC->name:
|
|
|
|
*
|
2015-07-07 15:33:53 -05:00
|
|
|
* - NSEC3 ownername match for qtype == NSEC3 (TODO?)
|
2015-07-04 03:23:02 -05:00
|
|
|
* - 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
|
|
|
|
* ========================+
|
2015-06-25 17:26:40 -05:00
|
|
|
* First find the closest encloser.
|
|
|
|
*/
|
|
|
|
for ( nc_name = rrset->name, ce_name = rrset->name + *rrset->name + 1
|
|
|
|
; *ce_name ; nc_name = ce_name, ce_name += *ce_name + 1) {
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
|
|
|
|
; i ; i = rrset_iter_next(i)) {
|
|
|
|
|
2015-06-27 16:28:23 -05:00
|
|
|
if ( (ce = rrset_iter_value(i))->rr_type
|
|
|
|
!= GETDNS_RRTYPE_NSEC3
|
2015-07-04 03:53:31 -05:00
|
|
|
|
|
|
|
/* Get the bitmap rdata field */
|
|
|
|
|| !(nsec_rr = rrtype_iter_init(&nsec_spc, ce))
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(bitmap = _getdns_rdf_iter_init_at(
|
2015-07-04 03:53:31 -05:00
|
|
|
&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?
|
2015-07-07 15:33:53 -05:00
|
|
|
* The NSEC could come from a parent zone!
|
|
|
|
*
|
2015-07-04 03:53:31 -05:00
|
|
|
*/
|
|
|
|
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_DNAME)
|
|
|
|
|| ( bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|
|
|
|
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|
|
|
|
)
|
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
|| !(keytag = a_key_signed_rrset(keyset, ce))
|
|
|
|
|| ( !(keytag & NSEC3_ITERATION_COUNT_HIGH)
|
|
|
|
&& !nsec3_matches_name(ce, ce_name)))
|
2015-06-27 16:28:23 -05:00
|
|
|
continue;
|
2015-06-25 17:26:40 -05:00
|
|
|
|
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);
|
|
|
|
|
2015-07-07 15:33:53 -05:00
|
|
|
if ( keytag & NSEC3_ITERATION_COUNT_HIGH
|
|
|
|
|| (keytag = nsec3_find_next_closer(
|
|
|
|
keyset, rrset, nc_name, opt_out)))
|
2015-07-04 03:23:02 -05:00
|
|
|
|
2015-07-02 10:30:37 -05:00
|
|
|
return keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
2015-06-25 17:26:40 -05:00
|
|
|
}
|
2015-06-25 03:04:19 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-14 03:49:00 -05:00
|
|
|
/* Ascend up to the root along chain_nodes. Try to find a keyset
|
2015-07-06 04:57:16 -05:00
|
|
|
* authenticated by a key in ta rrset (trust anchor). When we found one,
|
2015-07-14 03:49:00 -05:00
|
|
|
* descend back down, authenticating more specific keysets along the chain.
|
2015-07-06 04:57:16 -05:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-06-25 03:04:19 -05:00
|
|
|
static int chain_node_get_trusted_keys(
|
|
|
|
chain_node *node, getdns_rrset *ta, getdns_rrset **keys)
|
|
|
|
{
|
2015-07-02 10:30:37 -05:00
|
|
|
int s, keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-14 03:49:00 -05:00
|
|
|
/* Ascend up to the root */
|
2015-06-25 03:04:19 -05:00
|
|
|
if (! node)
|
|
|
|
return GETDNS_DNSSEC_BOGUS;
|
|
|
|
|
|
|
|
else if (ta->rr_type == GETDNS_RRTYPE_DS) {
|
|
|
|
|
2015-07-07 17:10:10 -05:00
|
|
|
if ((keytag = ds_authenticates_keys(ta, &node->dnskey))) {
|
2015-06-25 03:04:19 -05:00
|
|
|
*keys = &node->dnskey;
|
2015-07-02 10:30:37 -05:00
|
|
|
node->dnskey_signer = keytag;
|
2015-07-07 17:10:10 -05:00
|
|
|
return keytag & NO_SUPPORTED_ALGORITHMS
|
|
|
|
? GETDNS_DNSSEC_INSECURE
|
|
|
|
: GETDNS_DNSSEC_SECURE;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
} else if (ta->rr_type == GETDNS_RRTYPE_DNSKEY) {
|
|
|
|
|
|
|
|
/* ta is KSK */
|
2015-07-02 10:30:37 -05:00
|
|
|
if ((keytag = a_key_signed_rrset(ta, &node->dnskey))) {
|
2015-06-25 03:04:19 -05:00
|
|
|
*keys = &node->dnskey;
|
2015-07-02 10:30:37 -05:00
|
|
|
node->dnskey_signer = keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
return GETDNS_DNSSEC_SECURE;
|
|
|
|
}
|
2015-07-07 15:33:53 -05:00
|
|
|
/* ta is parent's ZSK */
|
2015-07-04 03:23:02 -05:00
|
|
|
if ((keytag = key_proves_nonexistance(ta, &node->ds, NULL))) {
|
2015-07-02 10:30:37 -05:00
|
|
|
node->ds_signer = keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
return GETDNS_DNSSEC_INSECURE;
|
2015-07-02 10:30:37 -05:00
|
|
|
}
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-02 10:30:37 -05:00
|
|
|
if ((keytag = a_key_signed_rrset(ta, &node->ds))) {
|
|
|
|
node->ds_signer = keytag;
|
2015-07-07 17:10:10 -05:00
|
|
|
if ((keytag = ds_authenticates_keys(
|
|
|
|
&node->ds, &node->dnskey))) {
|
2015-06-25 03:04:19 -05:00
|
|
|
*keys = &node->dnskey;
|
2015-07-02 10:30:37 -05:00
|
|
|
node->dnskey_signer = keytag;
|
2015-07-07 17:10:10 -05:00
|
|
|
return keytag & NO_SUPPORTED_ALGORITHMS
|
|
|
|
? GETDNS_DNSSEC_INSECURE
|
|
|
|
: GETDNS_DNSSEC_SECURE;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
return GETDNS_DNSSEC_BOGUS;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
return GETDNS_DNSSEC_BOGUS;
|
|
|
|
|
|
|
|
if (GETDNS_DNSSEC_SECURE !=
|
|
|
|
(s = chain_node_get_trusted_keys(node->parent, ta, keys)))
|
|
|
|
return s;
|
|
|
|
|
|
|
|
/* keys is an authenticated dnskey rrset always now (i.e. ZSK) */
|
|
|
|
ta = *keys;
|
2015-07-14 03:49:00 -05:00
|
|
|
/* Back down to the head */
|
2015-07-04 03:23:02 -05:00
|
|
|
if ((keytag = key_proves_nonexistance(ta, &node->ds, NULL))) {
|
2015-07-02 10:30:37 -05:00
|
|
|
node->ds_signer = keytag;
|
2015-06-25 03:04:19 -05:00
|
|
|
return GETDNS_DNSSEC_INSECURE;
|
2015-07-02 10:30:37 -05:00
|
|
|
}
|
2015-07-08 10:15:27 -05:00
|
|
|
if (key_matches_signer(ta, &node->ds)) {
|
|
|
|
|
|
|
|
if ((node->ds_signer = a_key_signed_rrset(ta, &node->ds)) &&
|
2015-07-07 17:10:10 -05:00
|
|
|
(keytag = ds_authenticates_keys(&node->ds, &node->dnskey))){
|
2015-06-25 03:04:19 -05:00
|
|
|
|
|
|
|
*keys = &node->dnskey;
|
2015-07-02 10:30:37 -05:00
|
|
|
node->dnskey_signer = keytag;
|
2015-07-07 17:10:10 -05:00
|
|
|
return keytag & NO_SUPPORTED_ALGORITHMS
|
|
|
|
? GETDNS_DNSSEC_INSECURE
|
|
|
|
: GETDNS_DNSSEC_SECURE;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
return GETDNS_DNSSEC_BOGUS;
|
|
|
|
}
|
2015-07-09 01:15:38 -05:00
|
|
|
/* 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 || rrset_has_rrs(&node->ds))
|
|
|
|
return GETDNS_DNSSEC_BOGUS;
|
|
|
|
|
|
|
|
/* Not at a zone cut, the trusted keyset must be authenticating
|
2015-07-14 03:49:00 -05:00
|
|
|
* something below (closer to head) this node.
|
2015-07-09 01:15:38 -05:00
|
|
|
*/
|
2015-06-25 03:04:19 -05:00
|
|
|
return GETDNS_DNSSEC_SECURE;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* 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.
|
|
|
|
*/
|
2015-06-25 03:04:19 -05:00
|
|
|
static int chain_head_validate_with_ta(chain_head *head, getdns_rrset *ta)
|
|
|
|
{
|
|
|
|
getdns_rrset *keys;
|
2015-07-04 03:23:02 -05:00
|
|
|
int s, keytag, opt_out;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-09 16:11:56 -05:00
|
|
|
debug_sec_print_rrset("validating ", &head->rrset);
|
|
|
|
debug_sec_print_rrset("with trust anchor ", ta);
|
|
|
|
|
2015-06-25 03:04:19 -05:00
|
|
|
if ((s = chain_node_get_trusted_keys(head->parent, ta, &keys))
|
|
|
|
!= GETDNS_DNSSEC_SECURE)
|
|
|
|
return s;
|
|
|
|
|
2015-06-25 17:26:40 -05:00
|
|
|
if (rrset_has_rrs(&head->rrset)) {
|
2015-07-02 10:30:37 -05:00
|
|
|
if ((keytag = a_key_signed_rrset(keys, &head->rrset))) {
|
|
|
|
head->signer = keytag;
|
2015-06-25 17:26:40 -05:00
|
|
|
return GETDNS_DNSSEC_SECURE;
|
2015-07-04 03:23:02 -05:00
|
|
|
|
|
|
|
} else if (!rrset_has_rrsigs(&head->rrset)
|
|
|
|
&& (keytag = key_proves_nonexistance(
|
|
|
|
keys, &head->rrset, &opt_out))
|
|
|
|
&& opt_out) {
|
|
|
|
|
|
|
|
head->signer = keytag;
|
|
|
|
return GETDNS_DNSSEC_INSECURE;
|
2015-07-02 10:30:37 -05:00
|
|
|
}
|
2015-07-04 03:23:02 -05:00
|
|
|
} else if ((keytag = key_proves_nonexistance(
|
|
|
|
keys, &head->rrset, &opt_out))) {
|
2015-07-02 10:30:37 -05:00
|
|
|
head->signer = keytag;
|
2015-07-07 15:33:53 -05:00
|
|
|
return opt_out || (keytag & NSEC3_ITERATION_COUNT_HIGH)
|
|
|
|
? GETDNS_DNSSEC_INSECURE : GETDNS_DNSSEC_SECURE;
|
2015-07-02 10:30:37 -05:00
|
|
|
}
|
2015-06-25 17:26:40 -05:00
|
|
|
return GETDNS_DNSSEC_BOGUS;
|
2015-06-25 03:04:19 -05:00
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* The DNSSEC status of the rrset in head is evaluated by trying the trust
|
|
|
|
* anchors in tas in turn. The best outcome counts.
|
|
|
|
*/
|
2015-06-25 03:04:19 -05:00
|
|
|
static int chain_head_validate(chain_head *head, rrset_iter *tas)
|
|
|
|
{
|
|
|
|
rrset_iter *i;
|
2015-07-07 17:10:10 -05:00
|
|
|
getdns_rrset *ta, dnskey_ta, ds_ta;
|
|
|
|
rrset_iter closest_ta;
|
|
|
|
int closest_labels, s = GETDNS_DNSSEC_INDETERMINATE;
|
2015-07-09 16:11:56 -05:00
|
|
|
size_t ta_labels, supported_algorithms;
|
2015-07-07 17:10:10 -05:00
|
|
|
rrtype_iter rr_spc, *rr;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-07 17:10:10 -05:00
|
|
|
/* Find the TA closest to the head's RRset name */
|
|
|
|
closest_labels = -1;
|
2015-06-25 03:04:19 -05:00
|
|
|
for (i = rrset_iter_rewind(tas); i ;i = rrset_iter_next(i)) {
|
|
|
|
ta = 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)
|
2015-07-09 16:11:56 -05:00
|
|
|
&& _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
|
|
|
|
2015-07-09 16:11:56 -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;
|
|
|
|
}
|
|
|
|
}
|
2015-07-09 16:11:56 -05:00
|
|
|
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;
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-07-07 17:10:10 -05:00
|
|
|
ta = rrset_iter_value(&closest_ta);
|
|
|
|
dnskey_ta = *ta;
|
|
|
|
dnskey_ta.rr_type = GETDNS_RRTYPE_DNSKEY;
|
|
|
|
ds_ta = *ta;
|
|
|
|
ds_ta.rr_type = GETDNS_RRTYPE_DS;
|
|
|
|
|
|
|
|
if (!rrset_has_rrs(&dnskey_ta))
|
|
|
|
return chain_head_validate_with_ta(head, &ds_ta);
|
|
|
|
|
|
|
|
/* Does the selected DNSKEY set have supported algorithms? */
|
|
|
|
supported_algorithms = 0;
|
|
|
|
for ( rr = rrtype_iter_init(&rr_spc, ta)
|
|
|
|
; rr; rr = rrtype_iter_next(rr)) {
|
|
|
|
|
|
|
|
if ( rr->rr_i.rr_type + 14 <= rr->rr_i.nxt
|
2015-07-07 17:18:19 -05:00
|
|
|
&& rr->rr_i.rr_type[13] != GLDNS_RSAMD5 /* Deprecated */
|
2015-07-07 17:10:10 -05:00
|
|
|
&& ldns_key_algo_supported(rr->rr_i.rr_type[13]))
|
|
|
|
|
|
|
|
supported_algorithms++;
|
|
|
|
}
|
|
|
|
if (!supported_algorithms) {
|
|
|
|
if (rrset_has_rrs(&ds_ta))
|
|
|
|
return chain_head_validate_with_ta(head, &ds_ta);
|
|
|
|
|
|
|
|
return GETDNS_DNSSEC_INSECURE;
|
|
|
|
}
|
|
|
|
s = chain_head_validate_with_ta(head, &dnskey_ta);
|
|
|
|
if (rrset_has_rrs(&ds_ta)) {
|
|
|
|
switch (chain_head_validate_with_ta(head, &ds_ta)) {
|
2015-06-25 03:04:19 -05:00
|
|
|
case GETDNS_DNSSEC_SECURE : s = GETDNS_DNSSEC_SECURE;
|
|
|
|
case GETDNS_DNSSEC_INSECURE: if (s != GETDNS_DNSSEC_SECURE)
|
|
|
|
s = GETDNS_DNSSEC_INSECURE;
|
2015-07-07 17:10:10 -05:00
|
|
|
break;
|
2015-06-25 03:04:19 -05:00
|
|
|
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;
|
2015-06-25 03:04:19 -05:00
|
|
|
default : break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* 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.
|
|
|
|
*/
|
|
|
|
static void chain_set_netreq_dnssec_status(chain_head *chain, rrset_iter *tas)
|
2015-07-01 14:50:47 -05:00
|
|
|
{
|
|
|
|
chain_head *head;
|
|
|
|
|
|
|
|
/* The netreq status is the worst for any head */
|
|
|
|
for (head = chain; head; head = head->next) {
|
2015-07-06 04:57:16 -05:00
|
|
|
if (!head->netreq)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (chain_head_validate(head, tas)) {
|
|
|
|
|
2015-07-01 14:50:47 -05:00
|
|
|
case GETDNS_DNSSEC_SECURE:
|
2015-07-06 04:57:16 -05:00
|
|
|
if (head->netreq->dnssec_status ==
|
|
|
|
GETDNS_DNSSEC_INDETERMINATE)
|
|
|
|
head->netreq->dnssec_status =
|
|
|
|
GETDNS_DNSSEC_SECURE;
|
2015-07-01 14:50:47 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case GETDNS_DNSSEC_INSECURE:
|
2015-07-06 04:57:16 -05:00
|
|
|
if (head->netreq->dnssec_status != GETDNS_DNSSEC_BOGUS)
|
|
|
|
head->netreq->dnssec_status =
|
|
|
|
GETDNS_DNSSEC_INSECURE;
|
2015-07-01 14:50:47 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case GETDNS_DNSSEC_BOGUS :
|
2015-07-06 04:57:16 -05:00
|
|
|
head->netreq->dnssec_status = GETDNS_DNSSEC_BOGUS;
|
2015-07-01 14:50:47 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
/* 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(chain_head *chain, rrset_iter *tas)
|
2015-06-23 09:41:34 -05:00
|
|
|
{
|
2015-07-06 04:57:16 -05:00
|
|
|
int s = GETDNS_DNSSEC_INDETERMINATE, t;
|
2015-06-23 09:41:34 -05:00
|
|
|
chain_head *head;
|
|
|
|
|
2015-06-29 15:23:01 -05:00
|
|
|
/* The netreq status is the worst for any head */
|
2015-06-23 09:41:34 -05:00
|
|
|
for (head = chain; head; head = head->next) {
|
2015-07-06 04:57:16 -05:00
|
|
|
t = chain_head_validate(head, tas);
|
|
|
|
switch (t) {
|
2015-06-29 15:23:01 -05:00
|
|
|
case GETDNS_DNSSEC_SECURE:
|
2015-07-06 04:57:16 -05:00
|
|
|
if (s == GETDNS_DNSSEC_INDETERMINATE)
|
|
|
|
s = GETDNS_DNSSEC_SECURE;
|
2015-06-29 15:23:01 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case GETDNS_DNSSEC_INSECURE:
|
2015-07-06 04:57:16 -05:00
|
|
|
if (s != GETDNS_DNSSEC_BOGUS)
|
|
|
|
s = GETDNS_DNSSEC_INSECURE;
|
2015-06-29 15:23:01 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case GETDNS_DNSSEC_BOGUS :
|
2015-07-06 04:57:16 -05:00
|
|
|
s = GETDNS_DNSSEC_BOGUS;
|
2015-06-29 15:23:01 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
2015-06-23 09:41:34 -05:00
|
|
|
}
|
|
|
|
}
|
2015-07-09 16:11:56 -05:00
|
|
|
DEBUG_SEC("chain_validate_dnssec() returning %d\n", s);
|
2015-07-06 04:57:16 -05:00
|
|
|
return s;
|
2015-06-23 09:41:34 -05:00
|
|
|
}
|
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
|
|
|
|
/**************** dnssec_return_validation_chain Extension ******************
|
|
|
|
*****************************************************************************/
|
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
static size_t count_outstanding_requests(chain_head *head)
|
2015-06-19 11:02:16 -05:00
|
|
|
{
|
2015-06-22 17:00:20 -05:00
|
|
|
size_t count;
|
|
|
|
chain_node *node;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
if (!head)
|
|
|
|
return 0;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
for ( node = head->parent, count = 0
|
|
|
|
; node
|
|
|
|
; node = node->parent) {
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
if (node->dnskey_req &&
|
|
|
|
node->dnskey_req->state != NET_REQ_FINISHED &&
|
|
|
|
node->dnskey_req->state != NET_REQ_CANCELED)
|
2015-06-22 17:00:20 -05:00
|
|
|
count++;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
if (node->ds_req &&
|
|
|
|
node->ds_req->state != NET_REQ_FINISHED &&
|
|
|
|
node->ds_req->state != NET_REQ_CANCELED)
|
2015-06-22 17:00:20 -05:00
|
|
|
count++;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
if (node->soa_req &&
|
|
|
|
node->soa_req->state != NET_REQ_FINISHED &&
|
|
|
|
node->soa_req->state != NET_REQ_CANCELED)
|
2015-06-22 17:00:20 -05:00
|
|
|
count++;
|
2015-06-19 11:02:16 -05:00
|
|
|
}
|
2015-06-22 17:00:20 -05:00
|
|
|
return count + count_outstanding_requests(head->next);
|
2015-06-19 11:02:16 -05:00
|
|
|
}
|
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
static void append_rrs2val_chain_list(getdns_context *ctxt,
|
2015-07-02 10:30:37 -05:00
|
|
|
getdns_list *val_chain_list, getdns_network_req *netreq, int signer)
|
2015-06-19 11:02:16 -05:00
|
|
|
{
|
2015-06-22 17:00:20 -05:00
|
|
|
rrset_iter *i, i_spc;
|
|
|
|
getdns_rrset *rrset;
|
|
|
|
rrtype_iter *rr, rr_spc;
|
|
|
|
rrsig_iter *rrsig, rrsig_spc;
|
|
|
|
getdns_dict *rr_dict;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
for ( i = rrset_iter_init(&i_spc,netreq->response,netreq->response_len)
|
|
|
|
; i
|
|
|
|
; i = rrset_iter_next(i)) {
|
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
rrset = rrset_iter_value(i);
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
if (rrset->rr_type != GETDNS_RRTYPE_DNSKEY &&
|
|
|
|
rrset->rr_type != GETDNS_RRTYPE_DS &&
|
|
|
|
rrset->rr_type != GETDNS_RRTYPE_NSEC &&
|
|
|
|
rrset->rr_type != GETDNS_RRTYPE_NSEC3)
|
|
|
|
continue;
|
2015-06-19 11:02:16 -05:00
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
for ( rr = rrtype_iter_init(&rr_spc, rrset)
|
|
|
|
; rr; rr = rrtype_iter_next(rr)) {
|
2014-02-03 16:34:55 -06:00
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
if (!(rr_dict = _getdns_rr_iter2rr_dict(
|
2015-06-30 07:43:52 -05:00
|
|
|
&ctxt->mf, &rr->rr_i)))
|
|
|
|
continue;
|
2014-02-03 16:34:55 -06:00
|
|
|
|
2015-08-19 09:30:15 -05:00
|
|
|
(void)_getdns_list_append_dict(val_chain_list, rr_dict);
|
2015-06-22 17:00:20 -05:00
|
|
|
getdns_dict_destroy(rr_dict);
|
|
|
|
}
|
|
|
|
for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset)
|
|
|
|
; rrsig; rrsig = rrsig_iter_next(rrsig)) {
|
2014-02-03 16:34:55 -06:00
|
|
|
|
2015-07-02 10:30:37 -05:00
|
|
|
if (/* No space for keytag & signer in rrsig rdata? */
|
|
|
|
rrsig->rr_i.nxt < rrsig->rr_i.rr_type + 28
|
|
|
|
|
|
|
|
/* We have a signer and it doesn't match? */
|
2015-07-08 05:21:04 -05:00
|
|
|
|| ((signer & 0xFFFF) &&
|
2015-07-02 10:30:37 -05:00
|
|
|
gldns_read_uint16(rrsig->rr_i.rr_type + 26)
|
|
|
|
!= (signer & 0xFFFF))
|
|
|
|
|
|
|
|
/* Could not convert to rr_dict */
|
2015-08-19 09:22:38 -05:00
|
|
|
|| !(rr_dict = _getdns_rr_iter2rr_dict(
|
2015-07-02 10:30:37 -05:00
|
|
|
&ctxt->mf, &rrsig->rr_i)))
|
2015-06-30 07:43:52 -05:00
|
|
|
continue;
|
2014-02-03 16:34:55 -06:00
|
|
|
|
2015-08-19 09:30:15 -05:00
|
|
|
(void)_getdns_list_append_dict(val_chain_list, rr_dict);
|
2015-06-22 17:00:20 -05:00
|
|
|
getdns_dict_destroy(rr_dict);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-02-03 16:34:55 -06:00
|
|
|
|
2015-07-09 01:15:38 -05:00
|
|
|
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 = 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);
|
|
|
|
|
2015-08-19 09:30:15 -05:00
|
|
|
(void)_getdns_list_append_dict(val_chain_list, rr_dict);
|
2015-07-09 01:15:38 -05:00
|
|
|
getdns_dict_destroy(rr_dict);
|
|
|
|
}
|
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
static void check_chain_complete(chain_head *chain)
|
2015-03-18 08:45:06 -05:00
|
|
|
{
|
2015-06-22 17:00:20 -05:00
|
|
|
getdns_dns_req *dnsreq;
|
2015-06-25 03:04:19 -05:00
|
|
|
getdns_context *context;
|
2015-06-22 17:00:20 -05:00
|
|
|
size_t o, node_count;
|
|
|
|
chain_head *head, *next;
|
|
|
|
chain_node *node;
|
|
|
|
getdns_list *val_chain_list;
|
|
|
|
getdns_dict *response_dict;
|
2015-06-25 03:04:19 -05:00
|
|
|
rrset_iter tas_iter;
|
2015-03-18 08:45:06 -05:00
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
if ((o = count_outstanding_requests(chain)) > 0) {
|
|
|
|
DEBUG_SEC("%zu outstanding requests\n", o);
|
2015-03-18 08:45:06 -05:00
|
|
|
return;
|
2015-06-22 17:00:20 -05:00
|
|
|
}
|
|
|
|
DEBUG_SEC("Chain done!\n");
|
2015-06-23 09:41:34 -05:00
|
|
|
dnsreq = chain->netreq->owner;
|
2015-06-25 03:04:19 -05:00
|
|
|
context = dnsreq->context;
|
2015-06-23 09:41:34 -05:00
|
|
|
|
|
|
|
#ifdef STUB_NATIVE_DNSSEC
|
2015-06-29 17:12:30 -05:00
|
|
|
/* Perform validation only on GETDNS_RESOLUTION_STUB (unbound_id == -1)
|
2015-07-04 03:23:02 -05:00
|
|
|
* Or when asked for the validation chain (to identify the RRSIGs that
|
|
|
|
* signed the RRSETs, so that only those will be included in the
|
|
|
|
* validation chain)
|
|
|
|
* In any case we must have a trust anchor.
|
2015-06-29 17:12:30 -05:00
|
|
|
*/
|
2015-07-04 03:23:02 -05:00
|
|
|
if (( chain->netreq->unbound_id == -1
|
|
|
|
|| dnsreq->dnssec_return_validation_chain)
|
|
|
|
&& context->trust_anchors)
|
|
|
|
|
2015-07-01 14:50:47 -05:00
|
|
|
chain_set_netreq_dnssec_status(chain,rrset_iter_init(&tas_iter,
|
2015-06-30 07:43:52 -05:00
|
|
|
context->trust_anchors, context->trust_anchors_len));
|
2015-07-04 03:23:02 -05:00
|
|
|
#else
|
|
|
|
if (dnsreq->dnssec_return_validation_chain
|
|
|
|
&& context->trust_anchors)
|
2015-06-22 17:00:20 -05:00
|
|
|
|
2015-07-09 01:15:38 -05:00
|
|
|
(void) chain_validate_dnssec(chain,rrset_iter_init(&tas_iter,
|
2015-07-04 03:23:02 -05:00
|
|
|
context->trust_anchors, context->trust_anchors_len));
|
|
|
|
#endif
|
2015-06-22 17:00:20 -05:00
|
|
|
val_chain_list = dnsreq->dnssec_return_validation_chain
|
2015-06-25 03:04:19 -05:00
|
|
|
? getdns_list_create_with_context(context) : NULL;
|
2015-06-22 17:00:20 -05:00
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
/* Walk chain to add values to val_chain_list and to cleanup */
|
2015-06-22 17:00:20 -05:00
|
|
|
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 ) {
|
|
|
|
|
2015-06-23 09:41:34 -05:00
|
|
|
if (node->dnskey_req) {
|
2015-07-02 10:30:37 -05:00
|
|
|
append_rrs2val_chain_list(
|
|
|
|
context, val_chain_list,
|
|
|
|
node->dnskey_req, node->dnskey_signer);
|
2015-08-19 09:33:19 -05:00
|
|
|
_getdns_dns_req_free(node->dnskey_req->owner);
|
2015-06-22 17:00:20 -05:00
|
|
|
}
|
2015-06-23 09:41:34 -05:00
|
|
|
if (node->ds_req) {
|
2015-07-02 10:30:37 -05:00
|
|
|
append_rrs2val_chain_list(
|
|
|
|
context, val_chain_list,
|
|
|
|
node->ds_req, node->ds_signer);
|
|
|
|
|
2015-07-09 01:15:38 -05:00
|
|
|
if (!node->ds_signer &&
|
|
|
|
!rrset_has_rrs(&node->ds)) {
|
|
|
|
/* Add empty DS, to prevent less
|
|
|
|
* specific to be able to authenticate
|
2015-07-14 03:49:00 -05:00
|
|
|
* below a zone cut (closer to head)
|
2015-07-09 01:15:38 -05:00
|
|
|
*/
|
|
|
|
append_empty_ds2val_chain_list(
|
|
|
|
context, val_chain_list,
|
|
|
|
&node->ds);
|
|
|
|
}
|
2015-08-19 09:33:19 -05:00
|
|
|
_getdns_dns_req_free(node->ds_req->owner);
|
2015-06-22 17:00:20 -05:00
|
|
|
}
|
2015-07-08 06:07:24 -05:00
|
|
|
if (node->soa_req) {
|
2015-08-19 09:33:19 -05:00
|
|
|
_getdns_dns_req_free(node->soa_req->owner);
|
2015-07-08 06:07:24 -05:00
|
|
|
}
|
2015-03-19 04:55:34 -05:00
|
|
|
}
|
2015-06-22 17:00:20 -05:00
|
|
|
GETDNS_FREE(head->my_mf, head);
|
|
|
|
}
|
2015-03-19 04:55:34 -05:00
|
|
|
|
2015-08-19 09:33:19 -05:00
|
|
|
response_dict = _getdns_create_getdns_response(dnsreq);
|
2015-06-22 17:00:20 -05:00
|
|
|
if (val_chain_list) {
|
|
|
|
(void) getdns_dict_set_list(
|
|
|
|
response_dict, "validation_chain", val_chain_list);
|
|
|
|
getdns_list_destroy(val_chain_list);
|
2015-03-18 08:45:06 -05:00
|
|
|
}
|
2015-06-25 03:04:19 -05:00
|
|
|
|
2015-06-22 17:00:20 -05:00
|
|
|
/* Final user callback */
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_call_user_callback(dnsreq, response_dict);
|
2015-03-18 08:45:06 -05:00
|
|
|
}
|
|
|
|
|
2014-02-03 16:34:55 -06:00
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
void _getdns_get_validation_chain(getdns_dns_req *dnsreq)
|
2015-06-22 17:00:20 -05:00
|
|
|
{
|
|
|
|
getdns_network_req *netreq, **netreq_p;
|
|
|
|
chain_head *chain = NULL;
|
|
|
|
|
2015-07-01 14:50:47 -05:00
|
|
|
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;
|
|
|
|
}
|
2015-07-01 14:50:47 -05:00
|
|
|
add_pkt2val_chain( &dnsreq->my_mf, &chain
|
|
|
|
, netreq->response, netreq->response_len
|
|
|
|
, netreq
|
|
|
|
);
|
2015-07-01 17:25:41 -05:00
|
|
|
add_question2val_chain( &dnsreq->my_mf, &chain
|
|
|
|
, netreq->response, netreq->response_len
|
|
|
|
, netreq->owner->name
|
|
|
|
, netreq->request_type
|
|
|
|
, netreq->request_class
|
|
|
|
, netreq
|
|
|
|
);
|
2015-07-01 14:50:47 -05:00
|
|
|
}
|
2015-06-22 17:00:20 -05:00
|
|
|
if (chain)
|
|
|
|
check_chain_complete(chain);
|
|
|
|
else
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_call_user_callback(dnsreq,
|
2015-08-19 09:33:19 -05:00
|
|
|
_getdns_create_getdns_response(dnsreq));
|
2015-06-22 17:00:20 -05:00
|
|
|
}
|
2015-07-06 04:57:16 -05:00
|
|
|
|
|
|
|
|
|
|
|
/******************* getdns_validate_dnssec() Function *********************
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
static int wire_validate_dnssec(uint8_t *to_val, size_t to_val_len,
|
|
|
|
uint8_t *support, size_t support_len, uint8_t *tas, size_t tas_len,
|
|
|
|
struct mem_funcs *mf)
|
|
|
|
{
|
|
|
|
chain_head *chain, *head, *next_head;
|
|
|
|
chain_node *node;
|
|
|
|
|
|
|
|
uint8_t qname_spc[256], *qname = NULL;
|
|
|
|
size_t qname_len = sizeof(qname_spc);
|
|
|
|
uint16_t qtype = 0, qclass = GETDNS_RRCLASS_IN;
|
|
|
|
|
2015-08-19 09:22:38 -05:00
|
|
|
_getdns_rr_iter rr_spc, *rr;
|
2015-07-02 08:31:31 -05:00
|
|
|
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.
|
|
|
|
*/
|
2015-08-19 09:22:38 -05:00
|
|
|
if ( (rr = _getdns_rr_iter_init(&rr_spc, to_val, to_val_len))
|
|
|
|
&& _getdns_rr_iter_section(rr) == GLDNS_SECTION_QUESTION
|
|
|
|
&& (qname = _getdns_owner_if_or_as_decompressed(
|
2015-07-02 08:31:31 -05:00
|
|
|
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(
|
|
|
|
chain, rrset_iter_init(&tas_iter, tas, tas_len));
|
|
|
|
|
|
|
|
/* Cleanup the chain */
|
|
|
|
for (head = chain; head; head = next_head) {
|
|
|
|
next_head = head->next;
|
|
|
|
GETDNS_FREE(*mf, head);
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
2015-06-22 17:00:20 -05:00
|
|
|
|
2015-07-01 14:50:47 -05:00
|
|
|
/*
|
|
|
|
* getdns_validate_dnssec
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
getdns_return_t
|
|
|
|
getdns_validate_dnssec(getdns_list *records_to_validate,
|
|
|
|
getdns_list *support_records,
|
|
|
|
getdns_list *trust_anchors)
|
2014-02-19 09:56:00 -06:00
|
|
|
{
|
2015-07-01 14:50:47 -05: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
|
|
|
|
2015-07-01 14:50:47 -05: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
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
int r = GETDNS_RETURN_MEMORY_ERROR;
|
2015-07-01 14:50:47 -05:00
|
|
|
struct mem_funcs *mf;
|
2015-03-18 08:45:06 -05:00
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
size_t i;
|
|
|
|
getdns_dict *reply;
|
2015-03-18 08:45:06 -05:00
|
|
|
|
2015-07-01 14:50:47 -05:00
|
|
|
#if defined(SEC_DEBUG) && SEC_DEBUG
|
|
|
|
fflush(stdout);
|
2015-03-18 15:43:41 -05:00
|
|
|
#endif
|
2015-03-18 08:45:06 -05:00
|
|
|
|
2015-07-01 14:50:47 -05:00
|
|
|
if (!records_to_validate || !support_records || !trust_anchors)
|
|
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
mf = &records_to_validate->mf;
|
2014-02-19 09:56:00 -06:00
|
|
|
|
2015-07-01 17:25:41 -05:00
|
|
|
/* First convert everything to wire format
|
2015-07-01 14:50:47 -05:00
|
|
|
*/
|
|
|
|
if (!(support = _getdns_list2wire(support_records,
|
|
|
|
support_buf, &support_len, mf)))
|
2015-07-02 08:31:31 -05:00
|
|
|
return GETDNS_RETURN_MEMORY_ERROR;
|
2014-02-19 09:56:00 -06:00
|
|
|
|
2015-07-01 14:50:47 -05:00
|
|
|
if (!(tas = _getdns_list2wire(trust_anchors,
|
|
|
|
tas_buf, &tas_len, mf)))
|
|
|
|
goto exit_free_support;
|
2014-02-19 09:56:00 -06:00
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
if (!(to_val = _getdns_list2wire(records_to_validate,
|
|
|
|
to_val_buf, &to_val_len, mf)))
|
2015-07-01 17:25:41 -05:00
|
|
|
goto exit_free_tas;
|
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
if ((r = wire_validate_dnssec(
|
|
|
|
to_val, to_val_len, support, support_len, tas, tas_len, mf)) !=
|
|
|
|
GETDNS_RETURN_GENERIC_ERROR)
|
|
|
|
goto exit_free_to_val;
|
2015-07-01 17:25:41 -05:00
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
for (i = 0; !getdns_list_get_dict(records_to_validate,i,&reply); i++) {
|
2014-02-19 09:56:00 -06:00
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
DEBUG_SEC("REPLY %zu, r: %d\n", i, r);
|
|
|
|
if (to_val != to_val_buf)
|
|
|
|
GETDNS_FREE(*mf, to_val);
|
|
|
|
to_val_len = sizeof(to_val_buf);
|
2015-07-01 17:25:41 -05:00
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
if (!(to_val = _getdns_reply2wire(
|
|
|
|
reply, to_val_buf, &to_val_len, mf)))
|
|
|
|
continue;
|
2014-02-19 09:56:00 -06:00
|
|
|
|
2015-07-09 16:11:56 -05:00
|
|
|
r = GETDNS_DNSSEC_INDETERMINATE;
|
2015-07-02 08:31:31 -05:00
|
|
|
switch (wire_validate_dnssec(
|
|
|
|
to_val, to_val_len, support, support_len, tas, tas_len, mf)) {
|
|
|
|
case GETDNS_DNSSEC_SECURE:
|
2015-07-09 16:11:56 -05:00
|
|
|
if (r == GETDNS_DNSSEC_INDETERMINATE)
|
2015-07-02 08:31:31 -05:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2015-07-02 08:31:31 -05:00
|
|
|
DEBUG_SEC("REPLY %zu, r: %d\n", i, r);
|
2014-02-19 09:56:00 -06:00
|
|
|
|
2015-07-02 08:31:31 -05:00
|
|
|
exit_free_to_val:
|
|
|
|
if (to_val != to_val_buf)
|
|
|
|
GETDNS_FREE(*mf, to_val);
|
2015-07-01 17:25:41 -05:00
|
|
|
exit_free_tas:
|
2015-07-01 14:50:47 -05:00
|
|
|
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;
|
2015-07-01 14:50:47 -05:00
|
|
|
}
|
2014-02-19 09:56:00 -06:00
|
|
|
|
2015-07-06 04:57:16 -05:00
|
|
|
|
|
|
|
/****************** 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)
|
2015-03-15 15:25:38 -05:00
|
|
|
!= LDNS_RR_TYPE_DS &&
|
2015-06-30 07:43:52 -05:00
|
|
|
gldns_wirerr_get_type(rr, len, dname_len)
|
2015-03-15 15:25:38 -05:00
|
|
|
!= LDNS_RR_TYPE_DNSKEY)
|
|
|
|
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 */
|