mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-22 10:10:06 +00:00
All databases in the codebase follow the same structure: a database is an associative container from DNS names to nodes, and each node is an associative container from RR types to RR data. Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has its own corresponding node type (qpznode, qpcnode, etc). However, some code needs to work with nodes generically regardless of their specific type - for example, to acquire locks, manage references, or register/unregister slabs from the heap. Currently, these generic node operations are implemented as methods in the database vtable, which creates problematic coupling between database and node lifetimes. If a node outlives its parent database, the node destructor will destroy all RR data, and each RR data destructor will try to unregister from heaps by calling a virtual function from the database vtable. Since the database was already freed, this causes a crash. This commit breaks the coupling by standardizing the layout of all database nodes, adding a dedicated vtable for node operations, and moving node-specific methods from the database vtable to the node vtable.
2018 lines
53 KiB
C
2018 lines
53 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <inttypes.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <isc/base32.h>
|
|
#include <isc/buffer.h>
|
|
#include <isc/heap.h>
|
|
#include <isc/iterated_hash.h>
|
|
#include <isc/log.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/region.h>
|
|
#include <isc/result.h>
|
|
#include <isc/types.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/db.h>
|
|
#include <dns/dbiterator.h>
|
|
#include <dns/dnssec.h>
|
|
#include <dns/fixedname.h>
|
|
#include <dns/keytable.h>
|
|
#include <dns/keyvalues.h>
|
|
#include <dns/name.h>
|
|
#include <dns/nsec.h>
|
|
#include <dns/nsec3.h>
|
|
#include <dns/rdata.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/rdatasetiter.h>
|
|
#include <dns/rdatastruct.h>
|
|
#include <dns/rdatatype.h>
|
|
#include <dns/secalg.h>
|
|
#include <dns/types.h>
|
|
#include <dns/zone.h>
|
|
#include <dns/zoneverify.h>
|
|
|
|
#include <dst/dst.h>
|
|
|
|
typedef struct vctx {
|
|
isc_mem_t *mctx;
|
|
dns_zone_t *zone;
|
|
dns_db_t *db;
|
|
dns_dbversion_t *ver;
|
|
dns_name_t *origin;
|
|
dns_keytable_t *secroots;
|
|
bool goodksk;
|
|
bool goodzsk;
|
|
dns_rdataset_t keyset;
|
|
dns_rdataset_t keysigs;
|
|
dns_rdataset_t soaset;
|
|
dns_rdataset_t soasigs;
|
|
dns_rdataset_t nsecset;
|
|
dns_rdataset_t nsecsigs;
|
|
dns_rdataset_t nsec3paramset;
|
|
dns_rdataset_t nsec3paramsigs;
|
|
unsigned char revoked_ksk[DST_MAX_ALGS];
|
|
unsigned char revoked_zsk[DST_MAX_ALGS];
|
|
unsigned char standby_ksk[DST_MAX_ALGS];
|
|
unsigned char standby_zsk[DST_MAX_ALGS];
|
|
unsigned char ksk_algorithms[DST_MAX_ALGS];
|
|
unsigned char zsk_algorithms[DST_MAX_ALGS];
|
|
unsigned char bad_algorithms[DST_MAX_ALGS];
|
|
unsigned char act_algorithms[DST_MAX_ALGS];
|
|
isc_heap_t *expected_chains;
|
|
isc_heap_t *found_chains;
|
|
} vctx_t;
|
|
|
|
struct nsec3_chain_fixed {
|
|
uint8_t hash;
|
|
uint8_t salt_length;
|
|
uint8_t next_length;
|
|
uint16_t iterations;
|
|
/*
|
|
* The following non-fixed-length data is stored in memory after the
|
|
* fields declared above for each NSEC3 chain element:
|
|
*
|
|
* unsigned char salt[salt_length];
|
|
* unsigned char owner[next_length];
|
|
* unsigned char next[next_length];
|
|
*/
|
|
};
|
|
|
|
/*
|
|
* Helper function used to calculate length of variable-length
|
|
* data section in object pointed to by 'chain'.
|
|
*/
|
|
static size_t
|
|
chain_length(struct nsec3_chain_fixed *chain) {
|
|
return chain->salt_length + 2 * chain->next_length;
|
|
}
|
|
|
|
/*%
|
|
* Log a zone verification error described by 'fmt' and the variable arguments
|
|
* following it. Either use dns_zone_logv() or print to stderr, depending on
|
|
* whether the function was invoked from within named or by a standalone tool,
|
|
* respectively.
|
|
*/
|
|
static void
|
|
zoneverify_log_error(const vctx_t *vctx, const char *fmt, ...) {
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
if (vctx->zone != NULL) {
|
|
dns_zone_logv(vctx->zone, DNS_LOGCATEGORY_GENERAL,
|
|
ISC_LOG_ERROR, NULL, fmt, ap);
|
|
} else {
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
va_end(ap);
|
|
}
|
|
|
|
static bool
|
|
is_delegation(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
|
uint32_t *ttlp) {
|
|
dns_rdataset_t nsset;
|
|
isc_result_t result;
|
|
|
|
if (dns_name_equal(name, vctx->origin)) {
|
|
return false;
|
|
}
|
|
|
|
dns_rdataset_init(&nsset);
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_ns, 0, 0, &nsset, NULL);
|
|
if (dns_rdataset_isassociated(&nsset)) {
|
|
if (ttlp != NULL) {
|
|
*ttlp = nsset.ttl;
|
|
}
|
|
dns_rdataset_disassociate(&nsset);
|
|
}
|
|
|
|
return result == ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*%
|
|
* Return true if version 'ver' of database 'db' contains a DNAME RRset at
|
|
* 'node'; return false otherwise.
|
|
*/
|
|
static bool
|
|
has_dname(const vctx_t *vctx, dns_dbnode_t *node) {
|
|
dns_rdataset_t dnameset;
|
|
isc_result_t result;
|
|
|
|
dns_rdataset_init(&dnameset);
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_dname, 0, 0, &dnameset,
|
|
NULL);
|
|
if (dns_rdataset_isassociated(&dnameset)) {
|
|
dns_rdataset_disassociate(&dnameset);
|
|
}
|
|
|
|
return result == ISC_R_SUCCESS;
|
|
}
|
|
|
|
static bool
|
|
goodsig(const vctx_t *vctx, dns_rdata_t *sigrdata, const dns_name_t *name,
|
|
dst_key_t **dstkeys, size_t nkeys, dns_rdataset_t *rdataset) {
|
|
dns_rdata_rrsig_t sig;
|
|
isc_result_t result;
|
|
dst_algorithm_t algorithm;
|
|
|
|
result = dns_rdata_tostruct(sigrdata, &sig, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
algorithm = dst_algorithm_fromdata(sig.algorithm, sig.signature,
|
|
sig.siglen);
|
|
|
|
for (size_t key = 0; key < nkeys; key++) {
|
|
if (algorithm != dst_key_alg(dstkeys[key]) ||
|
|
sig.keyid != dst_key_id(dstkeys[key]) ||
|
|
!dns_name_equal(&sig.signer, vctx->origin))
|
|
{
|
|
continue;
|
|
}
|
|
result = dns_dnssec_verify(name, rdataset, dstkeys[key], false,
|
|
vctx->mctx, sigrdata, NULL);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
nsec_bitmap_equal(dns_rdata_nsec_t *nsec, dns_rdata_t *rdata) {
|
|
isc_result_t result;
|
|
dns_rdata_nsec_t tmpnsec;
|
|
|
|
result = dns_rdata_tostruct(rdata, &tmpnsec, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (nsec->len != tmpnsec.len ||
|
|
memcmp(nsec->typebits, tmpnsec.typebits, nsec->len) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static isc_result_t
|
|
verifynsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
|
const dns_name_t *nextname, isc_result_t *vresult) {
|
|
unsigned char buffer[DNS_NSEC_BUFFERSIZE];
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char nextbuf[DNS_NAME_FORMATSIZE];
|
|
char found[DNS_NAME_FORMATSIZE];
|
|
dns_rdataset_t rdataset;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdata_t tmprdata = DNS_RDATA_INIT;
|
|
dns_rdata_nsec_t nsec;
|
|
isc_result_t result;
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_nsec, 0, 0, &rdataset, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
zoneverify_log_error(vctx, "Missing NSEC record for %s",
|
|
namebuf);
|
|
*vresult = ISC_R_FAILURE;
|
|
result = ISC_R_SUCCESS;
|
|
goto done;
|
|
}
|
|
|
|
result = dns_rdataset_first(&rdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_rdataset_first(): %s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &nsec, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
/* Check next name is consistent */
|
|
if (!dns_name_equal(&nsec.next, nextname)) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_name_format(nextname, nextbuf, sizeof(nextbuf));
|
|
dns_name_format(&nsec.next, found, sizeof(found));
|
|
zoneverify_log_error(vctx,
|
|
"Bad NSEC record for %s, next name "
|
|
"mismatch (expected:%s, found:%s)",
|
|
namebuf, nextbuf, found);
|
|
*vresult = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
/* Check bit map is consistent */
|
|
result = dns_nsec_buildrdata(vctx->db, vctx->ver, node, nextname,
|
|
buffer, &tmprdata);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_nsec_buildrdata(): %s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
if (!nsec_bitmap_equal(&nsec, &tmprdata)) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
zoneverify_log_error(vctx,
|
|
"Bad NSEC record for %s, bit map "
|
|
"mismatch",
|
|
namebuf);
|
|
*vresult = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
result = dns_rdataset_next(&rdataset);
|
|
if (result != ISC_R_NOMORE) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
zoneverify_log_error(vctx, "Multiple NSEC records for %s",
|
|
namebuf);
|
|
*vresult = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
*vresult = ISC_R_SUCCESS;
|
|
result = ISC_R_SUCCESS;
|
|
|
|
done:
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
check_no_rrsig(const vctx_t *vctx, const dns_rdataset_t *rdataset,
|
|
const dns_name_t *name, dns_dbnode_t *node) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
dns_rdataset_t sigrdataset = DNS_RDATASET_INIT;
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
|
isc_result_t result;
|
|
|
|
dns_rdataset_init(&sigrdataset);
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
DNS_RDATASETITER_FOREACH (rdsiter) {
|
|
dns_rdatasetiter_current(rdsiter, &sigrdataset);
|
|
if (sigrdataset.type == dns_rdatatype_rrsig &&
|
|
sigrdataset.covers == rdataset->type)
|
|
{
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf,
|
|
sizeof(typebuf));
|
|
zoneverify_log_error(
|
|
vctx,
|
|
"Warning: Found unexpected signatures "
|
|
"for %s/%s",
|
|
namebuf, typebuf);
|
|
break;
|
|
}
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(&sigrdataset)) {
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
}
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static bool
|
|
chain_compare(void *arg1, void *arg2) {
|
|
struct nsec3_chain_fixed *e1 = arg1, *e2 = arg2;
|
|
/*
|
|
* Do each element in turn to get a stable sort.
|
|
*/
|
|
if (e1->hash < e2->hash) {
|
|
return true;
|
|
}
|
|
if (e1->hash > e2->hash) {
|
|
return false;
|
|
}
|
|
if (e1->iterations < e2->iterations) {
|
|
return true;
|
|
}
|
|
if (e1->iterations > e2->iterations) {
|
|
return false;
|
|
}
|
|
if (e1->salt_length < e2->salt_length) {
|
|
return true;
|
|
}
|
|
if (e1->salt_length > e2->salt_length) {
|
|
return false;
|
|
}
|
|
if (e1->next_length < e2->next_length) {
|
|
return true;
|
|
}
|
|
if (e1->next_length > e2->next_length) {
|
|
return false;
|
|
}
|
|
if (memcmp(e1 + 1, e2 + 1, chain_length(e1)) < 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
chain_equal(const struct nsec3_chain_fixed *e1,
|
|
const struct nsec3_chain_fixed *e2, size_t data_length) {
|
|
if (e1->hash != e2->hash) {
|
|
return false;
|
|
}
|
|
if (e1->iterations != e2->iterations) {
|
|
return false;
|
|
}
|
|
if (e1->salt_length != e2->salt_length) {
|
|
return false;
|
|
}
|
|
if (e1->next_length != e2->next_length) {
|
|
return false;
|
|
}
|
|
|
|
return memcmp(e1 + 1, e2 + 1, data_length) == 0;
|
|
}
|
|
|
|
static void
|
|
record_nsec3(const vctx_t *vctx, const unsigned char *rawhash,
|
|
const dns_rdata_nsec3_t *nsec3, isc_heap_t *chains) {
|
|
struct nsec3_chain_fixed *element = NULL;
|
|
unsigned char *cp = NULL;
|
|
size_t len;
|
|
|
|
len = sizeof(*element) + nsec3->next_length * 2 + nsec3->salt_length;
|
|
|
|
element = isc_mem_get(vctx->mctx, len);
|
|
*element = (struct nsec3_chain_fixed){
|
|
.hash = nsec3->hash,
|
|
.salt_length = nsec3->salt_length,
|
|
.next_length = nsec3->next_length,
|
|
.iterations = nsec3->iterations,
|
|
};
|
|
cp = (unsigned char *)(element + 1);
|
|
memmove(cp, nsec3->salt, nsec3->salt_length);
|
|
cp += nsec3->salt_length;
|
|
memmove(cp, rawhash, nsec3->next_length);
|
|
cp += nsec3->next_length;
|
|
memmove(cp, nsec3->next, nsec3->next_length);
|
|
isc_heap_insert(chains, element);
|
|
}
|
|
|
|
/*
|
|
* Check whether any NSEC3 within 'rdataset' matches the parameters in
|
|
* 'nsec3param'.
|
|
*/
|
|
static isc_result_t
|
|
find_nsec3_match(const dns_rdata_nsec3param_t *nsec3param,
|
|
dns_rdataset_t *rdataset, size_t rhsize,
|
|
dns_rdata_nsec3_t *nsec3_match) {
|
|
/*
|
|
* Find matching NSEC3 record.
|
|
*/
|
|
DNS_RDATASET_FOREACH (rdataset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
dns_rdata_tostruct(&rdata, nsec3_match, NULL);
|
|
if (nsec3_match->hash == nsec3param->hash &&
|
|
nsec3_match->next_length == rhsize &&
|
|
nsec3_match->iterations == nsec3param->iterations &&
|
|
nsec3_match->salt_length == nsec3param->salt_length &&
|
|
memcmp(nsec3_match->salt, nsec3param->salt,
|
|
nsec3param->salt_length) == 0)
|
|
{
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return ISC_R_NOTFOUND;
|
|
}
|
|
|
|
static isc_result_t
|
|
match_nsec3(const vctx_t *vctx, const dns_name_t *name,
|
|
const dns_rdata_nsec3param_t *nsec3param, dns_rdataset_t *rdataset,
|
|
const unsigned char types[8192], unsigned int maxtype,
|
|
const unsigned char *rawhash, size_t rhsize,
|
|
isc_result_t *vresult) {
|
|
unsigned char cbm[8244];
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
dns_rdata_nsec3_t nsec3;
|
|
isc_result_t result;
|
|
unsigned int len;
|
|
|
|
result = find_nsec3_match(nsec3param, rdataset, rhsize, &nsec3);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
zoneverify_log_error(vctx, "Missing NSEC3 record for %s",
|
|
namebuf);
|
|
*vresult = result;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Check the type list.
|
|
*/
|
|
len = dns_nsec_compressbitmap(cbm, types, maxtype);
|
|
if (nsec3.len != len || memcmp(cbm, nsec3.typebits, len) != 0) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
zoneverify_log_error(vctx,
|
|
"Bad NSEC3 record for %s, bit map "
|
|
"mismatch",
|
|
namebuf);
|
|
*vresult = ISC_R_FAILURE;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Record chain.
|
|
*/
|
|
record_nsec3(vctx, rawhash, &nsec3, vctx->expected_chains);
|
|
|
|
/*
|
|
* Make sure there is only one NSEC3 record with this set of
|
|
* parameters.
|
|
*/
|
|
for (result = dns_rdataset_next(rdataset); result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(rdataset))
|
|
{
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
if (nsec3.hash == nsec3param->hash &&
|
|
nsec3.iterations == nsec3param->iterations &&
|
|
nsec3.salt_length == nsec3param->salt_length &&
|
|
memcmp(nsec3.salt, nsec3param->salt, nsec3.salt_length) ==
|
|
0)
|
|
{
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
zoneverify_log_error(vctx,
|
|
"Multiple NSEC3 records with the "
|
|
"same parameter set for %s",
|
|
namebuf);
|
|
*vresult = DNS_R_DUPLICATE;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
}
|
|
if (result != ISC_R_NOMORE) {
|
|
return result;
|
|
}
|
|
|
|
*vresult = ISC_R_SUCCESS;
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static bool
|
|
innsec3params(const dns_rdata_nsec3_t *nsec3, dns_rdataset_t *nsec3paramset) {
|
|
dns_rdata_nsec3param_t nsec3param;
|
|
isc_result_t result;
|
|
|
|
DNS_RDATASET_FOREACH (nsec3paramset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdataset_current(nsec3paramset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
if (nsec3param.flags == 0 && nsec3param.hash == nsec3->hash &&
|
|
nsec3param.iterations == nsec3->iterations &&
|
|
nsec3param.salt_length == nsec3->salt_length &&
|
|
memcmp(nsec3param.salt, nsec3->salt, nsec3->salt_length) ==
|
|
0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static isc_result_t
|
|
record_found(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
|
dns_rdataset_t *nsec3paramset) {
|
|
unsigned char owner[NSEC3_MAX_HASH_LENGTH];
|
|
dns_rdata_nsec3_t nsec3;
|
|
dns_rdataset_t rdataset;
|
|
dns_label_t hashlabel;
|
|
isc_buffer_t b;
|
|
isc_result_t result;
|
|
|
|
if (nsec3paramset == NULL || !dns_rdataset_isassociated(nsec3paramset))
|
|
{
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_nsec3, 0, 0, &rdataset,
|
|
NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
dns_name_getlabel(name, 0, &hashlabel);
|
|
isc_region_consume(&hashlabel, 1);
|
|
isc_buffer_init(&b, owner, sizeof(owner));
|
|
result = isc_base32hex_decoderegion(&hashlabel, &b);
|
|
if (result != ISC_R_SUCCESS) {
|
|
result = ISC_R_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
DNS_RDATASET_FOREACH (&rdataset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
if (nsec3.next_length != isc_buffer_usedlength(&b)) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We only care about NSEC3 records that match a NSEC3PARAM
|
|
* record.
|
|
*/
|
|
if (!innsec3params(&nsec3, nsec3paramset)) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Record chain.
|
|
*/
|
|
record_nsec3(vctx, owner, &nsec3, vctx->found_chains);
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
|
|
cleanup:
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
isoptout(const vctx_t *vctx, const dns_rdata_nsec3param_t *nsec3param,
|
|
bool *optout) {
|
|
dns_rdataset_t rdataset;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdata_nsec3_t nsec3;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t *hashname;
|
|
isc_result_t result;
|
|
dns_dbnode_t *node = NULL;
|
|
unsigned char rawhash[NSEC3_MAX_HASH_LENGTH];
|
|
size_t rhsize = sizeof(rawhash);
|
|
|
|
dns_fixedname_init(&fixed);
|
|
result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, vctx->origin,
|
|
vctx->origin, nsec3param->hash,
|
|
nsec3param->iterations, nsec3param->salt,
|
|
nsec3param->salt_length);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
hashname = dns_fixedname_name(&fixed);
|
|
result = dns_db_findnsec3node(vctx->db, hashname, false, &node);
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_nsec3, 0, 0,
|
|
&rdataset, NULL);
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
*optout = false;
|
|
result = ISC_R_SUCCESS;
|
|
goto done;
|
|
}
|
|
|
|
result = dns_rdataset_first(&rdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_rdataset_first(): %s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
|
|
result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
*optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0);
|
|
|
|
done:
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
verifynsec3(const vctx_t *vctx, const dns_name_t *name,
|
|
const dns_rdata_t *rdata, bool delegation, bool empty,
|
|
const unsigned char types[8192], unsigned int maxtype,
|
|
isc_result_t *vresult) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char hashbuf[DNS_NAME_FORMATSIZE];
|
|
dns_rdataset_t rdataset;
|
|
dns_rdata_nsec3param_t nsec3param;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t *hashname;
|
|
isc_result_t result, tvresult = ISC_R_UNSET;
|
|
dns_dbnode_t *node = NULL;
|
|
unsigned char rawhash[NSEC3_MAX_HASH_LENGTH];
|
|
size_t rhsize = sizeof(rawhash);
|
|
bool optout = false;
|
|
|
|
result = dns_rdata_tostruct(rdata, &nsec3param, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (nsec3param.flags != 0) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
if (!dns_nsec3_supportedhash(nsec3param.hash)) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
if (nsec3param.iterations > DNS_NSEC3_MAXITERATIONS) {
|
|
result = DNS_R_NSEC3ITERRANGE;
|
|
zoneverify_log_error(vctx, "verifynsec3: %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
|
|
result = isoptout(vctx, &nsec3param, &optout);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
dns_fixedname_init(&fixed);
|
|
result = dns_nsec3_hashname(
|
|
&fixed, rawhash, &rhsize, name, vctx->origin, nsec3param.hash,
|
|
nsec3param.iterations, nsec3param.salt, nsec3param.salt_length);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* We don't use dns_db_find() here as it works with the chosen
|
|
* nsec3 chain and we may also be called with uncommitted data
|
|
* from dnssec-signzone so the secure status of the zone may not
|
|
* be up to date.
|
|
*/
|
|
dns_rdataset_init(&rdataset);
|
|
hashname = dns_fixedname_name(&fixed);
|
|
result = dns_db_findnsec3node(vctx->db, hashname, false, &node);
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_nsec3, 0, 0,
|
|
&rdataset, NULL);
|
|
}
|
|
if (result != ISC_R_SUCCESS &&
|
|
(!delegation || (empty && !optout) ||
|
|
(!empty && dns_nsec_isset(types, dns_rdatatype_ds))))
|
|
{
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_name_format(hashname, hashbuf, sizeof(hashbuf));
|
|
zoneverify_log_error(vctx, "Missing NSEC3 record for %s (%s)",
|
|
namebuf, hashbuf);
|
|
} else if (result == ISC_R_NOTFOUND && delegation && (!empty || optout))
|
|
{
|
|
result = ISC_R_SUCCESS;
|
|
} else if (result == ISC_R_SUCCESS) {
|
|
result = match_nsec3(vctx, name, &nsec3param, &rdataset, types,
|
|
maxtype, rawhash, rhsize, &tvresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto done;
|
|
}
|
|
result = tvresult;
|
|
}
|
|
|
|
*vresult = result;
|
|
result = ISC_R_SUCCESS;
|
|
|
|
done:
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
verifynsec3s(const vctx_t *vctx, const dns_name_t *name,
|
|
dns_rdataset_t *nsec3paramset, bool delegation, bool empty,
|
|
const unsigned char types[8192], unsigned int maxtype,
|
|
isc_result_t *vresult) {
|
|
isc_result_t result = ISC_R_NOMORE;
|
|
|
|
DNS_RDATASET_FOREACH (nsec3paramset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdataset_current(nsec3paramset, &rdata);
|
|
result = verifynsec3(vctx, name, &rdata, delegation, empty,
|
|
types, maxtype, vresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
if (*vresult != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name,
|
|
dns_dbnode_t *node, dst_key_t **dstkeys, size_t nkeys) {
|
|
unsigned char set_algorithms[DST_MAX_ALGS] = { 0 };
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
dns_rdataset_t sigrdataset = DNS_RDATASET_INIT;
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
|
bool match = false;
|
|
isc_result_t result;
|
|
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
DNS_RDATASETITER_FOREACH (rdsiter) {
|
|
dns_rdatasetiter_current(rdsiter, &sigrdataset);
|
|
if (sigrdataset.type == dns_rdatatype_rrsig &&
|
|
sigrdataset.covers == rdataset->type)
|
|
{
|
|
match = true;
|
|
break;
|
|
}
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
}
|
|
|
|
if (!match) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
|
|
zoneverify_log_error(vctx, "No signatures for %s/%s", namebuf,
|
|
typebuf);
|
|
for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) {
|
|
if (vctx->act_algorithms[i] != 0) {
|
|
vctx->bad_algorithms[i] = 1;
|
|
}
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
goto done;
|
|
}
|
|
|
|
DNS_RDATASET_FOREACH (&sigrdataset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdata_rrsig_t sig;
|
|
dst_algorithm_t algorithm;
|
|
|
|
dns_rdataset_current(&sigrdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &sig, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
if (rdataset->ttl != sig.originalttl) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf,
|
|
sizeof(typebuf));
|
|
zoneverify_log_error(vctx,
|
|
"TTL mismatch for "
|
|
"%s %s keytag %u",
|
|
namebuf, typebuf, sig.keyid);
|
|
continue;
|
|
}
|
|
algorithm = dst_algorithm_fromdata(sig.algorithm, sig.signature,
|
|
sig.siglen);
|
|
if ((set_algorithms[algorithm] != 0) ||
|
|
(vctx->act_algorithms[algorithm] == 0))
|
|
{
|
|
continue;
|
|
}
|
|
if (goodsig(vctx, &rdata, name, dstkeys, nkeys, rdataset)) {
|
|
dns_rdataset_settrust(rdataset, dns_trust_secure);
|
|
dns_rdataset_settrust(&sigrdataset, dns_trust_secure);
|
|
set_algorithms[algorithm] = 1;
|
|
}
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
|
|
if (memcmp(set_algorithms, vctx->act_algorithms,
|
|
sizeof(set_algorithms)) != 0)
|
|
{
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
|
|
for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) {
|
|
if ((vctx->act_algorithms[i] != 0) &&
|
|
(set_algorithms[i] == 0))
|
|
{
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
|
zoneverify_log_error(vctx,
|
|
"No correct %s signature "
|
|
"for %s %s",
|
|
algbuf, namebuf, typebuf);
|
|
vctx->bad_algorithms[i] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (dns_rdataset_isassociated(&sigrdataset)) {
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
}
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
verifynode(vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
|
bool delegation, dst_key_t **dstkeys, size_t nkeys,
|
|
dns_rdataset_t *nsecset, dns_rdataset_t *nsec3paramset,
|
|
const dns_name_t *nextname, isc_result_t *vresult) {
|
|
unsigned char types[8192] = { 0 };
|
|
unsigned int maxtype = 0;
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
|
isc_result_t result, tvresult = ISC_R_UNSET;
|
|
|
|
REQUIRE(vresult != NULL || (nsecset == NULL && nsec3paramset == NULL));
|
|
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
|
|
DNS_RDATASETITER_FOREACH (rdsiter) {
|
|
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
|
dns_rdatasetiter_current(rdsiter, &rdataset);
|
|
|
|
/*
|
|
* If we are not at a delegation then everything should be
|
|
* signed. If we are at a delegation then only the DS set
|
|
* is signed. The NS set is not signed at a delegation but
|
|
* its existence is recorded in the bit map. Anything else
|
|
* other than NSEC and DS is not signed at a delegation.
|
|
*/
|
|
if (rdataset.type != dns_rdatatype_rrsig &&
|
|
(!delegation || rdataset.type == dns_rdatatype_ds ||
|
|
rdataset.type == dns_rdatatype_nsec))
|
|
{
|
|
result = verifyset(vctx, &rdataset, name, node, dstkeys,
|
|
nkeys);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
return result;
|
|
}
|
|
dns_nsec_setbit(types, rdataset.type, 1);
|
|
if (rdataset.type > maxtype) {
|
|
maxtype = rdataset.type;
|
|
}
|
|
} else if (rdataset.type != dns_rdatatype_rrsig) {
|
|
if (rdataset.type == dns_rdatatype_ns) {
|
|
dns_nsec_setbit(types, rdataset.type, 1);
|
|
if (rdataset.type > maxtype) {
|
|
maxtype = rdataset.type;
|
|
}
|
|
}
|
|
result = check_no_rrsig(vctx, &rdataset, name, node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
return result;
|
|
}
|
|
} else {
|
|
dns_nsec_setbit(types, rdataset.type, 1);
|
|
if (rdataset.type > maxtype) {
|
|
maxtype = rdataset.type;
|
|
}
|
|
}
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
if (vresult == NULL) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
*vresult = ISC_R_SUCCESS;
|
|
|
|
if (nsecset != NULL && dns_rdataset_isassociated(nsecset)) {
|
|
result = verifynsec(vctx, name, node, nextname, &tvresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
*vresult = tvresult;
|
|
}
|
|
|
|
if (nsec3paramset != NULL && dns_rdataset_isassociated(nsec3paramset)) {
|
|
result = verifynsec3s(vctx, name, nsec3paramset, delegation,
|
|
false, types, maxtype, &tvresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
*vresult = tvresult;
|
|
}
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
is_empty(const vctx_t *vctx, dns_dbnode_t *node) {
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
|
isc_result_t result;
|
|
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
result = dns_rdatasetiter_first(rdsiter);
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
check_no_nsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node) {
|
|
bool nsec_exists = false;
|
|
dns_rdataset_t rdataset;
|
|
isc_result_t result;
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_nsec, 0, 0, &rdataset, NULL);
|
|
if (result != ISC_R_NOTFOUND) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
zoneverify_log_error(vctx, "unexpected NSEC RRset at %s",
|
|
namebuf);
|
|
nsec_exists = true;
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
|
|
return nsec_exists ? ISC_R_FAILURE : ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
free_element(isc_mem_t *mctx, struct nsec3_chain_fixed *e) {
|
|
size_t len;
|
|
|
|
len = sizeof(*e) + e->salt_length + 2 * e->next_length;
|
|
isc_mem_put(mctx, e, len);
|
|
}
|
|
|
|
static void
|
|
free_element_heap(void *element, void *uap) {
|
|
struct nsec3_chain_fixed *e = (struct nsec3_chain_fixed *)element;
|
|
isc_mem_t *mctx = (isc_mem_t *)uap;
|
|
|
|
free_element(mctx, e);
|
|
}
|
|
|
|
static bool
|
|
_checknext(const vctx_t *vctx, const struct nsec3_chain_fixed *first,
|
|
const struct nsec3_chain_fixed *e) {
|
|
char buf[512];
|
|
const unsigned char *d1 = (const unsigned char *)(first + 1);
|
|
const unsigned char *d2 = (const unsigned char *)(e + 1);
|
|
isc_buffer_t b;
|
|
isc_region_t sr;
|
|
|
|
d1 += first->salt_length + first->next_length;
|
|
d2 += e->salt_length;
|
|
|
|
if (memcmp(d1, d2, first->next_length) == 0) {
|
|
return true;
|
|
}
|
|
|
|
sr.base = UNCONST(d1 - first->next_length);
|
|
sr.length = first->next_length;
|
|
isc_buffer_init(&b, buf, sizeof(buf));
|
|
isc_base32hex_totext(&sr, 1, "", &b);
|
|
zoneverify_log_error(vctx, "Break in NSEC3 chain at: %.*s",
|
|
(int)isc_buffer_usedlength(&b), buf);
|
|
|
|
sr.base = UNCONST(d1);
|
|
sr.length = first->next_length;
|
|
isc_buffer_init(&b, buf, sizeof(buf));
|
|
isc_base32hex_totext(&sr, 1, "", &b);
|
|
zoneverify_log_error(vctx, "Expected: %.*s",
|
|
(int)isc_buffer_usedlength(&b), buf);
|
|
|
|
sr.base = UNCONST(d2);
|
|
sr.length = first->next_length;
|
|
isc_buffer_init(&b, buf, sizeof(buf));
|
|
isc_base32hex_totext(&sr, 1, "", &b);
|
|
zoneverify_log_error(vctx, "Found: %.*s",
|
|
(int)isc_buffer_usedlength(&b), buf);
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
checknext(isc_mem_t *mctx, const vctx_t *vctx,
|
|
const struct nsec3_chain_fixed *first, struct nsec3_chain_fixed *prev,
|
|
const struct nsec3_chain_fixed *cur) {
|
|
bool result = _checknext(vctx, prev, cur);
|
|
|
|
if (prev != first) {
|
|
free_element(mctx, prev);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
checklast(isc_mem_t *mctx, const vctx_t *vctx, struct nsec3_chain_fixed *first,
|
|
struct nsec3_chain_fixed *prev) {
|
|
bool result = _checknext(vctx, prev, first);
|
|
if (prev != first) {
|
|
free_element(mctx, prev);
|
|
}
|
|
free_element(mctx, first);
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
verify_nsec3_chains(const vctx_t *vctx, isc_mem_t *mctx) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
struct nsec3_chain_fixed *e, *f = NULL;
|
|
struct nsec3_chain_fixed *first = NULL, *prev = NULL;
|
|
|
|
while ((e = isc_heap_element(vctx->expected_chains, 1)) != NULL) {
|
|
isc_heap_delete(vctx->expected_chains, 1);
|
|
if (f == NULL) {
|
|
f = isc_heap_element(vctx->found_chains, 1);
|
|
}
|
|
if (f != NULL) {
|
|
isc_heap_delete(vctx->found_chains, 1);
|
|
|
|
/*
|
|
* Check that they match.
|
|
*/
|
|
if (chain_equal(e, f, chain_length(e))) {
|
|
free_element(mctx, f);
|
|
f = NULL;
|
|
} else {
|
|
if (result == ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "Expected "
|
|
"and found "
|
|
"NSEC3 "
|
|
"chains not "
|
|
"equal");
|
|
}
|
|
result = ISC_R_FAILURE;
|
|
/*
|
|
* Attempt to resync found_chain.
|
|
*/
|
|
while (f != NULL && !chain_compare(e, f)) {
|
|
free_element(mctx, f);
|
|
f = isc_heap_element(vctx->found_chains,
|
|
1);
|
|
if (f != NULL) {
|
|
isc_heap_delete(
|
|
vctx->found_chains, 1);
|
|
}
|
|
if (f != NULL &&
|
|
chain_equal(e, f, chain_length(e)))
|
|
{
|
|
free_element(mctx, f);
|
|
f = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (result == ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "Expected and found NSEC3 "
|
|
"chains "
|
|
"not equal");
|
|
result = ISC_R_FAILURE;
|
|
}
|
|
|
|
if (first == NULL) {
|
|
prev = first = e;
|
|
} else if (!chain_equal(first, e, first->salt_length)) {
|
|
if (!checklast(mctx, vctx, first, prev)) {
|
|
result = ISC_R_FAILURE;
|
|
}
|
|
|
|
prev = first = e;
|
|
} else {
|
|
if (!checknext(mctx, vctx, first, prev, e)) {
|
|
result = ISC_R_FAILURE;
|
|
}
|
|
|
|
prev = e;
|
|
}
|
|
}
|
|
if (prev != NULL) {
|
|
if (!checklast(mctx, vctx, first, prev)) {
|
|
result = ISC_R_FAILURE;
|
|
}
|
|
}
|
|
do {
|
|
if (f != NULL) {
|
|
if (result == ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "Expected and found "
|
|
"NSEC3 chains not "
|
|
"equal");
|
|
result = ISC_R_FAILURE;
|
|
}
|
|
free_element(mctx, f);
|
|
}
|
|
f = isc_heap_element(vctx->found_chains, 1);
|
|
if (f != NULL) {
|
|
isc_heap_delete(vctx->found_chains, 1);
|
|
}
|
|
} while (f != NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
verifyemptynodes(const vctx_t *vctx, const dns_name_t *name,
|
|
const dns_name_t *prevname, bool isdelegation,
|
|
dns_rdataset_t *nsec3paramset, isc_result_t *vresult) {
|
|
dns_namereln_t reln;
|
|
int order;
|
|
unsigned int labels, nlabels, i;
|
|
dns_name_t suffix;
|
|
isc_result_t result, tvresult = ISC_R_UNSET;
|
|
|
|
*vresult = ISC_R_SUCCESS;
|
|
|
|
reln = dns_name_fullcompare(prevname, name, &order, &labels);
|
|
if (order >= 0) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
nlabels = dns_name_countlabels(name);
|
|
|
|
if (reln == dns_namereln_commonancestor ||
|
|
reln == dns_namereln_contains)
|
|
{
|
|
dns_name_init(&suffix);
|
|
for (i = labels + 1; i < nlabels; i++) {
|
|
dns_name_getlabelsequence(name, nlabels - i, i,
|
|
&suffix);
|
|
if (nsec3paramset != NULL &&
|
|
dns_rdataset_isassociated(nsec3paramset))
|
|
{
|
|
result = verifynsec3s(
|
|
vctx, &suffix, nsec3paramset,
|
|
isdelegation, true, NULL, 0, &tvresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
*vresult = tvresult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
vctx_init(vctx_t *vctx, isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db,
|
|
dns_dbversion_t *ver, dns_name_t *origin, dns_keytable_t *secroots) {
|
|
memset(vctx, 0, sizeof(*vctx));
|
|
|
|
vctx->mctx = mctx;
|
|
vctx->zone = zone;
|
|
vctx->db = db;
|
|
vctx->ver = ver;
|
|
vctx->origin = origin;
|
|
vctx->secroots = secroots;
|
|
vctx->goodksk = false;
|
|
vctx->goodzsk = false;
|
|
|
|
dns_rdataset_init(&vctx->keyset);
|
|
dns_rdataset_init(&vctx->keysigs);
|
|
dns_rdataset_init(&vctx->soaset);
|
|
dns_rdataset_init(&vctx->soasigs);
|
|
dns_rdataset_init(&vctx->nsecset);
|
|
dns_rdataset_init(&vctx->nsecsigs);
|
|
dns_rdataset_init(&vctx->nsec3paramset);
|
|
dns_rdataset_init(&vctx->nsec3paramsigs);
|
|
|
|
vctx->expected_chains = NULL;
|
|
isc_heap_create(mctx, chain_compare, NULL, 1024,
|
|
&vctx->expected_chains);
|
|
|
|
vctx->found_chains = NULL;
|
|
isc_heap_create(mctx, chain_compare, NULL, 1024, &vctx->found_chains);
|
|
}
|
|
|
|
static void
|
|
vctx_destroy(vctx_t *vctx) {
|
|
if (dns_rdataset_isassociated(&vctx->keyset)) {
|
|
dns_rdataset_disassociate(&vctx->keyset);
|
|
}
|
|
if (dns_rdataset_isassociated(&vctx->keysigs)) {
|
|
dns_rdataset_disassociate(&vctx->keysigs);
|
|
}
|
|
if (dns_rdataset_isassociated(&vctx->soaset)) {
|
|
dns_rdataset_disassociate(&vctx->soaset);
|
|
}
|
|
if (dns_rdataset_isassociated(&vctx->soasigs)) {
|
|
dns_rdataset_disassociate(&vctx->soasigs);
|
|
}
|
|
if (dns_rdataset_isassociated(&vctx->nsecset)) {
|
|
dns_rdataset_disassociate(&vctx->nsecset);
|
|
}
|
|
if (dns_rdataset_isassociated(&vctx->nsecsigs)) {
|
|
dns_rdataset_disassociate(&vctx->nsecsigs);
|
|
}
|
|
if (dns_rdataset_isassociated(&vctx->nsec3paramset)) {
|
|
dns_rdataset_disassociate(&vctx->nsec3paramset);
|
|
}
|
|
if (dns_rdataset_isassociated(&vctx->nsec3paramsigs)) {
|
|
dns_rdataset_disassociate(&vctx->nsec3paramsigs);
|
|
}
|
|
isc_heap_foreach(vctx->expected_chains, free_element_heap, vctx->mctx);
|
|
isc_heap_destroy(&vctx->expected_chains);
|
|
isc_heap_foreach(vctx->found_chains, free_element_heap, vctx->mctx);
|
|
isc_heap_destroy(&vctx->found_chains);
|
|
}
|
|
|
|
static isc_result_t
|
|
check_apex_rrsets(vctx_t *vctx) {
|
|
dns_dbnode_t *node = NULL;
|
|
isc_result_t result;
|
|
|
|
result = dns_db_findnode(vctx->db, vctx->origin, false, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx,
|
|
"failed to find the zone's origin: %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_dnskey, 0, 0, &vctx->keyset,
|
|
&vctx->keysigs);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "Zone contains no DNSSEC keys");
|
|
goto done;
|
|
}
|
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_soa, 0, 0, &vctx->soaset,
|
|
&vctx->soasigs);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "Zone contains no SOA record");
|
|
goto done;
|
|
}
|
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
dns_rdatatype_nsec, 0, 0, &vctx->nsecset,
|
|
&vctx->nsecsigs);
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
|
zoneverify_log_error(vctx, "NSEC lookup failed");
|
|
goto done;
|
|
}
|
|
|
|
result = dns_db_findrdataset(
|
|
vctx->db, node, vctx->ver, dns_rdatatype_nsec3param, 0, 0,
|
|
&vctx->nsec3paramset, &vctx->nsec3paramsigs);
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
|
zoneverify_log_error(vctx, "NSEC3PARAM lookup failed");
|
|
goto done;
|
|
}
|
|
|
|
if (!dns_rdataset_isassociated(&vctx->keysigs)) {
|
|
zoneverify_log_error(vctx, "DNSKEY is not signed "
|
|
"(keys offline or inactive?)");
|
|
result = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
if (!dns_rdataset_isassociated(&vctx->soasigs)) {
|
|
zoneverify_log_error(vctx, "SOA is not signed "
|
|
"(keys offline or inactive?)");
|
|
result = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->nsecset) &&
|
|
!dns_rdataset_isassociated(&vctx->nsecsigs))
|
|
{
|
|
zoneverify_log_error(vctx, "NSEC is not signed "
|
|
"(keys offline or inactive?)");
|
|
result = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->nsec3paramset) &&
|
|
!dns_rdataset_isassociated(&vctx->nsec3paramsigs))
|
|
{
|
|
zoneverify_log_error(vctx, "NSEC3PARAM is not signed "
|
|
"(keys offline or inactive?)");
|
|
result = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
if (!dns_rdataset_isassociated(&vctx->nsecset) &&
|
|
!dns_rdataset_isassociated(&vctx->nsec3paramset))
|
|
{
|
|
zoneverify_log_error(vctx, "No valid NSEC/NSEC3 chain for "
|
|
"testing");
|
|
result = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
done:
|
|
dns_db_detachnode(&node);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*%
|
|
* Update 'vctx' tables tracking active and standby key algorithms used in the
|
|
* verified zone based on the signatures made using 'dnskey' (prepared from
|
|
* 'rdata') found at zone apex. Set 'vctx->goodksk' or 'vctx->goodzsk' to true
|
|
* if 'dnskey' correctly signs the DNSKEY RRset at zone apex and either
|
|
* 'vctx->secroots' is NULL or 'dnskey' is present in 'vctx->secroots'.
|
|
*
|
|
* The variables to update are chosen based on 'is_ksk', which is true when
|
|
* 'dnskey' is a KSK and false otherwise.
|
|
*/
|
|
static void
|
|
check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey,
|
|
dns_rdata_t *keyrdata, bool is_ksk) {
|
|
unsigned char *active_keys = NULL, *standby_keys = NULL;
|
|
dns_keynode_t *keynode = NULL;
|
|
bool *goodkey = NULL;
|
|
dst_key_t *key = NULL;
|
|
isc_result_t result;
|
|
dns_rdataset_t dsset;
|
|
dst_algorithm_t algorithm;
|
|
|
|
active_keys = (is_ksk ? vctx->ksk_algorithms : vctx->zsk_algorithms);
|
|
standby_keys = (is_ksk ? vctx->standby_ksk : vctx->standby_zsk);
|
|
goodkey = (is_ksk ? &vctx->goodksk : &vctx->goodzsk);
|
|
algorithm = dst_algorithm_fromdata(dnskey->algorithm, dnskey->data,
|
|
dnskey->datalen);
|
|
|
|
/*
|
|
* First, does this key sign the DNSKEY rrset?
|
|
*/
|
|
if (!dns_dnssec_selfsigns(keyrdata, vctx->origin, &vctx->keyset,
|
|
&vctx->keysigs, false, vctx->mctx))
|
|
{
|
|
if (!is_ksk &&
|
|
dns_dnssec_signs(keyrdata, vctx->origin, &vctx->soaset,
|
|
&vctx->soasigs, false, vctx->mctx))
|
|
{
|
|
if (active_keys[algorithm] != DNS_KEYALG_MAX) {
|
|
active_keys[algorithm]++;
|
|
}
|
|
} else {
|
|
if (standby_keys[algorithm] != DNS_KEYALG_MAX) {
|
|
standby_keys[algorithm]++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (active_keys[algorithm] != DNS_KEYALG_MAX) {
|
|
active_keys[algorithm]++;
|
|
}
|
|
|
|
/*
|
|
* If a trust anchor table was not supplied, a correctly self-signed
|
|
* DNSKEY RRset is good enough.
|
|
*/
|
|
if (vctx->secroots == NULL) {
|
|
*goodkey = true;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Convert the supplied key rdata to dst_key_t. (If this
|
|
* fails we can't go further.)
|
|
*/
|
|
result = dns_dnssec_keyfromrdata(vctx->origin, keyrdata, vctx->mctx,
|
|
&key);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
/*
|
|
* Look up the supplied key in the trust anchor table.
|
|
* If we don't find an exact match, or if the keynode data
|
|
* is NULL, then we have neither a DNSKEY nor a DS format
|
|
* trust anchor, and can give up.
|
|
*/
|
|
result = dns_keytable_find(vctx->secroots, vctx->origin, &keynode);
|
|
if (result != ISC_R_SUCCESS) {
|
|
/* No such trust anchor */
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* If the keynode has any DS format trust anchors, that means
|
|
* it doesn't have any DNSKEY ones. So, we can check for a DS
|
|
* match and then stop.
|
|
*/
|
|
dns_rdataset_init(&dsset);
|
|
if (dns_keynode_dsset(keynode, &dsset)) {
|
|
DNS_RDATASET_FOREACH (&dsset) {
|
|
dns_rdata_t dsrdata = DNS_RDATA_INIT;
|
|
dns_rdata_t newdsrdata = DNS_RDATA_INIT;
|
|
unsigned char buf[DNS_DS_BUFFERSIZE];
|
|
dns_rdata_ds_t ds;
|
|
|
|
dns_rdata_reset(&dsrdata);
|
|
dns_rdataset_current(&dsset, &dsrdata);
|
|
result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (ds.key_tag != dst_key_id(key) ||
|
|
ds.algorithm !=
|
|
dst_algorithm_tosecalg(dst_key_alg(key)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result = dns_ds_buildrdata(vctx->origin, keyrdata,
|
|
ds.digest_type, buf,
|
|
sizeof(buf), &newdsrdata);
|
|
if (result != ISC_R_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
|
|
dns_rdataset_settrust(&vctx->keyset,
|
|
dns_trust_secure);
|
|
dns_rdataset_settrust(&vctx->keysigs,
|
|
dns_trust_secure);
|
|
*goodkey = true;
|
|
break;
|
|
}
|
|
}
|
|
dns_rdataset_disassociate(&dsset);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
if (keynode != NULL) {
|
|
dns_keynode_detach(&keynode);
|
|
}
|
|
if (key != NULL) {
|
|
dst_key_free(&key);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Check that the DNSKEY RR has at least one self signing KSK and one ZSK per
|
|
* algorithm in it (or, if -x was used, one self-signing KSK).
|
|
*/
|
|
static isc_result_t
|
|
check_dnskey(vctx_t *vctx) {
|
|
dns_rdata_dnskey_t dnskey;
|
|
isc_result_t result;
|
|
bool is_ksk;
|
|
|
|
DNS_RDATASET_FOREACH (&vctx->keyset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(&vctx->keyset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
is_ksk = ((dnskey.flags & DNS_KEYFLAG_KSK) != 0);
|
|
|
|
if ((dnskey.flags & DNS_KEYOWNER_ZONE) != 0 &&
|
|
(dnskey.flags & DNS_KEYFLAG_REVOKE) != 0)
|
|
{
|
|
dst_algorithm_t algorithm;
|
|
if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
|
|
!dns_dnssec_selfsigns(&rdata, vctx->origin,
|
|
&vctx->keyset, &vctx->keysigs,
|
|
false, vctx->mctx))
|
|
{
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char buffer[1024];
|
|
isc_buffer_t buf;
|
|
|
|
dns_name_format(vctx->origin, namebuf,
|
|
sizeof(namebuf));
|
|
isc_buffer_init(&buf, buffer, sizeof(buffer));
|
|
result = dns_rdata_totext(&rdata, NULL, &buf);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(
|
|
vctx, "dns_rdata_totext: %s",
|
|
isc_result_totext(result));
|
|
return ISC_R_FAILURE;
|
|
}
|
|
zoneverify_log_error(
|
|
vctx,
|
|
"revoked KSK is not self signed:\n"
|
|
"%s DNSKEY %.*s",
|
|
namebuf,
|
|
(int)isc_buffer_usedlength(&buf),
|
|
buffer);
|
|
return ISC_R_FAILURE;
|
|
}
|
|
algorithm = dst_algorithm_fromdata(
|
|
dnskey.algorithm, dnskey.data, dnskey.datalen);
|
|
if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
|
|
vctx->revoked_ksk[algorithm] != DNS_KEYALG_MAX)
|
|
{
|
|
vctx->revoked_ksk[algorithm]++;
|
|
} else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 &&
|
|
vctx->revoked_zsk[algorithm] !=
|
|
DNS_KEYALG_MAX)
|
|
{
|
|
vctx->revoked_zsk[algorithm]++;
|
|
}
|
|
} else {
|
|
check_dnskey_sigs(vctx, &dnskey, &rdata, is_ksk);
|
|
}
|
|
dns_rdata_freestruct(&dnskey);
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
determine_active_algorithms(vctx_t *vctx, bool ignore_kskflag,
|
|
bool keyset_kskonly,
|
|
void (*report)(const char *, ...)) {
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
|
|
report("Verifying the zone using the following algorithms:");
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->act_algorithms); i++) {
|
|
if (ignore_kskflag) {
|
|
vctx->act_algorithms[i] = (vctx->ksk_algorithms[i] !=
|
|
0 ||
|
|
vctx->zsk_algorithms[i] != 0)
|
|
? 1
|
|
: 0;
|
|
} else {
|
|
vctx->act_algorithms[i] = vctx->ksk_algorithms[i] != 0
|
|
? 1
|
|
: 0;
|
|
}
|
|
if (vctx->act_algorithms[i] != 0) {
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
|
report("- %s", algbuf);
|
|
}
|
|
}
|
|
|
|
if (ignore_kskflag || keyset_kskonly) {
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) {
|
|
/*
|
|
* The counts should both be zero or both be non-zero. Mark
|
|
* the algorithm as bad if this is not met.
|
|
*/
|
|
if ((vctx->ksk_algorithms[i] != 0) ==
|
|
(vctx->zsk_algorithms[i] != 0))
|
|
{
|
|
continue;
|
|
}
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
|
zoneverify_log_error(vctx, "Missing %s for algorithm %s",
|
|
(vctx->ksk_algorithms[i] != 0) ? "ZSK"
|
|
: "self-"
|
|
"signed "
|
|
"KSK",
|
|
algbuf);
|
|
vctx->bad_algorithms[i] = 1;
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Check that all the records not yet verified were signed by keys that are
|
|
* present in the DNSKEY RRset.
|
|
*/
|
|
static isc_result_t
|
|
verify_nodes(vctx_t *vctx, isc_result_t *vresult) {
|
|
dns_fixedname_t fname, fnextname, fprevname, fzonecut;
|
|
dns_name_t *name, *nextname, *prevname, *zonecut;
|
|
dns_dbnode_t *node = NULL, *nextnode;
|
|
dns_dbiterator_t *dbiter = NULL;
|
|
dst_key_t **dstkeys;
|
|
size_t count, nkeys = 0;
|
|
bool done = false;
|
|
isc_result_t tvresult = ISC_R_UNSET;
|
|
isc_result_t result;
|
|
|
|
name = dns_fixedname_initname(&fname);
|
|
nextname = dns_fixedname_initname(&fnextname);
|
|
dns_fixedname_init(&fprevname);
|
|
prevname = NULL;
|
|
dns_fixedname_init(&fzonecut);
|
|
zonecut = NULL;
|
|
|
|
count = dns_rdataset_count(&vctx->keyset);
|
|
dstkeys = isc_mem_cget(vctx->mctx, count, sizeof(*dstkeys));
|
|
|
|
DNS_RDATASET_FOREACH (&vctx->keyset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(&vctx->keyset, &rdata);
|
|
dstkeys[nkeys] = NULL;
|
|
result = dns_dnssec_keyfromrdata(vctx->origin, &rdata,
|
|
vctx->mctx, &dstkeys[nkeys]);
|
|
if (result == ISC_R_SUCCESS) {
|
|
nkeys++;
|
|
}
|
|
}
|
|
|
|
result = dns_db_createiterator(vctx->db, DNS_DB_NONSEC3, &dbiter);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_db_createiterator(): %s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
|
|
result = dns_dbiterator_first(dbiter);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_dbiterator_first(): %s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
|
|
while (!done) {
|
|
bool isdelegation = false;
|
|
|
|
result = dns_dbiterator_current(dbiter, &node, name);
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
|
|
zoneverify_log_error(vctx,
|
|
"dns_dbiterator_current(): %s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
if (!dns_name_issubdomain(name, vctx->origin)) {
|
|
result = check_no_nsec(vctx, name, node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_db_detachnode(&node);
|
|
goto done;
|
|
}
|
|
dns_db_detachnode(&node);
|
|
result = dns_dbiterator_next(dbiter);
|
|
if (result == ISC_R_NOMORE) {
|
|
done = true;
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx,
|
|
"dns_dbiterator_next(): "
|
|
"%s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
continue;
|
|
}
|
|
if (is_delegation(vctx, name, node, NULL)) {
|
|
zonecut = dns_fixedname_name(&fzonecut);
|
|
dns_name_copy(name, zonecut);
|
|
isdelegation = true;
|
|
} else if (has_dname(vctx, node)) {
|
|
zonecut = dns_fixedname_name(&fzonecut);
|
|
dns_name_copy(name, zonecut);
|
|
}
|
|
nextnode = NULL;
|
|
result = dns_dbiterator_next(dbiter);
|
|
while (result == ISC_R_SUCCESS) {
|
|
result = dns_dbiterator_current(dbiter, &nextnode,
|
|
nextname);
|
|
if (result != ISC_R_SUCCESS &&
|
|
result != DNS_R_NEWORIGIN)
|
|
{
|
|
zoneverify_log_error(vctx,
|
|
"dns_dbiterator_current():"
|
|
" %s",
|
|
isc_result_totext(result));
|
|
dns_db_detachnode(&node);
|
|
goto done;
|
|
}
|
|
if (!dns_name_issubdomain(nextname, vctx->origin) ||
|
|
(zonecut != NULL &&
|
|
dns_name_issubdomain(nextname, zonecut)))
|
|
{
|
|
result = check_no_nsec(vctx, nextname,
|
|
nextnode);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_db_detachnode(&node);
|
|
dns_db_detachnode(&nextnode);
|
|
goto done;
|
|
}
|
|
dns_db_detachnode(&nextnode);
|
|
result = dns_dbiterator_next(dbiter);
|
|
continue;
|
|
}
|
|
result = is_empty(vctx, nextnode);
|
|
dns_db_detachnode(&nextnode);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
break;
|
|
case ISC_R_NOMORE:
|
|
result = dns_dbiterator_next(dbiter);
|
|
continue;
|
|
default:
|
|
dns_db_detachnode(&node);
|
|
}
|
|
break;
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
done = true;
|
|
nextname = vctx->origin;
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx,
|
|
"iterating through the database "
|
|
"failed: %s",
|
|
isc_result_totext(result));
|
|
dns_db_detachnode(&node);
|
|
goto done;
|
|
}
|
|
result = verifynode(vctx, name, node, isdelegation, dstkeys,
|
|
nkeys, &vctx->nsecset, &vctx->nsec3paramset,
|
|
nextname, &tvresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_db_detachnode(&node);
|
|
goto done;
|
|
}
|
|
if (*vresult == ISC_R_UNSET) {
|
|
*vresult = ISC_R_SUCCESS;
|
|
}
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
*vresult = tvresult;
|
|
}
|
|
if (prevname != NULL) {
|
|
result = verifyemptynodes(
|
|
vctx, name, prevname, isdelegation,
|
|
&vctx->nsec3paramset, &tvresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_db_detachnode(&node);
|
|
goto done;
|
|
}
|
|
} else {
|
|
prevname = dns_fixedname_name(&fprevname);
|
|
}
|
|
dns_name_copy(name, prevname);
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
*vresult = tvresult;
|
|
}
|
|
dns_db_detachnode(&node);
|
|
}
|
|
|
|
dns_dbiterator_destroy(&dbiter);
|
|
|
|
result = dns_db_createiterator(vctx->db, DNS_DB_NSEC3ONLY, &dbiter);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "dns_db_createiterator(): %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
|
|
DNS_DBITERATOR_FOREACH (dbiter) {
|
|
result = dns_dbiterator_current(dbiter, &node, name);
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
|
|
zoneverify_log_error(vctx,
|
|
"dns_dbiterator_current(): %s",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
result = verifynode(vctx, name, node, false, dstkeys, nkeys,
|
|
NULL, NULL, NULL, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
zoneverify_log_error(vctx, "verifynode: %s",
|
|
isc_result_totext(result));
|
|
dns_db_detachnode(&node);
|
|
goto done;
|
|
}
|
|
result = record_found(vctx, name, node, &vctx->nsec3paramset);
|
|
dns_db_detachnode(&node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
done:
|
|
while (nkeys-- > 0U) {
|
|
dst_key_free(&dstkeys[nkeys]);
|
|
}
|
|
isc_mem_cput(vctx->mctx, dstkeys, count, sizeof(*dstkeys));
|
|
if (dbiter != NULL) {
|
|
dns_dbiterator_destroy(&dbiter);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
check_bad_algorithms(const vctx_t *vctx, void (*report)(const char *, ...)) {
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
bool first = true;
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->bad_algorithms); i++) {
|
|
if (vctx->bad_algorithms[i] == 0) {
|
|
continue;
|
|
}
|
|
if (first) {
|
|
report("The zone is not fully signed "
|
|
"for the following algorithms:");
|
|
}
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
|
report(" %s", algbuf);
|
|
first = false;
|
|
}
|
|
|
|
if (!first) {
|
|
report(".");
|
|
}
|
|
|
|
return first ? ISC_R_SUCCESS : ISC_R_FAILURE;
|
|
}
|
|
|
|
static void
|
|
print_summary(const vctx_t *vctx, bool keyset_kskonly,
|
|
void (*report)(const char *, ...)) {
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
|
|
report("Zone fully signed:");
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) {
|
|
if ((vctx->ksk_algorithms[i] == 0) &&
|
|
(vctx->standby_ksk[i] == 0) &&
|
|
(vctx->revoked_ksk[i] == 0) &&
|
|
(vctx->zsk_algorithms[i] == 0) &&
|
|
(vctx->standby_zsk[i] == 0) && (vctx->revoked_zsk[i] == 0))
|
|
{
|
|
continue;
|
|
}
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
|
report("Algorithm: %s: KSKs: "
|
|
"%u active, %u stand-by, %u revoked",
|
|
algbuf, vctx->ksk_algorithms[i], vctx->standby_ksk[i],
|
|
vctx->revoked_ksk[i]);
|
|
report("%*sZSKs: "
|
|
"%u active, %u %s, %u revoked",
|
|
(int)strlen(algbuf) + 13, "", vctx->zsk_algorithms[i],
|
|
vctx->standby_zsk[i],
|
|
keyset_kskonly ? "present" : "stand-by",
|
|
vctx->revoked_zsk[i]);
|
|
}
|
|
}
|
|
|
|
isc_result_t
|
|
dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
|
|
dns_name_t *origin, dns_keytable_t *secroots,
|
|
isc_mem_t *mctx, bool ignore_kskflag, bool keyset_kskonly,
|
|
void (*report)(const char *, ...)) {
|
|
const char *keydesc = (secroots == NULL ? "self-signed" : "trusted");
|
|
isc_result_t result, vresult = ISC_R_UNSET;
|
|
vctx_t vctx;
|
|
|
|
vctx_init(&vctx, mctx, zone, db, ver, origin, secroots);
|
|
|
|
result = check_apex_rrsets(&vctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
result = check_dnskey(&vctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
if (ignore_kskflag) {
|
|
if (!vctx.goodksk && !vctx.goodzsk) {
|
|
zoneverify_log_error(&vctx, "No %s DNSKEY found",
|
|
keydesc);
|
|
result = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
} else if (!vctx.goodksk) {
|
|
zoneverify_log_error(&vctx, "No %s KSK DNSKEY found", keydesc);
|
|
result = ISC_R_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
determine_active_algorithms(&vctx, ignore_kskflag, keyset_kskonly,
|
|
report);
|
|
|
|
result = verify_nodes(&vctx, &vresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
result = verify_nsec3_chains(&vctx, mctx);
|
|
if (vresult == ISC_R_UNSET) {
|
|
vresult = ISC_R_SUCCESS;
|
|
}
|
|
if (result != ISC_R_SUCCESS && vresult == ISC_R_SUCCESS) {
|
|
vresult = result;
|
|
}
|
|
|
|
result = check_bad_algorithms(&vctx, report);
|
|
if (result != ISC_R_SUCCESS) {
|
|
report("DNSSEC completeness test failed.");
|
|
goto done;
|
|
}
|
|
|
|
result = vresult;
|
|
if (result != ISC_R_SUCCESS) {
|
|
report("DNSSEC completeness test failed (%s).",
|
|
isc_result_totext(result));
|
|
goto done;
|
|
}
|
|
|
|
if (vctx.goodksk || ignore_kskflag) {
|
|
print_summary(&vctx, keyset_kskonly, report);
|
|
}
|
|
|
|
done:
|
|
vctx_destroy(&vctx);
|
|
|
|
return result;
|
|
}
|