1/*	$NetBSD: kaspconf.c,v 1.7 2024/02/21 22:52:44 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16#include <inttypes.h>
17#include <stdbool.h>
18#include <stdlib.h>
19
20#include <isc/mem.h>
21#include <isc/print.h>
22#include <isc/region.h>
23#include <isc/result.h>
24#include <isc/string.h>
25#include <isc/types.h>
26#include <isc/util.h>
27
28#include <dns/kasp.h>
29#include <dns/keyvalues.h>
30#include <dns/log.h>
31#include <dns/nsec3.h>
32#include <dns/secalg.h>
33#include <dns/ttl.h>
34
35#include <isccfg/cfg.h>
36#include <isccfg/duration.h>
37#include <isccfg/kaspconf.h>
38#include <isccfg/namedconf.h>
39
40#define DEFAULT_NSEC3PARAM_ITER	   0
41#define DEFAULT_NSEC3PARAM_SALTLEN 0
42
43/*
44 * Utility function for getting a configuration option.
45 */
46static isc_result_t
47confget(cfg_obj_t const *const *maps, const char *name, const cfg_obj_t **obj) {
48	for (size_t i = 0;; i++) {
49		if (maps[i] == NULL) {
50			return (ISC_R_NOTFOUND);
51		}
52		if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) {
53			return (ISC_R_SUCCESS);
54		}
55	}
56}
57
58/*
59 * Utility function for parsing durations from string.
60 */
61static uint32_t
62parse_duration(const char *str) {
63	uint32_t time = 0;
64	isccfg_duration_t duration;
65	isc_result_t result;
66	isc_textregion_t tr;
67
68	DE_CONST(str, tr.base);
69	tr.length = strlen(tr.base);
70	result = isccfg_parse_duration(&tr, &duration);
71	if (result == ISC_R_SUCCESS) {
72		time = isccfg_duration_toseconds(&duration);
73	}
74	return (time);
75}
76
77/*
78 * Utility function for configuring durations.
79 */
80static uint32_t
81get_duration(const cfg_obj_t **maps, const char *option, const char *dfl) {
82	const cfg_obj_t *obj;
83	isc_result_t result;
84	obj = NULL;
85
86	result = confget(maps, option, &obj);
87	if (result == ISC_R_NOTFOUND) {
88		return (parse_duration(dfl));
89	}
90	INSIST(result == ISC_R_SUCCESS);
91	return (cfg_obj_asduration(obj));
92}
93
94/*
95 * Create a new kasp key derived from configuration.
96 */
97static isc_result_t
98cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
99		       isc_log_t *logctx, uint32_t ksk_min_lifetime,
100		       uint32_t zsk_min_lifetime) {
101	isc_result_t result;
102	dns_kasp_key_t *key = NULL;
103
104	/* Create a new key reference. */
105	result = dns_kasp_key_create(kasp, &key);
106	if (result != ISC_R_SUCCESS) {
107		return (result);
108	}
109
110	if (config == NULL) {
111		/* We are creating a key reference for the default kasp. */
112		key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK;
113		key->lifetime = 0; /* unlimited */
114		key->algorithm = DNS_KEYALG_ECDSA256;
115		key->length = -1;
116	} else {
117		const char *rolestr = NULL;
118		const cfg_obj_t *obj = NULL;
119		isc_consttextregion_t alg;
120		bool error = false;
121
122		rolestr = cfg_obj_asstring(cfg_tuple_get(config, "role"));
123		if (strcmp(rolestr, "ksk") == 0) {
124			key->role |= DNS_KASP_KEY_ROLE_KSK;
125		} else if (strcmp(rolestr, "zsk") == 0) {
126			key->role |= DNS_KASP_KEY_ROLE_ZSK;
127		} else if (strcmp(rolestr, "csk") == 0) {
128			key->role |= DNS_KASP_KEY_ROLE_KSK;
129			key->role |= DNS_KASP_KEY_ROLE_ZSK;
130		}
131
132		key->lifetime = 0; /* unlimited */
133		obj = cfg_tuple_get(config, "lifetime");
134		if (cfg_obj_isduration(obj)) {
135			key->lifetime = cfg_obj_asduration(obj);
136		}
137		if (key->lifetime > 0) {
138			if (key->lifetime < 30 * (24 * 3600)) {
139				cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
140					    "dnssec-policy: key lifetime is "
141					    "shorter than 30 days");
142			}
143			if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0 &&
144			    key->lifetime <= ksk_min_lifetime)
145			{
146				error = true;
147			}
148			if ((key->role & DNS_KASP_KEY_ROLE_ZSK) != 0 &&
149			    key->lifetime <= zsk_min_lifetime)
150			{
151				error = true;
152			}
153			if (error) {
154				cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
155					    "dnssec-policy: key lifetime is "
156					    "shorter than the time it takes to "
157					    "do a rollover");
158				result = ISC_R_FAILURE;
159				goto cleanup;
160			}
161		}
162
163		obj = cfg_tuple_get(config, "algorithm");
164		alg.base = cfg_obj_asstring(obj);
165		alg.length = strlen(alg.base);
166		result = dns_secalg_fromtext(&key->algorithm,
167					     (isc_textregion_t *)&alg);
168		if (result != ISC_R_SUCCESS) {
169			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
170				    "dnssec-policy: bad algorithm %s",
171				    alg.base);
172			result = DNS_R_BADALG;
173			goto cleanup;
174		}
175
176		obj = cfg_tuple_get(config, "length");
177		if (cfg_obj_isuint32(obj)) {
178			uint32_t min, size;
179			size = cfg_obj_asuint32(obj);
180
181			switch (key->algorithm) {
182			case DNS_KEYALG_RSASHA1:
183			case DNS_KEYALG_NSEC3RSASHA1:
184			case DNS_KEYALG_RSASHA256:
185			case DNS_KEYALG_RSASHA512:
186				min = DNS_KEYALG_RSASHA512 ? 1024 : 512;
187				if (size < min || size > 4096) {
188					cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
189						    "dnssec-policy: key with "
190						    "algorithm %s has invalid "
191						    "key length %u",
192						    alg.base, size);
193					result = ISC_R_RANGE;
194					goto cleanup;
195				}
196				break;
197			case DNS_KEYALG_ECDSA256:
198			case DNS_KEYALG_ECDSA384:
199			case DNS_KEYALG_ED25519:
200			case DNS_KEYALG_ED448:
201				cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
202					    "dnssec-policy: key algorithm %s "
203					    "has predefined length; ignoring "
204					    "length value %u",
205					    alg.base, size);
206			default:
207				break;
208			}
209
210			key->length = size;
211		}
212	}
213
214	dns_kasp_addkey(kasp, key);
215	return (ISC_R_SUCCESS);
216
217cleanup:
218
219	dns_kasp_key_destroy(key);
220	return (result);
221}
222
223static isc_result_t
224cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
225			  isc_log_t *logctx) {
226	dns_kasp_key_t *kkey;
227	unsigned int min_keysize = 4096;
228	const cfg_obj_t *obj = NULL;
229	uint32_t iter = DEFAULT_NSEC3PARAM_ITER;
230	uint32_t saltlen = DEFAULT_NSEC3PARAM_SALTLEN;
231	uint32_t badalg = 0;
232	bool optout = false;
233	isc_result_t ret = ISC_R_SUCCESS;
234
235	/* How many iterations. */
236	obj = cfg_tuple_get(config, "iterations");
237	if (cfg_obj_isuint32(obj)) {
238		iter = cfg_obj_asuint32(obj);
239	}
240	dns_kasp_freeze(kasp);
241	for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
242	     kkey = ISC_LIST_NEXT(kkey, link))
243	{
244		unsigned int keysize = dns_kasp_key_size(kkey);
245		uint32_t keyalg = dns_kasp_key_algorithm(kkey);
246
247		if (keysize < min_keysize) {
248			min_keysize = keysize;
249		}
250
251		/* NSEC3 cannot be used with certain key algorithms. */
252		if (keyalg == DNS_KEYALG_RSAMD5 || keyalg == DNS_KEYALG_DH ||
253		    keyalg == DNS_KEYALG_DSA || keyalg == DNS_KEYALG_RSASHA1)
254		{
255			badalg = keyalg;
256		}
257	}
258	dns_kasp_thaw(kasp);
259
260	if (badalg > 0) {
261		char algstr[DNS_SECALG_FORMATSIZE];
262		dns_secalg_format((dns_secalg_t)badalg, algstr, sizeof(algstr));
263		cfg_obj_log(
264			obj, logctx, ISC_LOG_ERROR,
265			"dnssec-policy: cannot use nsec3 with algorithm '%s'",
266			algstr);
267		return (DNS_R_NSEC3BADALG);
268	}
269
270	if (iter > dns_nsec3_maxiterations()) {
271		ret = DNS_R_NSEC3ITERRANGE;
272	}
273
274	if (ret == DNS_R_NSEC3ITERRANGE) {
275		cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
276			    "dnssec-policy: nsec3 iterations value %u "
277			    "out of range",
278			    iter);
279		return (ret);
280	}
281
282	/* Opt-out? */
283	obj = cfg_tuple_get(config, "optout");
284	if (cfg_obj_isboolean(obj)) {
285		optout = cfg_obj_asboolean(obj);
286	}
287
288	/* Salt */
289	obj = cfg_tuple_get(config, "salt-length");
290	if (cfg_obj_isuint32(obj)) {
291		saltlen = cfg_obj_asuint32(obj);
292	}
293	if (saltlen > 0xff) {
294		cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
295			    "dnssec-policy: nsec3 salt length %u too high",
296			    saltlen);
297		return (DNS_R_NSEC3SALTRANGE);
298	}
299
300	dns_kasp_setnsec3param(kasp, iter, optout, saltlen);
301	return (ISC_R_SUCCESS);
302}
303
304isc_result_t
305cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
306		    isc_mem_t *mctx, isc_log_t *logctx,
307		    dns_kasplist_t *kasplist, dns_kasp_t **kaspp) {
308	isc_result_t result;
309	const cfg_obj_t *maps[2];
310	const cfg_obj_t *koptions = NULL;
311	const cfg_obj_t *keys = NULL;
312	const cfg_obj_t *nsec3 = NULL;
313	const cfg_listelt_t *element = NULL;
314	const char *kaspname = NULL;
315	dns_kasp_t *kasp = NULL;
316	size_t i = 0;
317	uint32_t sigrefresh = 0, sigvalidity = 0;
318	uint32_t dnskeyttl = 0, dsttl = 0, maxttl = 0;
319	uint32_t publishsafety = 0, retiresafety = 0;
320	uint32_t zonepropdelay = 0, parentpropdelay = 0;
321	uint32_t ipub = 0, iret = 0;
322	uint32_t ksk_min_lifetime = 0, zsk_min_lifetime = 0;
323
324	REQUIRE(config != NULL);
325	REQUIRE(kaspp != NULL && *kaspp == NULL);
326
327	kaspname = cfg_obj_asstring(cfg_tuple_get(config, "name"));
328	INSIST(kaspname != NULL);
329
330	cfg_obj_log(config, logctx, ISC_LOG_DEBUG(1),
331		    "dnssec-policy: load policy '%s'", kaspname);
332
333	result = dns_kasplist_find(kasplist, kaspname, &kasp);
334
335	if (result == ISC_R_SUCCESS) {
336		cfg_obj_log(
337			config, logctx, ISC_LOG_ERROR,
338			"dnssec-policy: duplicately named policy found '%s'",
339			kaspname);
340		dns_kasp_detach(&kasp);
341		return (ISC_R_EXISTS);
342	}
343	if (result != ISC_R_NOTFOUND) {
344		return (result);
345	}
346
347	/* No kasp with configured name was found in list, create new one. */
348	INSIST(kasp == NULL);
349	result = dns_kasp_create(mctx, kaspname, &kasp);
350	if (result != ISC_R_SUCCESS) {
351		return (result);
352	}
353	INSIST(kasp != NULL);
354
355	/* Now configure. */
356	INSIST(DNS_KASP_VALID(kasp));
357
358	if (config != NULL) {
359		koptions = cfg_tuple_get(config, "options");
360		maps[i++] = koptions;
361	}
362	maps[i] = NULL;
363
364	/* Configuration: Signatures */
365	sigrefresh = get_duration(maps, "signatures-refresh",
366				  DNS_KASP_SIG_REFRESH);
367	dns_kasp_setsigrefresh(kasp, sigrefresh);
368
369	sigvalidity = get_duration(maps, "signatures-validity-dnskey",
370				   DNS_KASP_SIG_VALIDITY_DNSKEY);
371	if (sigrefresh >= (sigvalidity * 0.9)) {
372		cfg_obj_log(
373			config, logctx, ISC_LOG_ERROR,
374			"dnssec-policy: policy '%s' signatures-refresh must be "
375			"at most 90%% of the signatures-validity-dnskey",
376			kaspname);
377		result = ISC_R_FAILURE;
378	}
379	dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity);
380
381	sigvalidity = get_duration(maps, "signatures-validity",
382				   DNS_KASP_SIG_VALIDITY);
383	if (sigrefresh >= (sigvalidity * 0.9)) {
384		cfg_obj_log(
385			config, logctx, ISC_LOG_ERROR,
386			"dnssec-policy: policy '%s' signatures-refresh must be "
387			"at most 90%% of the signatures-validity",
388			kaspname);
389		result = ISC_R_FAILURE;
390	}
391	dns_kasp_setsigvalidity(kasp, sigvalidity);
392
393	if (result != ISC_R_SUCCESS) {
394		goto cleanup;
395	}
396
397	/* Configuration: Zone settings */
398	maxttl = get_duration(maps, "max-zone-ttl", DNS_KASP_ZONE_MAXTTL);
399	dns_kasp_setzonemaxttl(kasp, maxttl);
400
401	zonepropdelay = get_duration(maps, "zone-propagation-delay",
402				     DNS_KASP_ZONE_PROPDELAY);
403	dns_kasp_setzonepropagationdelay(kasp, zonepropdelay);
404
405	/* Configuration: Parent settings */
406	dsttl = get_duration(maps, "parent-ds-ttl", DNS_KASP_DS_TTL);
407	dns_kasp_setdsttl(kasp, dsttl);
408
409	parentpropdelay = get_duration(maps, "parent-propagation-delay",
410				       DNS_KASP_PARENT_PROPDELAY);
411	dns_kasp_setparentpropagationdelay(kasp, parentpropdelay);
412
413	/* Configuration: Keys */
414	dnskeyttl = get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL);
415	dns_kasp_setdnskeyttl(kasp, dnskeyttl);
416
417	publishsafety = get_duration(maps, "publish-safety",
418				     DNS_KASP_PUBLISH_SAFETY);
419	dns_kasp_setpublishsafety(kasp, publishsafety);
420
421	retiresafety = get_duration(maps, "retire-safety",
422				    DNS_KASP_RETIRE_SAFETY);
423	dns_kasp_setretiresafety(kasp, retiresafety);
424
425	dns_kasp_setpurgekeys(
426		kasp, get_duration(maps, "purge-keys", DNS_KASP_PURGE_KEYS));
427
428	ipub = dnskeyttl + publishsafety + zonepropdelay;
429	iret = dsttl + retiresafety + parentpropdelay;
430	ksk_min_lifetime = ISC_MAX(ipub, iret);
431
432	iret = (sigvalidity - sigrefresh) + maxttl + retiresafety +
433	       zonepropdelay;
434	zsk_min_lifetime = ISC_MAX(ipub, iret);
435
436	(void)confget(maps, "keys", &keys);
437	if (keys != NULL) {
438		char role[256] = { 0 };
439		bool warn[256][2] = { { false } };
440		dns_kasp_key_t *kkey = NULL;
441
442		for (element = cfg_list_first(keys); element != NULL;
443		     element = cfg_list_next(element))
444		{
445			cfg_obj_t *kobj = cfg_listelt_value(element);
446			result = cfg_kaspkey_fromconfig(kobj, kasp, logctx,
447							ksk_min_lifetime,
448							zsk_min_lifetime);
449			if (result != ISC_R_SUCCESS) {
450				goto cleanup;
451			}
452		}
453		dns_kasp_freeze(kasp);
454		for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
455		     kkey = ISC_LIST_NEXT(kkey, link))
456		{
457			uint32_t keyalg = dns_kasp_key_algorithm(kkey);
458			INSIST(keyalg < ARRAY_SIZE(role));
459
460			if (dns_kasp_key_zsk(kkey)) {
461				if ((role[keyalg] & DNS_KASP_KEY_ROLE_ZSK) != 0)
462				{
463					warn[keyalg][0] = true;
464				}
465				role[keyalg] |= DNS_KASP_KEY_ROLE_ZSK;
466			}
467
468			if (dns_kasp_key_ksk(kkey)) {
469				if ((role[keyalg] & DNS_KASP_KEY_ROLE_KSK) != 0)
470				{
471					warn[keyalg][1] = true;
472				}
473				role[keyalg] |= DNS_KASP_KEY_ROLE_KSK;
474			}
475		}
476		dns_kasp_thaw(kasp);
477		for (i = 0; i < ARRAY_SIZE(role); i++) {
478			if (role[i] == 0) {
479				continue;
480			}
481			if (role[i] !=
482			    (DNS_KASP_KEY_ROLE_ZSK | DNS_KASP_KEY_ROLE_KSK))
483			{
484				cfg_obj_log(keys, logctx, ISC_LOG_ERROR,
485					    "dnssec-policy: algorithm %zu "
486					    "requires both KSK and ZSK roles",
487					    i);
488				result = ISC_R_FAILURE;
489			}
490			if (warn[i][0]) {
491				cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
492					    "dnssec-policy: algorithm %zu has "
493					    "multiple keys with ZSK role",
494					    i);
495			}
496			if (warn[i][1]) {
497				cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
498					    "dnssec-policy: algorithm %zu has "
499					    "multiple keys with KSK role",
500					    i);
501			}
502		}
503		if (result != ISC_R_SUCCESS) {
504			goto cleanup;
505		}
506	} else if (default_kasp) {
507		dns_kasp_key_t *key, *new_key;
508		/*
509		 * If there are no specific keys configured in the policy,
510		 * inherit from the default policy (except for the built-in
511		 * "insecure" policy).
512		 */
513		for (key = ISC_LIST_HEAD(dns_kasp_keys(default_kasp));
514		     key != NULL; key = ISC_LIST_NEXT(key, link))
515		{
516			/* Create a new key reference. */
517			new_key = NULL;
518			result = dns_kasp_key_create(kasp, &new_key);
519			if (result != ISC_R_SUCCESS) {
520				goto cleanup;
521			}
522
523			if (dns_kasp_key_ksk(key)) {
524				new_key->role |= DNS_KASP_KEY_ROLE_KSK;
525			}
526			if (dns_kasp_key_zsk(key)) {
527				new_key->role |= DNS_KASP_KEY_ROLE_ZSK;
528			}
529			new_key->lifetime = dns_kasp_key_lifetime(key);
530			new_key->algorithm = dns_kasp_key_algorithm(key);
531			new_key->length = dns_kasp_key_size(key);
532			dns_kasp_addkey(kasp, new_key);
533		}
534	}
535
536	if (strcmp(kaspname, "insecure") == 0) {
537		/* "dnssec-policy insecure": key list must be empty */
538		INSIST(dns_kasp_keylist_empty(kasp));
539	} else if (default_kasp != NULL) {
540		/* There must be keys configured. */
541		INSIST(!(dns_kasp_keylist_empty(kasp)));
542	}
543
544	/* Configuration: NSEC3 */
545	(void)confget(maps, "nsec3param", &nsec3);
546	if (nsec3 == NULL) {
547		if (default_kasp != NULL && dns_kasp_nsec3(default_kasp)) {
548			dns_kasp_setnsec3param(
549				kasp, dns_kasp_nsec3iter(default_kasp),
550				(dns_kasp_nsec3flags(default_kasp) == 0x01),
551				dns_kasp_nsec3saltlen(default_kasp));
552		} else {
553			dns_kasp_setnsec3(kasp, false);
554		}
555	} else {
556		dns_kasp_setnsec3(kasp, true);
557		result = cfg_nsec3param_fromconfig(nsec3, kasp, logctx);
558		if (result != ISC_R_SUCCESS) {
559			goto cleanup;
560		}
561	}
562
563	/* Append it to the list for future lookups. */
564	ISC_LIST_APPEND(*kasplist, kasp, link);
565	INSIST(!(ISC_LIST_EMPTY(*kasplist)));
566
567	/* Success: Attach the kasp to the pointer and return. */
568	dns_kasp_attach(kasp, kaspp);
569
570	/* Don't detach as kasp is on '*kasplist' */
571	return (ISC_R_SUCCESS);
572
573cleanup:
574
575	/* Something bad happened, detach (destroys kasp) and return error. */
576	dns_kasp_detach(&kasp);
577	return (result);
578}
579