Commit 3715b80b6b for bind

commit 3715b80b6bbe53980c727b1cfc2bbd5cf6092917
Author: Matthijs Mekking <matthijs@isc.org>
Date:   Fri Oct 9 11:40:22 2026 +0200

    Add configuration to set key flags

    This adds an optional parameter to 'keys' inside dnssec-policy to set
    specific key flags. The only two bits that can be set are SEP and ADT.

    For example:

      keys {
          csk lifetime unlimited algorithm 13 flags sep adt;
      };

diff --git a/bin/tests/system/checkconf/bad-kasp-key5.conf b/bin/tests/system/checkconf/bad-kasp-key5.conf
new file mode 100644
index 0000000000..3afc60f9b8
--- /dev/null
+++ b/bin/tests/system/checkconf/bad-kasp-key5.conf
@@ -0,0 +1,11 @@
+dnssec-policy "badflags" {
+	keys {
+		csk lifetime unlimited algorithm 13 flags foo;
+	};
+};
+
+zone "example.net" {
+	type primary;
+	file "example.db";
+	dnssec-policy "badflags";
+};
diff --git a/bin/tests/system/checkconf/bad-kasp-key6.conf b/bin/tests/system/checkconf/bad-kasp-key6.conf
new file mode 100644
index 0000000000..4880947351
--- /dev/null
+++ b/bin/tests/system/checkconf/bad-kasp-key6.conf
@@ -0,0 +1,11 @@
+dnssec-policy "badflags" {
+	keys {
+		csk lifetime unlimited algorithm 13 flags { sep; };
+	};
+};
+
+zone "example.net" {
+	type primary;
+	file "example.db";
+	dnssec-policy "badflags";
+};
diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf
index 98e80c7009..06ea31ddac 100644
--- a/bin/tests/system/checkconf/good-kasp.conf
+++ b/bin/tests/system/checkconf/good-kasp.conf
@@ -11,9 +11,11 @@ dnssec-policy "test" {
 	dnskey-ttl 3600;
 	inline-signing yes;
 	keys {
-		ksk key-directory lifetime P1Y algorithm ecdsa256;
-		zsk lifetime P30D algorithm 13;
-		csk key-store "hsm" lifetime unlimited algorithm rsasha256 2048;
+		ksk key-directory lifetime P1Y algorithm ecdsa256 flags sep adt;
+		zsk lifetime P30D algorithm 13 flags adt;
+		csk key-store "hsm" lifetime unlimited algorithm rsasha256 2048 flags adt sep;
+		ksk key-directory lifetime P1Y algorithm 14 tag-range 0 1000 flags ;
+		zsk key-directory lifetime unlimited algorithm ecdsa384 tag-range 1000 9999;
 	};
 	max-zone-ttl 86400;
 	nsec3param iterations 0 optout no salt-length 8;
diff --git a/bin/tests/system/checkconf/good.conf.j2 b/bin/tests/system/checkconf/good.conf.j2
index 412204e7ff..675ef1dc5b 100644
--- a/bin/tests/system/checkconf/good.conf.j2
+++ b/bin/tests/system/checkconf/good.conf.j2
@@ -10,9 +10,9 @@ dnssec-policy "test" {
 	};
 	dnskey-ttl 3600;
 	keys {
-		ksk key-directory lifetime P1Y algorithm 13 tag-range 0 32767;
-		zsk lifetime P30D algorithm 13;
-		csk key-store "hsm" lifetime P30D algorithm 8 2048;
+		ksk key-directory lifetime P1Y algorithm 13 tag-range 0 32767 flags sep adt;
+		zsk lifetime P30D algorithm 13 tag-range 0 100;
+		csk key-store "hsm" lifetime P30D algorithm 8 2048 flags adt sep;
 	};
 	manual-mode no;
 	max-zone-ttl 86400;
diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst
index a09e71f02f..d51a64a843 100644
--- a/doc/arm/reference.rst
+++ b/doc/arm/reference.rst
@@ -6490,6 +6490,12 @@ keys
     Each KSK/ZSK pair must have the same algorithm. A CSK combines the
     functionality of a ZSK and a KSK.

+    You can optionally set one or more key flags with ``flags``.
+    Two keywords are allowed, ``sep`` and ``adt``, and they will set the
+    corresponding key flags. If ``flags`` is not set, any KASP key with
+    the role ``csk`` or ``ksk`` will have the SEP bit set, and none of
+    the keys will have the ADT bit set.
+
 .. note:: When changing the ``key-directory`` or the ``key-store``, BIND will
    be unable to find existing key files. Be sure to copy key files to the
    new directory before changing the path used in the configuration file.
diff --git a/doc/misc/options b/doc/misc/options
index e5a58cabf5..eadf175850 100644
--- a/doc/misc/options
+++ b/doc/misc/options
@@ -15,7 +15,7 @@ dnssec-policy <string> {
 	cds-digest-types { <string>; ... };
 	dnskey-ttl <duration>;
 	inline-signing <boolean>;
-	keys { ( csk | ksk | zsk ) [ key-directory | key-store <string> ] lifetime <duration_or_unlimited> algorithm <string> [ tag-range <integer> <integer> ] [ <integer> ]; ... };
+	keys { ( csk | ksk | zsk ) [ key-directory | key-store <string> ] lifetime <duration_or_unlimited> algorithm <string> [ tag-range <integer> <integer> ] [ <integer> ] [ flags ( sep | adt ) ... ]; ... };
 	manual-mode <boolean>;
 	max-zone-ttl <duration>;
 	nsec3param [ iterations <integer> ] [ optout <boolean> ] [ salt-length <integer> ];
diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h
index 0b354a45cd..888848168a 100644
--- a/lib/dns/include/dns/kasp.h
+++ b/lib/dns/include/dns/kasp.h
@@ -70,6 +70,7 @@ struct dns_kasp_key {
 	dst_algorithm_t algorithm;
 	int		length;
 	uint8_t		role;
+	uint16_t	flags;
 	uint16_t	tag_min;
 	uint16_t	tag_max;
 };
@@ -711,6 +712,16 @@ dns_kasp_key_zsk(dns_kasp_key_t *key);
  *
  */

+uint16_t
+dns_kasp_key_flags(dns_kasp_key_t *key);
+/*%<
+ * Returns the key flags to use.
+ *
+ * Requires:
+ *
+ *\li  key != NULL
+ */
+
 uint16_t
 dns_kasp_key_tagmin(dns_kasp_key_t *key);
 /*%<
diff --git a/lib/dns/include/dns/keyvalues.h b/lib/dns/include/dns/keyvalues.h
index 0562a8085d..322db9b06f 100644
--- a/lib/dns/include/dns/keyvalues.h
+++ b/lib/dns/include/dns/keyvalues.h
@@ -33,6 +33,7 @@
 enum {
 	DNS_KEYOWNER_ZONE = 1 << 8,  /* zone key (mandatory for DNSKEY). */
 	DNS_KEYFLAG_REVOKE = 1 << 7, /* key revoked (per rfc5011) */
+	DNS_KEYFLAG_ADT = 1 << 1,    /* DELEG */
 	DNS_KEYFLAG_KSK = 1 << 0,    /* key signing key */
 };

diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c
index cd4e8cdfc1..4fb48012fb 100644
--- a/lib/dns/kasp.c
+++ b/lib/dns/kasp.c
@@ -493,6 +493,12 @@ dns_kasp_key_zsk(dns_kasp_key_t *key) {
 	return key->role & DNS_KASP_KEY_ROLE_ZSK;
 }

+uint16_t
+dns_kasp_key_flags(dns_kasp_key_t *key) {
+	REQUIRE(key != NULL);
+	return key->flags;
+}
+
 uint16_t
 dns_kasp_key_tagmin(dns_kasp_key_t *key) {
 	REQUIRE(key != NULL);
diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h
index 2021232575..1046735ae2 100644
--- a/lib/isccfg/include/isccfg/grammar.h
+++ b/lib/isccfg/include/isccfg/grammar.h
@@ -475,6 +475,9 @@ cfg_print_bracketed_list(cfg_printer_t *pctx, const cfg_obj_t *obj);
 void
 cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type);

+void
+cfg_doc_enum_list(cfg_printer_t *pctx, const cfg_type_t *type);
+
 isc_result_t
 cfg_parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *type,
 		    cfg_obj_t **ret);
diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c
index 3156bb13a7..cb702d3713 100644
--- a/lib/isccfg/kaspconf.c
+++ b/lib/isccfg/kaspconf.c
@@ -151,6 +151,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
 	isc_result_t result;
 	dns_kasp_key_t *key = NULL;
 	const cfg_obj_t *tagrange = NULL;
+	const cfg_obj_t *keyflags = NULL;
 	uint32_t min_lifetime = UINT32_MAX;

 	/* Create a new key reference. */
@@ -163,6 +164,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
 		key->lifetime = 0; /* unlimited */
 		key->algorithm = DST_ALG_ECDSA256;
 		key->length = -1;
+		key->flags = DNS_KEYOWNER_ZONE | DNS_KEYFLAG_KSK;
 		CHECK(dns_keystorelist_find(keystorelist,
 					    DNS_KEYSTORE_KEYDIRECTORY,
 					    &key->keystore));
@@ -367,6 +369,28 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
 			key->length = size;
 		}

+		key->flags = DNS_KEYOWNER_ZONE;
+		keyflags = cfg_tuple_get(config, "flags");
+		if (cfg_obj_islist(keyflags)) {
+			const cfg_listelt_t *element = NULL;
+
+			for (element = cfg_list_first(keyflags);
+			     element != NULL; element = cfg_list_next(element))
+			{
+				const cfg_obj_t *flagobj =
+					cfg_listelt_value(element);
+				const char *flag = cfg_obj_asstring(flagobj);
+
+				if (strcasecmp(flag, "sep") == 0) {
+					key->flags |= DNS_KEYFLAG_KSK;
+				} else if (strcasecmp(flag, "adt") == 0) {
+					key->flags |= DNS_KEYFLAG_ADT;
+				}
+			}
+		} else if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0) {
+			key->flags |= DNS_KEYFLAG_KSK;
+		}
+
 		tagrange = cfg_tuple_get(config, "tag-range");
 		if (cfg_obj_istuple(tagrange)) {
 			uint32_t tag_min = 0, tag_max = 0xffff;
@@ -835,6 +859,7 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
 			new_key->lifetime = dns_kasp_key_lifetime(key);
 			new_key->algorithm = dns_kasp_key_algorithm(key);
 			new_key->length = dns_kasp_key_size(key);
+			new_key->flags = dns_kasp_key_flags(key);
 			result = dns_keystorelist_find(
 				keystorelist, DNS_KEYSTORE_KEYDIRECTORY,
 				&new_key->keystore);
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
index 8e29044f52..446bf4e8f9 100644
--- a/lib/isccfg/namedconf.c
+++ b/lib/isccfg/namedconf.c
@@ -635,7 +635,7 @@ static cfg_type_t cfg_type_lifetime = { "lifetime",	   parse_keyvalue,
 					print_keyvalue,	   doc_keyvalue,
 					&cfg_rep_duration, &lifetime_kw };
 /*
- *
+ * Keytag range.
  */
 static void
 print_tagrange(cfg_printer_t *pctx, const cfg_obj_t *obj) {
@@ -695,6 +695,30 @@ static cfg_type_t cfg_type_optional_tagrange = {
 	doc_optionaltagrange, &cfg_rep_tuple,	      &tagrange_kw
 };

+/*
+ * Key flags.
+ */
+static const char *keyflag_enums[] = { "sep", "adt", NULL };
+
+static cfg_type_t cfg_type_keyflag = { "keyflag",	  cfg_parse_enum,
+				       cfg_print_ustring, cfg_doc_enum,
+				       &cfg_rep_string,	  keyflag_enums };
+
+static cfg_type_t cfg_type_keyflaglist = {
+	"keyflaglist",	   cfg_parse_spacelist, cfg_print_spacelist,
+	cfg_doc_enum_list, &cfg_rep_list,	&cfg_type_keyflag
+};
+
+static keyword_type_t keyflags_kw = { "flags", &cfg_type_keyflaglist };
+
+static cfg_type_t cfg_type_optional_keyflags = {
+	"optional_keyflags",   parse_optional_keyvalue, print_keyvalue,
+	doc_optional_keyvalue, &cfg_rep_list,		&keyflags_kw
+};
+
+/*
+ * KASP key.
+ */
 static cfg_tuplefielddef_t kaspkey_fields[] = {
 	{ "role", &cfg_type_dnsseckeyrole, 0 },
 	{ "keystorage", &cfg_type_optional_keystore, 0 },
@@ -702,6 +726,7 @@ static cfg_tuplefielddef_t kaspkey_fields[] = {
 	{ "algorithm", &cfg_type_algorithm, 0 },
 	{ "tag-range", &cfg_type_optional_tagrange, 0 },
 	{ "length", &cfg_type_optional_uint32, 0 },
+	{ "flags", &cfg_type_optional_keyflags, 0 },
 	{ NULL, NULL, 0 }
 };
 static cfg_type_t cfg_type_kaspkey = { "kaspkey",	cfg_parse_tuple,
@@ -3388,6 +3413,9 @@ doc_optional_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type) {
 	cfg_print_cstr(pctx, " ]");
 }

+/*
+ * NOTIFY.
+ */
 static const char *notify_enums[] = { "explicit", "master-only", "primary-only",
 				      NULL };
 static isc_result_t
diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c
index d21de76e64..79bde3e1b8 100644
--- a/lib/isccfg/parser.c
+++ b/lib/isccfg/parser.c
@@ -2187,6 +2187,15 @@ cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type) {
 	cfg_print_cstr(pctx, "; ... }");
 }

+void
+cfg_doc_enum_list(cfg_printer_t *pctx, const cfg_type_t *type) {
+	REQUIRE(pctx != NULL);
+	REQUIRE(type != NULL);
+
+	cfg_doc_obj(pctx, type->of);
+	cfg_print_cstr(pctx, " ...");
+}
+
 /*
  * Parse a homogeneous list whose elements are of type 'elttype'
  * and where elements are separated by space.  The list ends