Commit 525679f60f for bind
commit 525679f60f4081e9386378723639cc9d82b84893
Author: Evan Hunt <each@isc.org>
Date: Wed Oct 21 17:55:15 2026 -0700
Add a warning when signing DELEG without an ADT key
When signing an RRset of type DELEG, named and dnssec-signzone
will check that at least one key in the DNSKEY RRset has the
ADT flag set. If not, a warning will be logged.
(In the future, this might become a fatal error, with an
optional switch to disable it.)
diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
index 98eb28daf7..687db5df24 100644
--- a/bin/dnssec/dnssec-signzone.c
+++ b/bin/dnssec/dnssec-signzone.c
@@ -182,6 +182,7 @@ static bool set_maxttl = false;
static dns_ttl_t maxttl = 0;
static bool no_max_check = false;
static const char *sync_records = "cdnskey,cds:sha-256";
+static bool have_adt = false;
#define INCSTAT(counter) \
if (printstats) { \
@@ -540,6 +541,13 @@ signset(dns_diff_t *del, dns_diff_t *add, dns_dbnode_t *node, dns_name_t *name,
vbprintf(1, "%s/%s:\n", namestr, typestr);
+ if (set->type == dns_rdatatype_deleg && !have_adt) {
+ fprintf(stderr,
+ "%s: warning: signing DELEG, but no DNSKEY with "
+ "the ADT flag is present.\n",
+ isc_commandline_progname);
+ }
+
RWLOCK(&keylist_lock, isc_rwlocktype_read);
arraysize = keycount;
RWUNLOCK(&keylist_lock, isc_rwlocktype_read);
@@ -2877,7 +2885,7 @@ findkeys:
}
static void
-warnifallksk(dns_db_t *db) {
+checkdnskeys(dns_db_t *db) {
dns_dbversion_t *currentversion = NULL;
dns_dbnode_t *node = NULL;
dns_rdataset_t rdataset;
@@ -2913,6 +2921,11 @@ warnifallksk(dns_db_t *db) {
check_result(result, "dns_rdata_tostruct");
if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0) {
have_non_ksk = true;
+ }
+ if ((dnskey.flags & DNS_KEYFLAG_ADT) != 0) {
+ have_adt = true;
+ }
+ if (have_non_ksk && have_adt) {
break;
}
}
@@ -3871,7 +3884,7 @@ main(int argc, char *argv[]) {
nokeys = true;
}
- warnifallksk(gdb);
+ checkdnskeys(gdb);
if (IS_NSEC3) {
bool answer;
diff --git a/bin/tests/system/deleg/ns1/named.conf.j2 b/bin/tests/system/deleg/ns1/named.conf.j2
index 444c03b3a5..e6927cbbcf 100644
--- a/bin/tests/system/deleg/ns1/named.conf.j2
+++ b/bin/tests/system/deleg/ns1/named.conf.j2
@@ -31,7 +31,7 @@ dnssec-policy nsec {
parent-ds-ttl 3600;
keys {
zsk key-directory lifetime 60d algorithm ecdsa256;
- ksk key-directory lifetime unlimited algorithm ecdsa256;
+ ksk key-directory lifetime unlimited algorithm ecdsa256 flags sep adt;
};
};
@@ -41,7 +41,7 @@ dnssec-policy nsec3 {
nsec3param iterations 0 optout no;
keys {
zsk key-directory lifetime 60d algorithm ecdsa256;
- ksk key-directory lifetime unlimited algorithm ecdsa256;
+ ksk key-directory lifetime unlimited algorithm ecdsa256 flags sep adt;
};
};
@@ -51,7 +51,7 @@ dnssec-policy optout {
nsec3param iterations 0 optout yes;
keys {
zsk key-directory lifetime 60d algorithm ecdsa256;
- ksk key-directory lifetime unlimited algorithm ecdsa256;
+ ksk key-directory lifetime unlimited algorithm ecdsa256 flags sep adt;
};
};
@@ -83,3 +83,10 @@ zone unsigned {
initial-file "example.db.in";
dnssec-policy none;
};
+
+zone noadt {
+ type primary;
+ file "noadt.db";
+ initial-file "example.db.in";
+ dnssec-policy default;
+};
diff --git a/bin/tests/system/deleg/tests_auth.py b/bin/tests/system/deleg/tests_auth.py
index b84f45311d..76d6c0b780 100644
--- a/bin/tests/system/deleg/tests_auth.py
+++ b/bin/tests/system/deleg/tests_auth.py
@@ -9,6 +9,8 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
+from re import compile as Re
+
import time
from dns import rdatatype, update
@@ -439,3 +441,11 @@ def test_deleg_updates(qtype, do, de):
if de:
expected = 4 # signed DELEG, signed NSEC/NSEC3
isctest.check.rr_count_eq(res.authority, expected)
+
+
+def test_deleg_noadt_warning(ns1):
+ # this should be found for zone noadt and no other zones
+ count1 = len(ns1.log.grep(Re("noadt/IN.*signing DELEG but no DNSKEY")))
+ count2 = len(ns1.log.grep(Re("signing DELEG but no DNSKEY")))
+ assert count1 != 0
+ assert count1 == count2
diff --git a/lib/dns/update.c b/lib/dns/update.c
index 9e17819ae1..8c2e71e2cf 100644
--- a/lib/dns/update.c
+++ b/lib/dns/update.c
@@ -920,8 +920,8 @@ find_zone_keys(dns_zone_t *zone, isc_mem_t *mctx, unsigned int maxkeys,
static isc_result_t
add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
dns_dbversion_t *ver, dns_name_t *name, dns_rdatatype_t type,
- dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys,
- isc_stdtime_t now, isc_stdtime_t inception, isc_stdtime_t expire) {
+ dns_diff_t *diff, dst_key_t **keys, size_t nkeys, isc_stdtime_t now,
+ isc_stdtime_t inception, isc_stdtime_t expire) {
isc_result_t result;
dns_dbnode_t *node = NULL;
dns_kasp_t *kasp = dns_zone_getkasp(zone);
@@ -930,7 +930,6 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
dns_stats_t *dnssecsignstats = dns_zone_getdnssecsignstats(zone);
isc_buffer_t buffer;
unsigned char data[DNS_RDATA_MAXLENGTH];
- unsigned int i;
bool added_sig = false;
bool use_kasp = false;
bool offlineksk = false;
@@ -956,15 +955,32 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0)
#define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0)
+#define ADT(x) ((dst_key_flags(x) & DNS_KEYFLAG_ADT) != 0)
#define ID(x) dst_key_id(x)
#define ALG(x) dst_key_alg(x)
+ if (type == dns_rdatatype_deleg) {
+ /* Warn if we don't have at least one ADT key */
+ bool adt = false;
+ for (size_t i = 0; i < nkeys; i++) {
+ if (ADT(keys[i])) {
+ adt = true;
+ break;
+ }
+ }
+ if (!adt) {
+ update_log(log, zone, ISC_LOG_WARNING,
+ "WARNING: signing DELEG but no DNSKEY "
+ "with the ADT flag is present");
+ }
+ }
+
/*
* If we are honoring KSK flags then we need to check that we
* have both KSK and non-KSK keys that are not revoked per
* algorithm.
*/
- for (i = 0; i < nkeys; i++) {
+ for (size_t i = 0; i < nkeys; i++) {
bool both = false;
/* Don't add signatures for offline or inactive keys */
diff --git a/lib/dns/zone.c b/lib/dns/zone.c
index f24df3642f..6569493951 100644
--- a/lib/dns/zone.c
+++ b/lib/dns/zone.c
@@ -116,6 +116,7 @@
*/
#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0)
#define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0)
+#define ADT(x) ((dst_key_flags(x) & DNS_KEYFLAG_ADT) != 0)
#define ZONEKEY(x) ((dst_key_flags(x) & DNS_KEYOWNER_ZONE) != 0)
#define ID(x) dst_key_id(x)
#define ALG(x) dst_key_alg(x)
@@ -5764,9 +5765,9 @@ cleanup:
static isc_result_t
add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, dns_zone_t *zone,
- dns_rdatatype_t type, dns_diff_t *diff, dst_key_t **keys,
- unsigned int nkeys, isc_mem_t *mctx, isc_stdtime_t now,
- isc_stdtime_t inception, isc_stdtime_t expire) {
+ dns_rdatatype_t type, dns_diff_t *diff, dst_key_t **keys, size_t nkeys,
+ isc_mem_t *mctx, isc_stdtime_t now, isc_stdtime_t inception,
+ isc_stdtime_t expire) {
isc_result_t result;
dns_dbnode_t *node = NULL;
dns_stats_t *dnssecsignstats;
@@ -5774,7 +5775,6 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, dns_zone_t *zone,
dns_rdata_t sig_rdata = DNS_RDATA_INIT;
unsigned char data[DNS_RDATA_MAXLENGTH];
isc_buffer_t buffer;
- unsigned int i;
bool use_kasp = false;
bool offlineksk = false;
@@ -5808,7 +5808,23 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, dns_zone_t *zone,
goto cleanup;
}
- for (i = 0; i < nkeys; i++) {
+ if (type == dns_rdatatype_deleg) {
+ /* Warn if we don't have at least one ADT key */
+ bool adt = false;
+ for (size_t i = 0; i < nkeys; i++) {
+ if (ADT(keys[i])) {
+ adt = true;
+ break;
+ }
+ }
+ if (!adt) {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "WARNING: signing DELEG but no DNSKEY "
+ "with the ADT flag is present");
+ }
+ }
+
+ for (size_t i = 0; i < nkeys; i++) {
/* Don't add signatures for offline or inactive keys */
if (!dst_key_isprivate(keys[i]) && !offlineksk) {
continue;
@@ -6462,8 +6478,8 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
bool build_nsec, dst_key_t *key, isc_stdtime_t now,
isc_stdtime_t inception, isc_stdtime_t expire, dns_ttl_t nsecttl,
bool both, bool is_ksk, bool is_zsk, bool fullsign,
- bool is_bottom_of_zone, dns_diff_t *diff, int32_t *signatures,
- isc_mem_t *mctx) {
+ bool is_bottom_of_zone, bool have_adt, dns_diff_t *diff,
+ int32_t *signatures, isc_mem_t *mctx) {
isc_result_t result;
dns_rdatasetiter_t *iterator = NULL;
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
@@ -6558,6 +6574,12 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
continue;
}
+ if (rdataset.type == dns_rdatatype_deleg && !have_adt) {
+ dns_zone_log(zone, ISC_LOG_WARNING,
+ "WARNING: signing DELEG but no DNSKEY "
+ "with the ADT flag is present");
+ }
+
/* Calculate the signature, creating a RRSIG RDATA. */
isc_buffer_clear(&buffer);
if (offlineksk && dns_rdatatype_iskeymaterial(rdataset.type)) {
@@ -8321,6 +8343,7 @@ zone_sign(dns_zone_t *zone) {
while (signing != NULL && nodes-- > 0 && signatures > 0) {
dns_signing_t *nextsigning = ISC_LIST_NEXT(signing, link);
bool has_alg = false;
+ bool have_adt = false;
dns_dbiterator_pause(signing->dbiterator);
@@ -8401,6 +8424,14 @@ zone_sign(dns_zone_t *zone) {
}
}
+ /* Check whether we still have any ADT keys */
+ for (i = 0; i < nkeys; i++) {
+ if (ADT(zone_keys[i])) {
+ have_adt = true;
+ break;
+ }
+ }
+
/*
* Process one node.
*/
@@ -8509,7 +8540,8 @@ zone_sign(dns_zone_t *zone) {
build_nsec, zone_keys[i], now, inception,
expire, zone_nsecttl(zone), both, is_ksk,
is_zsk, signing->fullsign, is_bottom_of_zone,
- zonediff.diff, &signatures, zone->mctx));
+ have_adt, zonediff.diff, &signatures,
+ zone->mctx));
/*
* If we are adding we are done. Look for other keys
* of the same algorithm if deleting.