From 42f43cebdd6887d42e9b440c2f2e15dd0812f252 Mon Sep 17 00:00:00 2001
From: Matthijs Mekking <matthijs@isc.org>
Date: Mon, 10 Jan 2022 17:18:47 +0100
Subject: [PATCH] Update dns_dnssec_syncdelete() function

Update the function that synchronizes the CDS and CDNSKEY DELETE
records. It now allows for the possibility that the CDS DELETE record
is published and the CDNSKEY DELETE record is not, and vice versa.

Also update the code in zone.c how 'dns_dnssec_syncdelete()' is called.

With KASP, we still maintain the DELETE records our self. Otherwise,
we publish the CDS and CDNSKEY DELETE record only if they are added
to the zone. We do still check if these records can be signed by a KSK.

This change will allow users to add a CDS and/or CDNSKEY DELETE record
manually, without BIND removing them on the next zone sign.

Note that this commit removes the check whether the key is a KSK, this
check is redundant because this check is also made in
'dst_key_is_signing()' when the role is set to DST_BOOL_KSK.

(cherry picked from commit 3d05c99abbfc34644ffeffe2884b6335fc12e055)
Conflict: NA
Reference: https://gitlab.isc.org/isc-projects/bind9/-/commit/42f43cebdd6887d42e9b440c2f2e15dd0812f252
---
 lib/dns/dnssec.c             | 44 +++++++++---------
 lib/dns/include/dns/dnssec.h |  9 ++--
 lib/dns/zone.c               | 87 +++++++++++++++++++++++++++++-------
 3 files changed, 99 insertions(+), 41 deletions(-)

diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index f9ae6acbb4..64c72b5d7a 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -2147,7 +2147,7 @@ isc_result_t
 dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
 		      dns_name_t *origin, dns_rdataclass_t zclass,
 		      dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx,
-		      bool dnssec_insecure) {
+		      bool expect_cds_delete, bool expect_cdnskey_delete) {
 	unsigned char dsbuf[5] = { 0, 0, 0, 0, 0 };  /* CDS DELETE rdata */
 	unsigned char keybuf[5] = { 0, 0, 3, 0, 0 }; /* CDNSKEY DELETE rdata */
 	char namebuf[DNS_NAME_FORMATSIZE];
@@ -2167,26 +2167,39 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
 
 	dns_name_format(origin, namebuf, sizeof(namebuf));
 
-	if (dnssec_insecure) {
-		if (!dns_rdataset_isassociated(cdnskey) ||
-		    !exists(cdnskey, &cdnskey_delete)) {
+	if (expect_cds_delete) {
+		if (!dns_rdataset_isassociated(cds) ||
+		    !exists(cds, &cds_delete)) {
 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
 				      DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
-				      "CDNSKEY (DELETE) for zone %s is now "
+				      "CDS (DELETE) for zone %s is now "
 				      "published",
 				      namebuf);
-			RETERR(addrdata(&cdnskey_delete, diff, origin, ttl,
+			RETERR(addrdata(&cds_delete, diff, origin, ttl, mctx));
+		}
+	} else {
+		if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete))
+		{
+			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+				      DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+				      "CDS (DELETE) for zone %s is now "
+				      "deleted",
+				      namebuf);
+			RETERR(delrdata(&cds_delete, diff, origin, cds->ttl,
 					mctx));
 		}
+	}
 
-		if (!dns_rdataset_isassociated(cds) ||
-		    !exists(cds, &cds_delete)) {
+	if (expect_cdnskey_delete) {
+		if (!dns_rdataset_isassociated(cdnskey) ||
+		    !exists(cdnskey, &cdnskey_delete)) {
 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
 				      DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
-				      "CDS (DELETE) for zone %s is now "
+				      "CDNSKEY (DELETE) for zone %s is now "
 				      "published",
 				      namebuf);
-			RETERR(addrdata(&cds_delete, diff, origin, ttl, mctx));
+			RETERR(addrdata(&cdnskey_delete, diff, origin, ttl,
+					mctx));
 		}
 	} else {
 		if (dns_rdataset_isassociated(cdnskey) &&
@@ -2199,17 +2212,6 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
 			RETERR(delrdata(&cdnskey_delete, diff, origin,
 					cdnskey->ttl, mctx));
 		}
-
-		if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete))
-		{
-			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
-				      DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
-				      "CDS (DELETE) for zone %s is now "
-				      "deleted",
-				      namebuf);
-			RETERR(delrdata(&cds_delete, diff, origin, cds->ttl,
-					mctx));
-		}
 	}
 
 	result = ISC_R_SUCCESS;
diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h
index 0ac96fceb5..9791ef128d 100644
--- a/lib/dns/include/dns/dnssec.h
+++ b/lib/dns/include/dns/dnssec.h
@@ -370,11 +370,14 @@ isc_result_t
 dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
 		      dns_name_t *origin, dns_rdataclass_t zclass,
 		      dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx,
-		      bool dnssec_insecure);
+		      bool expect_cds_delete, bool expect_cdnskey_delete);
 /*%<
  * Add or remove the CDS DELETE record and the CDNSKEY DELETE record.
- * If 'dnssec_insecure' is true, the DELETE records should be present.
- * Otherwise, the DELETE records must be removed from the RRsets (if present).
+ * If 'expect_cds_delete' is true, the CDS DELETE record should be present.
+ * Otherwise, the CDS DELETE record must be removed from the RRsets (if
+ * present). If 'expect_cdnskey_delete' is true, the CDNSKEY DELETE record
+ * should be present. Otherwise, the CDNSKEY DELETE record must be removed
+ * from the RRsets (if present).
  *
  * Returns:
  *\li   ISC_R_SUCCESS
diff --git a/lib/dns/zone.c b/lib/dns/zone.c
index 131e3200d2..76f03683de 100644
--- a/lib/dns/zone.c
+++ b/lib/dns/zone.c
@@ -21483,16 +21483,69 @@ zone_rekey(dns_zone_t *zone) {
 	KASP_UNLOCK(kasp);
 
 	if (result == ISC_R_SUCCESS) {
-		bool cds_delete = false;
+		bool cdsdel = false;
+		bool cdnskeydel = false;
 		isc_stdtime_t when;
 
 		/*
 		 * Publish CDS/CDNSKEY DELETE records if the zone is
 		 * transitioning from secure to insecure.
 		 */
-		if (kasp != NULL &&
-		    strcmp(dns_kasp_getname(kasp), "insecure") == 0) {
-			cds_delete = true;
+		if (kasp != NULL) {
+			if (strcmp(dns_kasp_getname(kasp), "insecure") == 0) {
+				cdsdel = true;
+				cdnskeydel = true;
+			}
+		} else {
+			/* Check if there is a CDS DELETE record. */
+			if (dns_rdataset_isassociated(&cdsset)) {
+				for (result = dns_rdataset_first(&cdsset);
+				     result == ISC_R_SUCCESS;
+				     result = dns_rdataset_next(&cdsset))
+				{
+					dns_rdata_t crdata = DNS_RDATA_INIT;
+					dns_rdataset_current(&cdsset, &crdata);
+					/*
+					 * CDS deletion record has this form
+					 * "0 0 0 00" which is 5 zero octets.
+					 */
+					if (crdata.length == 5U &&
+					    memcmp(crdata.data,
+						   (unsigned char[5]){ 0, 0, 0,
+								       0, 0 },
+						   5) == 0)
+					{
+						cdsdel = true;
+						break;
+					}
+				}
+			}
+
+			/* Check if there is a CDNSKEY DELETE record. */
+			if (dns_rdataset_isassociated(&cdnskeyset)) {
+				for (result = dns_rdataset_first(&cdnskeyset);
+				     result == ISC_R_SUCCESS;
+				     result = dns_rdataset_next(&cdnskeyset))
+				{
+					dns_rdata_t crdata = DNS_RDATA_INIT;
+					dns_rdataset_current(&cdnskeyset,
+							     &crdata);
+					/*
+					 * CDNSKEY deletion record has this form
+					 * "0 3 0 AA==" which is 2 zero octets,
+					 * a 3, and 2 zero octets.
+					 */
+					if (crdata.length == 5U &&
+					    memcmp(crdata.data,
+						   (unsigned char[5]){ 0, 0, 3,
+								       0, 0 },
+						   5) == 0)
+					{
+						cdnskeydel = true;
+						break;
+					}
+				}
+			}
 		}
 
 		/*
@@ -21529,36 +21582,36 @@ zone_rekey(dns_zone_t *zone) {
 			goto failure;
 		}
 
-		if (cds_delete) {
+		if (cdsdel || cdnskeydel) {
 			/*
 			 * Only publish CDS/CDNSKEY DELETE records if there is
 			 * a KSK that can be used to verify the RRset. This
 			 * means there must be a key with the KSK role that is
 			 * published and is used for signing.
 			 */
-			cds_delete = false;
+			bool allow = false;
 			for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
 			     key = ISC_LIST_NEXT(key, link)) {
 				dst_key_t *dstk = key->key;
-				bool ksk = false;
-				(void)dst_key_getbool(dstk, DST_BOOL_KSK, &ksk);
-				if (!ksk) {
-					continue;
-				}
 
-				if (dst_key_haskasp(dstk) &&
-				    dst_key_is_published(dstk, now, &when) &&
+				if (dst_key_is_published(dstk, now, &when) &&
 				    dst_key_is_signing(dstk, DST_BOOL_KSK, now,
 						       &when))
 				{
-					cds_delete = true;
+					allow = true;
 					break;
 				}
 			}
+			if (cdsdel) {
+				cdsdel = allow;
+			}
+			if (cdnskeydel) {
+				cdnskeydel = allow;
+			}
 		}
-		result = dns_dnssec_syncdelete(&cdsset, &cdnskeyset,
-					       &zone->origin, zone->rdclass,
-					       ttl, &diff, mctx, cds_delete);
+		result = dns_dnssec_syncdelete(
+			&cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl,
+			&diff, mctx, cdsdel, cdnskeydel);
 		if (result != ISC_R_SUCCESS) {
 			dnssec_log(zone, ISC_LOG_ERROR,
 				   "zone_rekey:couldn't update CDS/CDNSKEY "
-- 
2.23.0

