From c2e8c722980cd40e163f748e8502c9637ea55cf3 Mon Sep 17 00:00:00 2001
From: Matthijs Mekking <matthijs@isc.org>
Date: Tue, 3 May 2022 12:28:31 +0200
Subject: [PATCH] Check if key metadata is modified before writing

Add a new parameter to the dst_key structure, mark a key modified if
dst_key_(un)set[bool,num,state,time] is called. Only write out key
files during a keymgr run if the metadata has changed.

(cherry picked from commit 1da91b3ab46b3d875213a473595e671d9ff9c76f)
Conflict: NA
Reference: https://gitlab.isc.org/isc-projects/bind9/-/commit/c2e8c722980cd40e163f748e8502c9637ea55cf3
---
 lib/dns/dst_api.c         | 26 ++++++++++++++++++++++++++
 lib/dns/dst_internal.h    |  1 +
 lib/dns/include/dst/dst.h | 20 ++++++++++++++++++++
 lib/dns/keymgr.c          | 17 ++++++++++++++++-
 4 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c
index ab371330f0..8873a041b8 100644
--- a/lib/dns/dst_api.c
+++ b/lib/dns/dst_api.c
@@ -490,6 +490,16 @@ dst_key_isexternal(dst_key_t *key) {
 	return (key->external);
 }
 
+void
+dst_key_setmodified(dst_key_t *key, bool value) {
+	key->modified = value;
+}
+
+bool
+dst_key_ismodified(dst_key_t *key) {
+	return (key->modified);
+}
+
 isc_result_t
 dst_key_getfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
 		    int type, const char *directory, isc_mem_t *mctx,
@@ -637,6 +647,7 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, int type,
 	    (pubkey->key_flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY)
 	{
 		RETERR(computeid(pubkey));
+		pubkey->modified = false;
 		*keyp = pubkey;
 		pubkey = NULL;
 		goto out;
@@ -690,6 +701,7 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, int type,
 		RETERR(DST_R_INVALIDPRIVATEKEY);
 	}
 
+	key->modified = false;
 	*keyp = key;
 	key = NULL;
 
@@ -1047,6 +1059,8 @@ dst_key_setbool(dst_key_t *key, int type, bool value) {
 	REQUIRE(type <= DST_MAX_BOOLEAN);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || !key->boolset[type] ||
+			key->bools[type] != value;
 	key->bools[type] = value;
 	key->boolset[type] = true;
 	isc_mutex_unlock(&key->mdlock);
@@ -1058,6 +1072,7 @@ dst_key_unsetbool(dst_key_t *key, int type) {
 	REQUIRE(type <= DST_MAX_BOOLEAN);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || key->boolset[type];
 	key->boolset[type] = false;
 	isc_mutex_unlock(&key->mdlock);
 }
@@ -1089,6 +1104,8 @@ dst_key_setnum(dst_key_t *key, int type, uint32_t value) {
 	REQUIRE(type <= DST_MAX_NUMERIC);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || !key->numset[type] ||
+			key->nums[type] != value;
 	key->nums[type] = value;
 	key->numset[type] = true;
 	isc_mutex_unlock(&key->mdlock);
@@ -1100,6 +1117,7 @@ dst_key_unsetnum(dst_key_t *key, int type) {
 	REQUIRE(type <= DST_MAX_NUMERIC);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || key->numset[type];
 	key->numset[type] = false;
 	isc_mutex_unlock(&key->mdlock);
 }
@@ -1130,6 +1148,8 @@ dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when) {
 	REQUIRE(type <= DST_MAX_TIMES);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || !key->timeset[type] ||
+			key->times[type] != when;
 	key->times[type] = when;
 	key->timeset[type] = true;
 	isc_mutex_unlock(&key->mdlock);
@@ -1141,6 +1161,7 @@ dst_key_unsettime(dst_key_t *key, int type) {
 	REQUIRE(type <= DST_MAX_TIMES);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || key->timeset[type];
 	key->timeset[type] = false;
 	isc_mutex_unlock(&key->mdlock);
 }
@@ -1172,6 +1193,8 @@ dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state) {
 	REQUIRE(type <= DST_MAX_KEYSTATES);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || !key->keystateset[type] ||
+			key->keystates[type] != state;
 	key->keystates[type] = state;
 	key->keystateset[type] = true;
 	isc_mutex_unlock(&key->mdlock);
@@ -1183,6 +1206,7 @@ dst_key_unsetstate(dst_key_t *key, int type) {
 	REQUIRE(type <= DST_MAX_KEYSTATES);
 
 	isc_mutex_lock(&key->mdlock);
+	key->modified = key->modified || key->keystateset[type];
 	key->keystateset[type] = false;
 	isc_mutex_unlock(&key->mdlock);
 }
@@ -2747,4 +2771,6 @@ dst_key_copy_metadata(dst_key_t *to, dst_key_t *from) {
 			dst_key_unsetstate(to, i);
 		}
 	}
+
+	dst_key_setmodified(to, dst_key_ismodified(from));
 }
diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h
index e26837bfe4..4933cfd5aa 100644
--- a/lib/dns/dst_internal.h
+++ b/lib/dns/dst_internal.h
@@ -147,6 +147,7 @@ struct dst_key {
 	bool inactive; /*%< private key not present as it is
 			* inactive */
 	bool external; /*%< external key */
+	bool modified; /*%< set to true if key file metadata has changed */
 
 	int fmt_major; /*%< private key format, major version
 			* */
diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h
index d50761a63c..eab5501029 100644
--- a/lib/dns/include/dst/dst.h
+++ b/lib/dns/include/dst/dst.h
@@ -1107,6 +1107,26 @@ dst_key_isexternal(dst_key_t *key);
  *	'key' to be valid.
  */
 
+void
+dst_key_setmodified(dst_key_t *key, bool value);
+/*%<
+ * If 'value' is true, this marks the key to indicate that key file metadata
+ * has been modified. If 'value' is false, this resets the value, for example
+ * after you have written the key to file.
+ *
+ * Requires:
+ *	'key' to be valid.
+ */
+
+bool
+dst_key_ismodified(dst_key_t *key);
+/*%<
+ * Check if the key file has been modified.
+ *
+ * Requires:
+ *	'key' to be valid.
+ */
+
 bool
 dst_key_haskasp(dst_key_t *key);
 /*%<
diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c
index 965782b1f3..b01286575f 100644
--- a/lib/dns/keymgr.c
+++ b/lib/dns/keymgr.c
@@ -1512,6 +1512,7 @@ transition:
 			/* It is safe to make the transition. */
 			dst_key_setstate(dkey->key, i, next_state);
 			dst_key_settime(dkey->key, keystatetimes[i], now);
+			INSIST(dst_key_ismodified(dkey->key));
 			changed = true;
 		}
 	}
@@ -2183,9 +2184,10 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
 	for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
 	     dkey = ISC_LIST_NEXT(dkey, link))
 	{
-		if (!dkey->purge) {
+		if (dst_key_ismodified(dkey->key) && !dkey->purge) {
 			dns_dnssec_get_hints(dkey, now);
 			RETERR(dst_key_tofile(dkey->key, options, directory));
+			dst_key_setmodified(dkey->key, false);
 		}
 	}
 
@@ -2205,6 +2207,13 @@ failure:
 		}
 	}
 
+	if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+		char namebuf[DNS_NAME_FORMATSIZE];
+		dns_name_format(origin, namebuf, sizeof(namebuf));
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+			      DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(3),
+			      "keymgr: %s done", namebuf);
+	}
 	return (result);
 }
 
@@ -2282,6 +2291,9 @@ keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
 
 	dns_dnssec_get_hints(ksk_key, now);
 	result = dst_key_tofile(ksk_key->key, options, directory);
+	if (result == ISC_R_SUCCESS) {
+		dst_key_setmodified(ksk_key->key, false);
+	}
 	isc_dir_close(&dir);
 
 	return (result);
@@ -2582,6 +2594,9 @@ dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
 
 	dns_dnssec_get_hints(key, now);
 	result = dst_key_tofile(key->key, options, directory);
+	if (result == ISC_R_SUCCESS) {
+		dst_key_setmodified(key->key, false);
+	}
 	isc_dir_close(&dir);
 
 	return (result);
-- 
2.27.0

