Commit e0ca243a93 for bind

commit e0ca243a937d3a5cb9503d6197eb266e495b7a4e
Author: Martin Basti <mbasti@isc.org>
Date:   Mon Sep 7 13:20:50 2026 +0000

    Fix the minimum RSA key length check in dnssec-policy

    The non-FIPS minimum key length for RSA algorithms was computed as
    "DST_ALG_RSASHA512 ? 1024 : 512", testing the constant instead of the
    key's algorithm, so the intended 512-bit floor was unreachable and
    1024 was always enforced. Compare the algorithm instead: 1024 for the
    RSASHA512 based algorithms and 512 for the other RSA algorithms,
    matching the limits dnssec-keygen enforces.

    Assisted-by: Claude:claude-fable-5

diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c
index 297fa2ed0d..3156bb13a7 100644
--- a/lib/isccfg/kaspconf.c
+++ b/lib/isccfg/kaspconf.c
@@ -327,8 +327,14 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
 			case DST_ALG_RSASHA512PRIVATEOID:
 				if (isc_crypto_fips_mode()) {
 					min = 2048;
+				} else if (key->algorithm ==
+						   DST_ALG_RSASHA512 ||
+					   key->algorithm ==
+						   DST_ALG_RSASHA512PRIVATEOID)
+				{
+					min = 1024;
 				} else {
-					min = DST_ALG_RSASHA512 ? 1024 : 512;
+					min = 512;
 				}
 				if (size < min || size > 4096) {
 					if (log_errors) {
diff --git a/tests/isccfg/kaspconf_test.c b/tests/isccfg/kaspconf_test.c
new file mode 100644
index 0000000000..f3d1f99a18
--- /dev/null
+++ b/tests/isccfg/kaspconf_test.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/crypto.h>
+#include <isc/lib.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include <dns/kasp.h>
+#include <dns/keystore.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/kaspconf.h>
+#include <isccfg/namedconf.h>
+
+#include <tests/isc.h>
+
+ISC_SETUP_TEST_IMPL(group) {
+	isc_logconfig_t *logconfig = isc_logconfig_get();
+	isc_log_createandusechannel(
+		logconfig, "default_stderr", ISC_LOG_TOFILEDESC,
+		ISC_LOG_DYNAMIC, ISC_LOGDESTINATION_STDERR, 0,
+		ISC_LOGCATEGORY_DEFAULT, ISC_LOGMODULE_DEFAULT);
+
+	return 0;
+}
+
+/* Parse a dnssec-policy statement and configure a kasp from it. */
+static isc_result_t
+kasp_fromtext(const char *conftext) {
+	isc_result_t result;
+	isc_buffer_t b;
+	cfg_obj_t *config = NULL;
+	const cfg_obj_t *kasps = NULL;
+	dns_kasp_t *kasp = NULL;
+	dns_kasplist_t kasplist;
+	dns_keystorelist_t keystores;
+
+	ISC_LIST_INIT(kasplist);
+	ISC_LIST_INIT(keystores);
+
+	/* Add default key-store "key-directory". */
+	result = cfg_keystore_fromconfig(NULL, isc_g_mctx, &keystores, NULL);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	isc_buffer_constinit(&b, conftext, strlen(conftext));
+	isc_buffer_add(&b, strlen(conftext));
+	result = cfg_parse_buffer(&b, "text", 0, &cfg_type_namedconf, 0,
+				  &config);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	result = cfg_map_get(config, "dnssec-policy", &kasps);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	result = cfg_kasp_fromconfig(cfg_listelt_value(cfg_list_first(kasps)),
+				     NULL, 0, isc_g_mctx, &keystores, &kasplist,
+				     &kasp);
+	if (kasp != NULL) {
+		dns_kasp_detach(&kasp);
+	}
+
+	ISC_LIST_FOREACH(kasplist, k, link) {
+		ISC_LIST_UNLINK(kasplist, k, link);
+		dns_kasp_detach(&k);
+	}
+	ISC_LIST_FOREACH(keystores, ks, link) {
+		ISC_LIST_UNLINK(keystores, ks, link);
+		dns_keystore_detach(&ks);
+	}
+	cfg_obj_detach(&config);
+
+	return result;
+}
+
+#define CSK_POLICY(alg)                                              \
+	"dnssec-policy \"p\" {\n"                                    \
+	"	keys {\n"                                                  \
+	"		csk lifetime unlimited algorithm " alg ";\n" \
+	"	};\n"                                                      \
+	"};\n"
+
+ISC_RUN_TEST_IMPL(kaspkey_rsa_length) {
+	struct {
+		const char *conftext;
+		isc_result_t expected;
+	} testcases[] = {
+		/* The minimum for RSASHA512 based algorithms is 1024... */
+		{ CSK_POLICY("rsasha512 512"), ISC_R_RANGE },
+		{ CSK_POLICY("rsasha512 1024"), ISC_R_SUCCESS },
+		{ CSK_POLICY("rsasha512oid 512"), ISC_R_RANGE },
+		{ CSK_POLICY("rsasha512oid 1024"), ISC_R_SUCCESS },
+		/* ...but 512 for the other RSA algorithms. */
+		{ CSK_POLICY("rsasha256 512"), ISC_R_SUCCESS },
+		{ CSK_POLICY("rsasha256 511"), ISC_R_RANGE },
+		{ CSK_POLICY("rsasha256oid 512"), ISC_R_SUCCESS },
+		{ CSK_POLICY("rsasha1 512"), ISC_R_SUCCESS },
+		/* The maximum is 4096 for all of them. */
+		{ CSK_POLICY("rsasha256 4097"), ISC_R_RANGE },
+	};
+
+	if (isc_crypto_fips_mode()) {
+		skip();
+		return;
+	}
+
+	for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
+		isc_result_t result = kasp_fromtext(testcases[i].conftext);
+		assert_int_equal(result, testcases[i].expected);
+	}
+}
+
+ISC_TEST_LIST_START
+ISC_TEST_ENTRY(kaspkey_rsa_length)
+ISC_TEST_LIST_END
+
+ISC_TEST_MAIN_CUSTOM(setup_test_group, NULL)
diff --git a/tests/isccfg/meson.build b/tests/isccfg/meson.build
index 66adda5ccd..741fa4fa7a 100644
--- a/tests/isccfg/meson.build
+++ b/tests/isccfg/meson.build
@@ -12,6 +12,7 @@
 isccfg_tests = [
     'duration',
     'grammar',
+    'kaspconf',
     'parser',
 ]

@@ -32,6 +33,7 @@ foreach unit : isccfg_tests
         sources: default_sanitize_options,
         dependencies: [
             libisc_dep,
+            libdns_dep,
             libisccfg_dep,
             libtest_dep,