Check ZONEMD records when loading or transferring a zone

This commit is contained in:
Mark Andrews
2025-03-10 13:45:05 +11:00
parent d3fd1341e6
commit 17fb0463ee
18 changed files with 639 additions and 31 deletions
+1
View File
@@ -263,6 +263,7 @@ view \"_bind\" chaos {\n\
notify no;\n\
allow-new-zones no;\n\
max-cache-size 2M;\n\
zonemd-check no;\n\
\n\
# Prevent use of this zone in DNS amplified reflection DoS attacks\n\
rate-limit {\n\
@@ -42,6 +42,7 @@ zone "." {
allow-transfer { any; };
allow-update { any; };
allow-query { any; };
zonemd-check no;
};
include "trusted.conf";
@@ -284,6 +284,7 @@ zone "oldsigs.example" {
dnssec-policy jitter;
sig-signing-nodes 1000;
sig-signing-signatures 100;
zonemd-check no;
};
zone "prepub.example" {
@@ -329,6 +330,7 @@ zone "ttl4.example" {
zone "delay.example" {
type primary;
file "delay.example.db";
zonemd-check no;
};
zone "nozsk.example" {
@@ -337,6 +339,7 @@ zone "nozsk.example" {
allow-update { any; };
inline-signing no;
dnssec-policy autosign;
zonemd-check no;
};
zone "inaczsk.example" {
@@ -345,6 +348,7 @@ zone "inaczsk.example" {
allow-update { any; };
inline-signing no;
dnssec-policy autosign;
zonemd-check no;
};
zone "noksk.example" {
@@ -353,6 +357,7 @@ zone "noksk.example" {
allow-update { any; };
inline-signing no;
dnssec-policy autosign;
zonemd-check no;
};
zone "sync.example" {
+1 -1
View File
@@ -260,7 +260,7 @@ now="$(TZ=UTC date +%Y%m%d%H%M%S)"
check_expiry() (
$DIG $DIGOPTS AXFR oldsigs.example @10.53.0.3 >dig.out.test$n || return 1
nearest_expiration="$(awk '$4 == "RRSIG" { print $9 }' <dig.out.test$n | sort -n | head -1)"
if [ "$nearest_expiration" -le "$now" ]; then
if [ -z "$nearest_expiration" -o "$nearest_expiration" -le "$now" ]; then
echo_i "failed: $nearest_expiration <= $now"
return 1
fi
+1
View File
@@ -28,4 +28,5 @@ options {
zone "." {
type primary;
file "root.db";
zonemd-check no;
};
@@ -232,6 +232,7 @@ zone "peer.peer-ns-spoof" {
zone "dnskey-rrsigs-stripped" {
type primary;
file "dnskey-rrsigs-stripped.db.signed";
zonemd-check no;
};
zone "ds-rrsigs-stripped" {
+29
View File
@@ -182,17 +182,23 @@ zone "nsec3-unknown.example" {
type primary;
nsec3-test-zone yes;
file "nsec3-unknown.example.db.signed";
zonemd-check no;
};
zone "optout-unknown.example" {
type primary;
nsec3-test-zone yes;
file "optout-unknown.example.db.signed";
zonemd-check no;
};
zone "dnskey-unknown.example" {
type primary;
file "dnskey-unknown.example.db.signed";
/*
* ZONEMD: self validation failure SOA and DNSKEY
*/
zonemd-check no;
};
zone "digest-alg-unsupported.example" {
@@ -210,6 +216,10 @@ zone "ds-unsupported.example" {
zone "dnskey-unsupported.example" {
type primary;
file "dnskey-unsupported.example.db.signed";
/*
* ZONEMD: self validation failure SOA and DNSKEY
*/
zonemd-check no;
};
zone "dnskey-unsupported-2.example" {
@@ -221,6 +231,7 @@ zone "dnskey-nsec3-unknown.example" {
type primary;
nsec3-test-zone yes;
file "dnskey-nsec3-unknown.example.db.signed";
zonemd-check no;
};
zone "multiple.example" {
@@ -254,6 +265,7 @@ zone "expired.example" {
type primary;
allow-update { none; };
file "expired.example.db.signed";
zonemd-check no; /* signatures are expired */
};
zone "update-nsec3.example" {
@@ -334,12 +346,17 @@ zone "inline.example" {
zone "future.example" {
type primary;
file "future.example.db.signed";
zonemd-check no;
};
zone "managed-future.example" {
type primary;
file "managed-future.example.db.signed";
allow-update { any; };
/*
* ZONEMD: self validation failure SOA and DNSKEY
*/
zonemd-check no;
};
zone "revkey.example" {
@@ -375,11 +392,17 @@ zone "enabled.managed" {
zone "unsupported.managed" {
type primary;
file "unsupported.managed.db.signed";
/*
* ZONEMD: self validation failure SOA and DNSKEY
*/
zonemd-check no;
};
zone "revoked.managed" {
type primary;
file "revoked.managed.db.signed";
/* Only a revoked key in the zone. */
zonemd-check no;
};
zone "secure.trusted" {
@@ -400,11 +423,17 @@ zone "enabled.trusted" {
zone "unsupported.trusted" {
type primary;
file "unsupported.trusted.db.signed";
/*
* ZONEMD: self validation failure SOA and DNSKEY
*/
zonemd-check no;
};
zone "revoked.trusted" {
type primary;
file "revoked.trusted.db.signed";
/* Only a revoked key in the zone. */
zonemd-check no;
};
zone "too-many-iterations" {
@@ -48,12 +48,14 @@ zone "nsec3-unknown.example" {
type secondary;
primaries { 10.53.0.3; };
file "nsec3-unknown.example.bk";
zonemd-check no;
};
zone "optout-unknown.example" {
type secondary;
primaries { 10.53.0.3; };
file "optout-unknown.example.bk";
zonemd-check no;
};
zone "multiple.example" {
@@ -71,6 +73,7 @@ zone "split-rrsig" {
type primary;
file "split-rrsig.db.signed";
allow-update { any; };
zonemd-check no; /* signatures are expired */
};
include "trusted.conf";
@@ -27,4 +27,5 @@ options {
zone "." {
type primary;
file "root.db.signed";
zonemd-check no;
};
@@ -57,6 +57,7 @@ zone "verify-addzone" {
zone "verify-axfr" {
type primary;
file "verify-axfr.db.signed";
zonemd-check no;
};
zone "verify-csk" {
@@ -69,6 +70,7 @@ zone "verify-ixfr" {
file "verify-ixfr.db.signed";
ixfr-from-differences yes;
allow-transfer { 10.53.0.3; };
zonemd-check no;
};
zone "verify-reconfig" {
@@ -33,6 +33,7 @@ options {
trust-anchor-telemetry yes;
allow-new-zones yes;
dnssec-validation yes;
zonemd-check no;
};
zone "." {
@@ -10,6 +10,8 @@
; information regarding copyright ownership.
$TTL 10
zonemd.test. IN SOA zonemd.test. hostmaster.zonemd.test. 1 3600 900 2419200 3600
zonemd.test. IN NS zonemd.test.
zonemd.test. IN A 10.53.0.3
zonemd.test. IN SOA ns3.zonemd.test. hostmaster.zonemd.test. 1 3600 900 2419200 3600
zonemd.test. IN NS ns3.zonemd.test.
zonemd.test. IN NS ns5.zonemd.test.
ns3.zonemd.test. IN A 10.53.0.3
ns5.zonemd.test. IN A 10.53.0.5
@@ -40,3 +40,15 @@ zone "local.nil" {
file "local.db";
update-policy local;
};
zone "zonemd.test" {
type secondary;
primaries { 10.53.0.3; };
file "zonemd.test.bk";
};
zone "zonemd-nsec3.test" {
type secondary;
primaries { 10.53.0.3; };
file "zonemd-nsec3.test.bk";
};
+4 -4
View File
@@ -22,12 +22,12 @@ options {
};
zone "." {
type primary;
file "root.db.signed";
type primary;
file "root.db.signed";
};
// An unsigned zone that ns6 has a delegation for.
zone "unsigned." {
type primary;
file "unsigned.db";
type primary;
file "unsigned.db";
};
+4 -4
View File
@@ -22,12 +22,12 @@ options {
};
zone "." {
type primary;
file "root.db";
type primary;
file "root.db";
};
// A signed zone that ns5 has a delegation for.
zone "signed." {
type primary;
file "signed.db.signed";
type primary;
file "signed.db.signed";
};
+30 -11
View File
@@ -2718,21 +2718,40 @@ dns_zone_isloaded(dns_zone_t *zone);
*/
isc_result_t
dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver);
dns_zone_checkzonemd(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver);
/*%<
* If 'zone' is a mirror zone, perform DNSSEC validation of version 'ver' of
* its database, 'db'. Ensure that the DNSKEY RRset at zone apex is signed by
* at least one trust anchor specified for the view that 'zone' is assigned to.
* If 'ver' is NULL, use the current version of 'db'.
*
* If 'zone' is not a mirror zone, return ISC_R_SUCCESS immediately.
* Check that version 'ver' of a zone's database 'db' either contains a
* ZONEMD record which is a valid hash of contents of the database, or
* is not required to contain one.
*
* Returns:
*
* \li #ISC_R_SUCCESS either 'zone' is not a mirror zone or 'zone' is
* a mirror zone and all DNSSEC checks succeeded
* and the DNSKEY RRset at zone apex is signed by
* a trusted key
* \li #ISC_R_SUCCESS a ZONEMD is present at the zone apex,
* which is validly signed by a trusted key
*
* \li #ISC_R_NOTFOUND a ZONEMD record is not present at the
* zone apex, but is not required
*
* \li #DNS_R_BADZONE a ZONEMD record was required but
* was not present, was not validly
* signed, was not signed by a trusted
* key, etc.
*/
isc_result_t
dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver);
/*%<
* Perform DNSSEC validation of version 'ver' of a zone database, 'db'.
* Ensure that the DNSKEY RRset at zone apex is signed by at least one
* trust anchor specified for the view that 'zone' is assigned to.
* If 'ver' is NULL, use the current version of 'db'.
*
* This is meant to be used for mirror zones.
*
* Returns:
*
* \li #ISC_R_SUCCESS all DNSSEC checks succeeded, and the DNSKEY
* RRset at zone apex is signed by a trusted key
*
* \li #DNS_R_VERIFYFAILURE any other case
*/
+14 -2
View File
@@ -392,7 +392,13 @@ axfr_apply_done(void *arg) {
if (result == ISC_R_SUCCESS) {
CHECK(dns_db_endload(xfr->db, &xfr->axfr));
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL));
result = dns_zone_checkzonemd(xfr->zone, xfr->db, xfr->ver);
if (result == DNS_R_BADZONE) {
goto failure;
}
if (dns_zone_gettype(xfr->zone) == dns_zone_mirror) {
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL));
}
CHECK(axfr_finalize(xfr));
} else {
(void)dns_db_endload(xfr->db, &xfr->axfr);
@@ -511,7 +517,13 @@ static isc_result_t
ixfr_end_transaction(dns_xfrin_t *xfr) {
isc_result_t result = ISC_R_SUCCESS;
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver));
result = dns_zone_checkzonemd(xfr->zone, xfr->db, xfr->ver);
if (result == DNS_R_BADZONE) {
goto failure;
}
if (dns_zone_gettype(xfr->zone) == dns_zone_mirror) {
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver));
}
/* XXX enter ready-to-commit state here */
if (xfr->ixfr.journal != NULL) {
CHECK(dns_journal_commit(xfr->ixfr.journal));
+524 -6
View File
@@ -89,6 +89,7 @@
#include <dns/update.h>
#include <dns/xfrin.h>
#include <dns/zone.h>
#include <dns/zonemd.h>
#include <dns/zoneverify.h>
#include <dns/zt.h>
@@ -4903,6 +4904,520 @@ failure:
return result;
}
static bool
signed_by_dnskey(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *dnskeyset,
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
isc_result_t result = ISC_R_NOTFOUND;
bool answer;
bool ignoretime = DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_ACCEPTEXPIRED);
dst_key_t *key = NULL;
/*
* If rdataset is not associated there is nothing to verify.
*/
if (!dns_rdataset_isassociated(rdataset)) {
return false;
}
if (dns_rdataset_isassociated(dnskeyset) &&
dns_rdataset_isassociated(sigrdataset))
{
result = dns_rdataset_first(dnskeyset);
while (result == ISC_R_SUCCESS) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(dnskeyset, &rdata);
if (dns_dnssec_signs(&rdata, name, rdataset,
sigrdataset, ignoretime,
zone->mctx))
{
break;
}
result = dns_rdataset_next(dnskeyset);
}
}
if (key != NULL) {
dst_key_free(&key);
}
answer = (result == ISC_R_SUCCESS);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "signed_by_dnskey(%u) -> %s",
rdataset->type, answer ? "true" : "false");
return answer;
}
static bool
self_signed(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *dsset,
dns_rdataset_t *dnskeyset, dns_rdataset_t *sigrdataset) {
isc_result_t result = ISC_R_NOTFOUND;
dns_rdataset_t *rdataset = dnskeyset;
bool answer;
bool ignoretime = DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_ACCEPTEXPIRED);
dst_key_t *key = NULL;
if (dns_rdataset_isassociated(dnskeyset) &&
dns_rdataset_isassociated(sigrdataset))
{
result = dns_rdataset_first(dnskeyset);
while (result == ISC_R_SUCCESS) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(dnskeyset, &rdata);
if (dns_dnssec_signs(&rdata, name, rdataset,
sigrdataset, ignoretime,
zone->mctx))
{
if (dsset == NULL) {
/*
* There isn't a trust achor to
* check against so we are done.
*/
break;
}
CHECK(dns_dnssec_keyfromrdata(
name, &rdata, zone->mctx, &key));
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);
CHECK(dns_rdata_tostruct(&dsrdata, &ds,
NULL));
if (ds.key_tag != dst_key_id(key) ||
ds.algorithm != dst_key_alg(key))
{
continue;
}
result = dns_ds_buildrdata(
name, &rdata, ds.digest_type,
buf, &newdsrdata);
if (result != ISC_R_SUCCESS) {
continue;
}
if (dns_rdata_compare(&dsrdata,
&newdsrdata) == 0)
{
break;
}
}
if (result == ISC_R_SUCCESS) {
break;
}
dst_key_free(&key);
}
result = dns_rdataset_next(dnskeyset);
}
}
failure:
if (key != NULL) {
dst_key_free(&key);
}
answer = (result == ISC_R_SUCCESS);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "self_signed(%u) -> %s",
rdataset->type, answer ? "true" : "false");
return answer;
}
static bool
haszonemdbit(dns_rdataset_t *set) {
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
result = dns_rdataset_first(set);
if (result != ISC_R_SUCCESS) {
/* fail true */
return true;
}
dns_rdataset_current(set, &rdata);
if (set->type == dns_rdatatype_nsec) {
return dns_nsec_typepresent(&rdata, dns_rdatatype_zonemd);
} else {
return dns_nsec3_typepresent(&rdata, dns_rdatatype_zonemd);
}
}
static isc_result_t
find_origin_nsec3(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node,
dns_dbversion_t *version, bool *nsec3paramflagsok,
dns_rdataset_t *nsec3paramset,
dns_rdataset_t *signsec3paramset, dns_fixedname_t *fnsec3name,
dns_dbnode_t **nsec3node, dns_rdataset_t *nsec3set,
dns_rdataset_t *signsec3set) {
unsigned char hash[NSEC3_MAX_HASH_LENGTH];
size_t hashlen = sizeof(hash);
dns_rdata_nsec3param_t nsec3param;
isc_result_t result;
result = dns_db_findrdataset(
db, node, version, dns_rdatatype_nsec3param, dns_rdatatype_none,
0, nsec3paramset, signsec3paramset);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s",
dns_rdatatype_nsec3param, isc_result_totext(result));
if (result != ISC_R_SUCCESS) {
goto failure;
}
result = dns_rdataset_first(nsec3paramset);
while (result == ISC_R_SUCCESS) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(nsec3paramset, &rdata);
CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
if (nsec3param.flags == 0) {
*nsec3paramflagsok = true;
break;
}
result = dns_rdataset_next(nsec3paramset);
}
CHECK((result == ISC_R_NOMORE) ? ISC_R_NOTFOUND : result);
CHECK(dns_nsec3_hashname(fnsec3name, hash, &hashlen, &zone->origin,
&zone->origin, nsec3param.hash,
nsec3param.iterations, nsec3param.salt,
nsec3param.salt_length));
CHECK(dns_db_findnsec3node(db, dns_fixedname_name(fnsec3name), false,
nsec3node));
/* lookup NSEC3 record */
result = dns_db_findrdataset(db, *nsec3node, version,
dns_rdatatype_nsec3, dns_rdatatype_none, 0,
nsec3set, signsec3set);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s",
dns_rdatatype_nsec3, isc_result_totext(result));
failure:
return result;
}
isc_result_t
dns_zone_checkzonemd(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) {
enum { unknown = 0, good = 1, bad = 2 } found[256] = { unknown };
bool nsec3paramflagsok = false;
isc_result_t result;
dns_keynode_t *keynode = NULL;
dns_keytable_t *secroots = NULL;
dns_dbnode_t *node = NULL;
dns_dbnode_t *nsec3node = NULL;
dns_fixedname_t fnsec3name;
dns_name_t *origin;
dns_name_t *nsec3name = dns_fixedname_initname(&fnsec3name);
dns_rdata_soa_t soa;
dns_rdata_t soardata = DNS_RDATA_INIT;
dns_rdataset_t dsset = DNS_RDATASET_INIT, *dssetp = NULL;
dns_rdataset_t dnskeyset = DNS_RDATASET_INIT,
sigdnskeyset = DNS_RDATASET_INIT;
dns_rdataset_t nsec3paramset = DNS_RDATASET_INIT,
signsec3paramset = DNS_RDATASET_INIT;
dns_rdataset_t nsec3set = DNS_RDATASET_INIT,
signsec3set = DNS_RDATASET_INIT;
dns_rdataset_t nsecset = DNS_RDATASET_INIT,
signsecset = DNS_RDATASET_INIT;
dns_rdataset_t soaset = DNS_RDATASET_INIT,
sigsoaset = DNS_RDATASET_INIT;
dns_rdataset_t zonemdset = DNS_RDATASET_INIT,
sigzonemdset = DNS_RDATASET_INIT;
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(db != NULL);
if (!DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_CHECK)) {
return ISC_R_NOTFOUND;
}
origin = &zone->origin;
if (zone->view != NULL) {
result = dns_view_getsecroots(zone->view, &secroots);
if (result == ISC_R_SUCCESS) {
result = dns_keytable_find(secroots, origin, &keynode);
}
if (result == ISC_R_SUCCESS) {
if (dns_keynode_dsset(keynode, &dsset)) {
dssetp = &dsset;
}
}
}
dns_zone_log(zone, ISC_LOG_DEBUG(3), "in zone_checkzonemd");
CHECK(dns_db_findnode(db, &zone->origin, false, &node));
result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa,
dns_rdatatype_none, 0, &soaset,
&sigsoaset);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s",
dns_rdatatype_soa, isc_result_totext(result));
CHECK((result == ISC_R_NOTFOUND) ? DNS_R_BADZONE : result);
result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey,
dns_rdatatype_none, 0, &dnskeyset,
&sigdnskeyset);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s",
dns_rdatatype_dnskey, isc_result_totext(result));
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
goto failure;
}
result = dns_db_findrdataset(db, node, version, dns_rdatatype_zonemd,
dns_rdatatype_none, 0, &zonemdset,
&sigzonemdset);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s",
dns_rdatatype_zonemd, isc_result_totext(result));
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
goto failure;
}
/*
* Look for proofs that the zone is fully signed (NSEC or NSEC3PARAM)
* at apex and that ZONEMD does not exist (apex NSEC/NSEC3),
*/
result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec,
dns_rdatatype_none, 0, &nsecset,
&signsecset);
dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s",
dns_rdatatype_nsec, isc_result_totext(result));
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
goto failure;
}
if (result == ISC_R_NOTFOUND) {
result = find_origin_nsec3(zone, db, node, version,
&nsec3paramflagsok, &nsec3paramset,
&signsec3paramset, &fnsec3name,
&nsec3node, &nsec3set, &signsec3set);
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
goto failure;
}
}
/*
* The zone is signed, check that zonemdset is
* correctly signed, or that it doesn't exist and
* that its nonexistence is not an error.
*
* For a zone to be deemed signed, DNSKEY and one of
* NSEC or NSEC3PARAM (with 0 flags) need to exist.
*/
if (dns_rdataset_isassociated(&dnskeyset) &&
(dns_rdataset_isassociated(&nsecset) ||
(dns_rdataset_isassociated(&nsec3paramset) && nsec3paramflagsok)))
{
if (!self_signed(zone, origin, dssetp, &dnskeyset,
&sigdnskeyset))
{
dns_zone_log(zone, ISC_LOG_INFO,
"ZONEMD: self validation failure DNSKEY");
CHECK(DNS_R_BADZONE);
}
if (!signed_by_dnskey(zone, origin, &dnskeyset, &soaset,
&sigsoaset))
{
dns_zone_log(zone, ISC_LOG_INFO,
"ZONEMD: self validation failure SOA");
CHECK(DNS_R_BADZONE);
}
if (!dns_rdataset_isassociated(&zonemdset)) {
if (signed_by_dnskey(zone, origin, &dnskeyset, &nsecset,
&signsecset) &&
!haszonemdbit(&nsecset))
{
if (DNS_ZONEMD_OPTION(zone,
DNS_ZONEMDOPT_REQUIRED))
{
dns_zone_log(zone, ISC_LOG_INFO,
"ZONEMD required but not "
"present");
CHECK(DNS_R_BADZONE);
}
CHECK(ISC_R_NOTFOUND);
}
if (signed_by_dnskey(zone, nsec3name, &dnskeyset,
&nsec3set, &signsec3set) &&
signed_by_dnskey(zone, origin, &dnskeyset,
&nsec3paramset,
&signsec3paramset) &&
!haszonemdbit(&nsec3set))
{
if (DNS_ZONEMD_OPTION(zone,
DNS_ZONEMDOPT_REQUIRED))
{
dns_zone_log(zone, ISC_LOG_INFO,
"ZONEMD required but not "
"present");
CHECK(DNS_R_BADZONE);
}
CHECK(ISC_R_NOTFOUND);
}
CHECK(DNS_R_BADZONE);
} else if (!signed_by_dnskey(zone, origin, &dnskeyset,
&zonemdset, &sigzonemdset))
{
dns_zone_log(zone, ISC_LOG_INFO,
"ZONEMD validation failed");
CHECK(DNS_R_BADZONE);
}
} else {
if (dssetp != NULL) {
dns_zone_log(zone, ISC_LOG_INFO,
"zone at a trust anchor should be signed "
"but isn't");
CHECK(DNS_R_BADZONE);
}
if (!dns_rdataset_isassociated(&zonemdset)) {
if (DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_REQUIRED) &&
DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_DNSSECONLY))
{
dns_zone_log(zone, ISC_LOG_INFO,
"ZONEMD required but not present");
CHECK(DNS_R_BADZONE);
}
CHECK(ISC_R_NOTFOUND);
}
}
/*
* Check the ZONEMD records.
*/
CHECK(dns_rdataset_first(&soaset));
dns_rdataset_current(&soaset, &soardata);
CHECK(dns_rdata_tostruct(&soardata, &soa, NULL));
result = dns_rdataset_first(&zonemdset);
while (result == ISC_R_SUCCESS) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(&zonemdset, &rdata);
if (dns_zonemd_supported(&rdata)) {
dns_rdata_zonemd_t zonemd;
dns_rdata_t check = DNS_RDATA_INIT;
unsigned char buf[DNS_ZONEMD_BUFFERSIZE];
CHECK(dns_rdata_tostruct(&rdata, &zonemd, NULL));
if (soa.serial != zonemd.serial) {
dns_zone_log(zone, ISC_LOG_DEBUG(3),
"zonemd serial mismatch: soa=%u "
"zonemd=%u",
soa.serial, zonemd.serial);
found[zonemd.digest_type] = bad;
goto nextzonemd;
}
if (found[zonemd.digest_type] != unknown) {
if (found[zonemd.digest_type] == good) {
dns_zone_log(zone, ISC_LOG_DEBUG(3),
"zonemd duplicate: digest "
"type=%u",
zonemd.digest_type);
found[zonemd.digest_type] = bad;
}
} else {
CHECK(dns_zonemd_buildrdata(
&check, db, version, zonemd.scheme,
zonemd.digest_type, zone->mctx, buf,
sizeof(buf)));
if (dns_rdata_compare(&check, &rdata) != 0) {
char buf1[256] = { 0 },
buf2[256] = { 0 };
isc_buffer_t b;
dns_zone_log(zone, ISC_LOG_DEBUG(3),
"zonemd dns_rdata_compare "
"!= 0");
isc_buffer_init(&b, buf1, sizeof(buf1));
dns_rdata_totext(&check, NULL, &b);
isc_buffer_init(&b, buf2, sizeof(buf2));
dns_rdata_totext(&rdata, NULL, &b);
found[zonemd.digest_type] = bad;
} else {
found[zonemd.digest_type] = good;
}
}
}
nextzonemd:
result = dns_rdataset_next(&zonemdset);
}
if (result == ISC_R_NOMORE) {
size_t i;
for (i = 0; i < ARRAY_SIZE(found); i++) {
if (found[i] == good) {
result = ISC_R_SUCCESS;
}
}
if (result != ISC_R_SUCCESS) {
if (DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_REQUIRED)) {
dns_zone_log(zone, ISC_LOG_INFO,
"valid ZONEMD required but not "
"present");
result = DNS_R_BADZONE;
} else {
result = ISC_R_NOTFOUND;
}
}
}
failure:
if (dns_rdataset_isassociated(&dsset)) {
dns_rdataset_disassociate(&dsset);
}
if (dns_rdataset_isassociated(&soaset)) {
dns_rdataset_disassociate(&soaset);
}
if (dns_rdataset_isassociated(&sigsoaset)) {
dns_rdataset_disassociate(&sigsoaset);
}
if (dns_rdataset_isassociated(&nsecset)) {
dns_rdataset_disassociate(&nsecset);
}
if (dns_rdataset_isassociated(&signsecset)) {
dns_rdataset_disassociate(&signsecset);
}
if (dns_rdataset_isassociated(&nsec3set)) {
dns_rdataset_disassociate(&nsec3set);
}
if (dns_rdataset_isassociated(&signsec3set)) {
dns_rdataset_disassociate(&signsec3set);
}
if (dns_rdataset_isassociated(&dnskeyset)) {
dns_rdataset_disassociate(&dnskeyset);
}
if (dns_rdataset_isassociated(&sigdnskeyset)) {
dns_rdataset_disassociate(&sigdnskeyset);
}
if (dns_rdataset_isassociated(&zonemdset)) {
dns_rdataset_disassociate(&zonemdset);
}
if (dns_rdataset_isassociated(&sigzonemdset)) {
dns_rdataset_disassociate(&sigzonemdset);
}
if (dns_rdataset_isassociated(&nsec3paramset)) {
dns_rdataset_disassociate(&nsec3paramset);
}
if (dns_rdataset_isassociated(&signsec3paramset)) {
dns_rdataset_disassociate(&signsec3paramset);
}
if (node != NULL) {
dns_db_detachnode(db, &node);
}
if (nsec3node != NULL) {
dns_db_detachnode(db, &nsec3node);
}
if (keynode != NULL) {
dns_keynode_detach(&keynode);
}
if (secroots != NULL) {
dns_keytable_detach(&secroots);
}
dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_zone_checkzonemd -> %s",
isc_result_totext(result));
return result;
}
/*
* The zone is presumed to be locked.
* If this is a inline_raw zone the secure version is also locked.
@@ -5157,11 +5672,18 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime,
goto cleanup;
}
result = dns_zone_verifydb(zone, db, NULL);
if (result != ISC_R_SUCCESS) {
result = dns_zone_checkzonemd(zone, db, NULL);
if (result == DNS_R_BADZONE) {
goto cleanup;
}
if (zone->type == dns_zone_mirror) {
result = dns_zone_verifydb(zone, db, NULL);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
}
if (zone->db != NULL) {
unsigned int oldsoacount;
@@ -24617,10 +25139,6 @@ dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver) {
ENTER;
if (dns_zone_gettype(zone) != dns_zone_mirror) {
return ISC_R_SUCCESS;
}
if (ver == NULL) {
dns_db_currentversion(db, &version);
} else {