Check ZONEMD records when loading or transferring a zone
This commit is contained in:
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user