Commit de3079c20d for bind
commit de3079c20d2173eeffca8d0a8c2468313173be5a
Author: Nicki Křížek <nicki@isc.org>
Date: Fri Oct 2 14:02:24 2026 +0000
Ignore key files of other keys with the same key tag
named finds the files of a key by its key tag. Key files are named after
the zone, the algorithm, and the key tag (K<zone>+<alg>+<tag>), and for
each DNSKEY in the zone, named built that file name and opened the file,
without checking that the key in it is the key in the DNSKEY.
With offline-ksk, the KSK and the ZSKs are generated separately, and
named only has the files of the ZSKs. The signatures over the DNSKEY,
CDS, and CDNSKEY RRsets are made in advance by whoever holds the KSK,
and reach named in the imported SKR. Nothing prevents a ZSK from getting
the same key tag as the KSK, and when that happened, named opened the
ZSK's files for the KSK's DNSKEY. It then believed it had the ZSK twice
and no KSK:
- Only keys in the KSK role sign the DNSKEY RRset, so named never took
the KSK's signature from the SKR, and left the DNSKEY RRset unsigned.
Validating resolvers then cannot trust the zone and treat it as bogus.
- named signed some RRsets, such as the SOA, twice with the ZSK. With
deterministic signatures (Ed25519 and Ed448, and ECDSA with OpenSSL
3.2 and later outside of FIPS mode), the two signatures came out
identical, adding the second one failed with "not exact", and so did
the whole key update, which named retried every ten minutes.
The same mix-up happens whenever the DNSKEY RRset holds a key whose
files named does not have, but which shares the key tag of a key whose
files named does have, e.g. the DNSKEY of another provider in a
multi-signer setup.
Check that a key file found by its key tag holds the same key as the
DNSKEY, and treat the file as missing otherwise, just as when there is
no file at all, e.g. for an offline KSK or another provider's key. When
the policy uses several key-stores, look for the matching file in the
remaining ones.
Assisted-by: Claude:claude-opus-5-5
diff --git a/lib/dns/zone.c b/lib/dns/zone.c
index 5685e329ac..d1930727f8 100644
--- a/lib/dns/zone.c
+++ b/lib/dns/zone.c
@@ -5005,6 +5005,29 @@ was_dumping(dns_zone_t *zone) {
return false;
}
+/*
+ * Key files are named after the key tag, but distinct keys may share a key
+ * tag, e.g. an offline KSK and a ZSK. Treat a key file holding a different
+ * key than 'pubkey' as missing.
+ */
+static isc_result_t
+keyfromdir(dst_key_t *pubkey, const char *directory, isc_mem_t *mctx,
+ dst_key_t **key) {
+ isc_result_t result;
+
+ result = dst_key_fromfile(
+ dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey),
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE, directory,
+ mctx, key);
+ if (result == ISC_R_SUCCESS && !dst_key_pubcompare(pubkey, *key, true))
+ {
+ dst_key_free(key);
+ result = ISC_R_FILENOTFOUND;
+ }
+
+ return result;
+}
+
static isc_result_t
keyfromfile(dns_zone_t *zone, dst_key_t *pubkey, isc_mem_t *mctx,
dst_key_t **key) {
@@ -5016,23 +5039,14 @@ keyfromfile(dns_zone_t *zone, dst_key_t *pubkey, isc_mem_t *mctx,
if (kasp == NULL || (strcmp(dns_kasp_getname(kasp), "none") == 0) ||
(strcmp(dns_kasp_getname(kasp), "insecure") == 0))
{
- result = dst_key_fromfile(
- dst_key_name(pubkey), dst_key_id(pubkey),
- dst_key_alg(pubkey),
- DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE,
- directory, mctx, &foundkey);
+ result = keyfromdir(pubkey, directory, mctx, &foundkey);
} else {
ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
dns_keystore_t *ks = dns_kasp_key_keystore(kkey);
directory = dns_keystore_directory(ks,
zone->keydirectory);
- result = dst_key_fromfile(
- dst_key_name(pubkey), dst_key_id(pubkey),
- dst_key_alg(pubkey),
- DST_TYPE_PUBLIC | DST_TYPE_PRIVATE |
- DST_TYPE_STATE,
- directory, mctx, &foundkey);
+ result = keyfromdir(pubkey, directory, mctx, &foundkey);
if (result == ISC_R_SUCCESS) {
break;
}