2039 lines
54 KiB
C
2039 lines
54 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/log.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[256];
|
|
unsigned char revoked_zsk[256];
|
|
unsigned char standby_ksk[256];
|
|
unsigned char standby_zsk[256];
|
|
unsigned char ksk_algorithms[256];
|
|
unsigned char zsk_algorithms[256];
|
|
unsigned char bad_algorithms[256];
|
|
unsigned char act_algorithms[256];
|
|
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;
|
|
|
|
result = dns_rdata_tostruct(sigrdata, &sig, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
for (size_t key = 0; key < nkeys; key++) {
|
|
if (sig.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,
|
|
0, 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_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;
|
|
}
|
|
for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
|
|
result = dns_rdatasetiter_next(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) {
|
|
isc_result_t result;
|
|
|
|
/*
|
|
* Find matching NSEC3 record.
|
|
*/
|
|
for (result = dns_rdataset_first(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_match, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
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 result;
|
|
}
|
|
|
|
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;
|
|
|
|
for (result = dns_rdataset_first(nsec3paramset);
|
|
result == ISC_R_SUCCESS; result = dns_rdataset_next(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;
|
|
}
|
|
|
|
for (result = dns_rdataset_first(&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.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(vctx->db, &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(vctx->db, &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;
|
|
|
|
for (result = dns_rdataset_first(nsec3paramset);
|
|
result == ISC_R_SUCCESS; result = dns_rdataset_next(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;
|
|
}
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
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[256] = { 0 };
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
dns_rdataset_t sigrdataset;
|
|
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;
|
|
}
|
|
for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
|
|
result = dns_rdatasetiter_next(rdsiter))
|
|
{
|
|
dns_rdatasetiter_current(rdsiter, &sigrdataset);
|
|
if (sigrdataset.type == dns_rdatatype_rrsig &&
|
|
sigrdataset.covers == rdataset->type)
|
|
{
|
|
break;
|
|
}
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
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;
|
|
}
|
|
|
|
for (result = dns_rdataset_first(&sigrdataset); result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&sigrdataset))
|
|
{
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdata_rrsig_t sig;
|
|
|
|
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;
|
|
}
|
|
if ((set_algorithms[sig.algorithm] != 0) ||
|
|
(vctx->act_algorithms[sig.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[sig.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))
|
|
{
|
|
dns_secalg_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_rdataset_t rdataset;
|
|
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;
|
|
}
|
|
|
|
result = dns_rdatasetiter_first(rdsiter);
|
|
dns_rdataset_init(&rdataset);
|
|
while (result == ISC_R_SUCCESS) {
|
|
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);
|
|
result = dns_rdatasetiter_next(rdsiter);
|
|
}
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
if (result != ISC_R_NOMORE) {
|
|
zoneverify_log_error(vctx, "rdataset iteration failed: %s",
|
|
isc_result_totext(result));
|
|
return result;
|
|
}
|
|
|
|
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, NULL);
|
|
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(vctx->db, &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;
|
|
|
|
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);
|
|
|
|
/*
|
|
* 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[dnskey->algorithm] != DNS_KEYALG_MAX) {
|
|
active_keys[dnskey->algorithm]++;
|
|
}
|
|
} else {
|
|
if (standby_keys[dnskey->algorithm] != DNS_KEYALG_MAX) {
|
|
standby_keys[dnskey->algorithm]++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) {
|
|
active_keys[dnskey->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)) {
|
|
for (result = dns_rdataset_first(&dsset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&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_key_alg(key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result = dns_ds_buildrdata(vctx->origin, keyrdata,
|
|
ds.digest_type, 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_t rdata = DNS_RDATA_INIT;
|
|
dns_rdata_dnskey_t dnskey;
|
|
isc_result_t result;
|
|
bool is_ksk;
|
|
|
|
for (result = dns_rdataset_first(&vctx->keyset);
|
|
result == ISC_R_SUCCESS; result = dns_rdataset_next(&vctx->keyset))
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
|
|
vctx->revoked_ksk[dnskey.algorithm] !=
|
|
DNS_KEYALG_MAX)
|
|
{
|
|
vctx->revoked_ksk[dnskey.algorithm]++;
|
|
} else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 &&
|
|
vctx->revoked_zsk[dnskey.algorithm] !=
|
|
DNS_KEYALG_MAX)
|
|
{
|
|
vctx->revoked_zsk[dnskey.algorithm]++;
|
|
}
|
|
} else {
|
|
check_dnskey_sigs(vctx, &dnskey, &rdata, is_ksk);
|
|
}
|
|
dns_rdata_freestruct(&dnskey);
|
|
dns_rdata_reset(&rdata);
|
|
}
|
|
|
|
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) {
|
|
dns_secalg_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;
|
|
}
|
|
dns_secalg_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));
|
|
|
|
for (result = dns_rdataset_first(&vctx->keyset);
|
|
result == ISC_R_SUCCESS; result = dns_rdataset_next(&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(vctx->db, &node);
|
|
goto done;
|
|
}
|
|
dns_db_detachnode(vctx->db, &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(vctx->db, &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(vctx->db, &node);
|
|
dns_db_detachnode(vctx->db, &nextnode);
|
|
goto done;
|
|
}
|
|
dns_db_detachnode(vctx->db, &nextnode);
|
|
result = dns_dbiterator_next(dbiter);
|
|
continue;
|
|
}
|
|
result = is_empty(vctx, nextnode);
|
|
dns_db_detachnode(vctx->db, &nextnode);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
break;
|
|
case ISC_R_NOMORE:
|
|
result = dns_dbiterator_next(dbiter);
|
|
continue;
|
|
default:
|
|
dns_db_detachnode(vctx->db, &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(vctx->db, &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(vctx->db, &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(vctx->db, &node);
|
|
goto done;
|
|
}
|
|
} else {
|
|
prevname = dns_fixedname_name(&fprevname);
|
|
}
|
|
dns_name_copy(name, prevname);
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
*vresult = tvresult;
|
|
}
|
|
dns_db_detachnode(vctx->db, &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;
|
|
}
|
|
|
|
for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS;
|
|
result = dns_dbiterator_next(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(vctx->db, &node);
|
|
goto done;
|
|
}
|
|
result = record_found(vctx, name, node, &vctx->nsec3paramset);
|
|
dns_db_detachnode(vctx->db, &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:");
|
|
}
|
|
dns_secalg_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;
|
|
}
|
|
dns_secalg_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;
|
|
}
|