/*
 * 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 <stdbool.h>
#include <stdlib.h>

#include <isc/crypto.h>
#include <isc/log.h>
#include <isc/mem.h>
#include <isc/region.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/types.h>
#include <isc/util.h>

#include <dns/kasp.h>
#include <dns/keystore.h>
#include <dns/keyvalues.h>
#include <dns/nsec3.h>
#include <dns/secalg.h>
#include <dns/ttl.h>

#include <dst/dst.h>

#include <isccfg/cfg.h>
#include <isccfg/duration.h>
#include <isccfg/kaspconf.h>
#include <isccfg/namedconf.h>

#define DEFAULT_NSEC3PARAM_ITER	   0
#define DEFAULT_NSEC3PARAM_SALTLEN 0

/*
 * Utility function for getting a configuration option.
 */
static isc_result_t
confget(cfg_obj_t const *const *maps, const char *name, const cfg_obj_t **obj) {
	for (size_t i = 0;; i++) {
		if (maps[i] == NULL) {
			return ISC_R_NOTFOUND;
		}
		if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) {
			return ISC_R_SUCCESS;
		}
	}
}

/*
 * Utility function for parsing durations from string.
 */
static uint32_t
parse_duration(const char *str) {
	uint32_t time = 0;
	isccfg_duration_t duration;
	isc_result_t result;
	isc_textregion_t tr;

	tr.base = UNCONST(str);
	tr.length = strlen(tr.base);
	result = isccfg_parse_duration(&tr, &duration);
	if (result == ISC_R_SUCCESS) {
		time = isccfg_duration_toseconds(&duration);
	}
	return time;
}

/*
 * Utility function for configuring durations.
 */
static uint32_t
get_duration(const cfg_obj_t **maps, const char *option, const char *dfl) {
	const cfg_obj_t *obj;
	isc_result_t result;
	obj = NULL;

	result = confget(maps, option, &obj);
	if (result == ISC_R_NOTFOUND) {
		return parse_duration(dfl);
	}
	INSIST(result == ISC_R_SUCCESS);
	return cfg_obj_asduration(obj);
}

/*
 * Utility function for configuring strings.
 */
static const char *
get_string(const cfg_obj_t **maps, const char *option) {
	const cfg_obj_t *obj;
	isc_result_t result;
	obj = NULL;

	result = confget(maps, option, &obj);
	if (result == ISC_R_NOTFOUND) {
		return NULL;
	}
	INSIST(result == ISC_R_SUCCESS);
	return cfg_obj_asstring(obj);
}

/*
 * Create a new kasp key derived from configuration.
 */
static isc_result_t
cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
		       bool check_algorithms, bool offline_ksk,
		       dns_keystorelist_t *keystorelist,
		       uint32_t ksk_min_lifetime, uint32_t zsk_min_lifetime) {
	isc_result_t result;
	dns_kasp_key_t *key = NULL;
	const cfg_obj_t *tagrange = NULL;

	/* Create a new key reference. */
	dns_kasp_key_create(kasp, &key);

	if (config == NULL) {
		/* We are creating a key reference for the default kasp. */
		INSIST(!offline_ksk);
		key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK;
		key->lifetime = 0; /* unlimited */
		key->algorithm = DST_ALG_ECDSA256;
		key->length = -1;
		result = dns_keystorelist_find(keystorelist,
					       DNS_KEYSTORE_KEYDIRECTORY,
					       &key->keystore);
		if (result != ISC_R_SUCCESS) {
			goto cleanup;
		}
	} else {
		const char *rolestr = NULL;
		const char *keydir = NULL;
		const cfg_obj_t *obj = NULL;
		isc_consttextregion_t alg;
		bool error = false;

		rolestr = cfg_obj_asstring(cfg_tuple_get(config, "role"));
		if (strcmp(rolestr, "ksk") == 0) {
			key->role |= DNS_KASP_KEY_ROLE_KSK;
		} else if (strcmp(rolestr, "zsk") == 0) {
			key->role |= DNS_KASP_KEY_ROLE_ZSK;
		} else if (strcmp(rolestr, "csk") == 0) {
			if (offline_ksk) {
				cfg_obj_log(
					config, ISC_LOG_ERROR,
					"dnssec-policy: csk keys are not "
					"allowed when offline-ksk is enabled");
				result = ISC_R_FAILURE;
				goto cleanup;
			}
			key->role |= DNS_KASP_KEY_ROLE_KSK;
			key->role |= DNS_KASP_KEY_ROLE_ZSK;
		}

		obj = cfg_tuple_get(config, "keystorage");
		if (cfg_obj_isstring(obj)) {
			keydir = cfg_obj_asstring(obj);
		}
		if (keydir == NULL) {
			keydir = DNS_KEYSTORE_KEYDIRECTORY;
		}
		result = dns_keystorelist_find(keystorelist, keydir,
					       &key->keystore);
		if (result == ISC_R_NOTFOUND) {
			cfg_obj_log(obj, ISC_LOG_ERROR,
				    "dnssec-policy: keystore %s does not exist",
				    keydir);
			result = ISC_R_FAILURE;
			goto cleanup;
		} else if (result != ISC_R_SUCCESS) {
			cfg_obj_log(obj, ISC_LOG_ERROR,
				    "dnssec-policy: bad keystore %s", keydir);
			result = ISC_R_FAILURE;
			goto cleanup;
		}
		INSIST(key->keystore != NULL);

		key->lifetime = 0; /* unlimited */
		obj = cfg_tuple_get(config, "lifetime");
		if (cfg_obj_isduration(obj)) {
			key->lifetime = cfg_obj_asduration(obj);
		}
		if (key->lifetime > 0) {
			if (key->lifetime < 30 * (24 * 3600)) {
				cfg_obj_log(obj, ISC_LOG_WARNING,
					    "dnssec-policy: key lifetime is "
					    "shorter than 30 days");
			}
			if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0 &&
			    key->lifetime <= ksk_min_lifetime)
			{
				error = true;
			}
			if ((key->role & DNS_KASP_KEY_ROLE_ZSK) != 0 &&
			    key->lifetime <= zsk_min_lifetime)
			{
				error = true;
			}
			if (error) {
				cfg_obj_log(obj, ISC_LOG_ERROR,
					    "dnssec-policy: key lifetime is "
					    "shorter than the time it takes to "
					    "do a rollover");
				result = ISC_R_FAILURE;
				goto cleanup;
			}
		}

		obj = cfg_tuple_get(config, "algorithm");
		alg.base = cfg_obj_asstring(obj);
		alg.length = strlen(alg.base);
		result = dst_algorithm_fromtext(&key->algorithm,
						(isc_textregion_t *)&alg);
		if (result != ISC_R_SUCCESS) {
			cfg_obj_log(obj, ISC_LOG_ERROR,
				    "dnssec-policy: bad algorithm %s",
				    alg.base);
			result = DNS_R_BADALG;
			goto cleanup;
		}
		if (check_algorithms && isc_crypto_fips_mode() &&
		    (key->algorithm == DST_ALG_RSASHA1 ||
		     key->algorithm == DST_ALG_NSEC3RSASHA1))
		{
			cfg_obj_log(obj, ISC_LOG_ERROR,
				    "dnssec-policy: algorithm %s not supported "
				    "in FIPS mode",
				    alg.base);
			result = DNS_R_BADALG;
			goto cleanup;
		}

		if (check_algorithms &&
		    !dst_algorithm_supported(key->algorithm))
		{
			cfg_obj_log(obj, ISC_LOG_ERROR,
				    "dnssec-policy: algorithm %s not supported",
				    alg.base);
			result = DNS_R_BADALG;
			goto cleanup;
		}

		switch (key->algorithm) {
		case DST_ALG_RSASHA1:
		case DST_ALG_NSEC3RSASHA1:
			cfg_obj_log(obj, ISC_LOG_WARNING,
				    "dnssec-policy: DNSSEC algorithm %s is "
				    "deprecated",
				    alg.base);
			break;
		default:
			break;
		}

		obj = cfg_tuple_get(config, "length");
		if (cfg_obj_isuint32(obj)) {
			uint32_t min, size;
			size = cfg_obj_asuint32(obj);

			switch (key->algorithm) {
			case DST_ALG_RSASHA1:
			case DST_ALG_NSEC3RSASHA1:
			case DST_ALG_RSASHA256:
			case DST_ALG_RSASHA512:
			case DST_ALG_RSASHA256PRIVATEOID:
			case DST_ALG_RSASHA512PRIVATEOID:
				if (isc_crypto_fips_mode()) {
					min = 2048;
				} else {
					min = DST_ALG_RSASHA512 ? 1024 : 512;
				}
				if (size < min || size > 4096) {
					cfg_obj_log(obj, ISC_LOG_ERROR,
						    "dnssec-policy: key with "
						    "algorithm %s has invalid "
						    "key length %u",
						    alg.base, size);
					result = ISC_R_RANGE;
					goto cleanup;
				}
				break;
			case DST_ALG_ECDSA256:
			case DST_ALG_ECDSA384:
			case DST_ALG_ED25519:
			case DST_ALG_ED448:
				cfg_obj_log(obj, ISC_LOG_WARNING,
					    "dnssec-policy: key algorithm %s "
					    "has predefined length; ignoring "
					    "length value %u",
					    alg.base, size);
			default:
				break;
			}

			key->length = size;
		}

		tagrange = cfg_tuple_get(config, "tag-range");
		if (cfg_obj_istuple(tagrange)) {
			uint32_t tag_min = 0, tag_max = 0xffff;
			obj = cfg_tuple_get(tagrange, "tag-min");
			tag_min = cfg_obj_asuint32(obj);
			if (tag_min > 0xffff) {
				cfg_obj_log(obj, ISC_LOG_ERROR,
					    "dnssec-policy: tag-min "
					    "too big");
				result = ISC_R_RANGE;
				goto cleanup;
			}
			obj = cfg_tuple_get(tagrange, "tag-max");
			tag_max = cfg_obj_asuint32(obj);
			if (tag_max > 0xffff) {
				cfg_obj_log(obj, ISC_LOG_ERROR,
					    "dnssec-policy: tag-max "
					    "too big");
				result = ISC_R_RANGE;
				goto cleanup;
			}
			if (tag_min >= tag_max) {
				cfg_obj_log(
					obj, ISC_LOG_ERROR,
					"dnssec-policy: tag-min >= tag_max");
				result = ISC_R_RANGE;
				goto cleanup;
			}
			key->tag_min = tag_min;
			key->tag_max = tag_max;
		}
	}

	dns_kasp_addkey(kasp, key);
	return ISC_R_SUCCESS;

cleanup:

	dns_kasp_key_destroy(key);
	return result;
}

static isc_result_t
cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) {
	unsigned int min_keysize = 4096;
	const cfg_obj_t *obj = NULL;
	uint32_t iter = DEFAULT_NSEC3PARAM_ITER;
	uint32_t saltlen = DEFAULT_NSEC3PARAM_SALTLEN;
	uint32_t badalg = 0;
	bool optout = false;

	/* How many iterations. */
	obj = cfg_tuple_get(config, "iterations");
	if (cfg_obj_isuint32(obj)) {
		iter = cfg_obj_asuint32(obj);
	}
	dns_kasp_freeze(kasp);
	ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
		unsigned int keysize = dns_kasp_key_size(kkey);
		uint32_t keyalg = dns_kasp_key_algorithm(kkey);

		if (keysize < min_keysize) {
			min_keysize = keysize;
		}

		/* NSEC3 cannot be used with certain key algorithms. */
		if (keyalg == DST_ALG_RSAMD5 || keyalg == DST_ALG_DSA ||
		    keyalg == DST_ALG_RSASHA1)
		{
			badalg = keyalg;
		}
	}
	dns_kasp_thaw(kasp);

	if (badalg > 0) {
		char algstr[DNS_SECALG_FORMATSIZE];
		dns_secalg_format((dns_secalg_t)badalg, algstr, sizeof(algstr));
		cfg_obj_log(
			obj, ISC_LOG_ERROR,
			"dnssec-policy: cannot use nsec3 with algorithm '%s'",
			algstr);
		return DNS_R_NSEC3BADALG;
	}

	if (iter != DEFAULT_NSEC3PARAM_ITER) {
		cfg_obj_log(obj, ISC_LOG_ERROR,
			    "dnssec-policy: nsec3 iterations value %u "
			    "not allowed, must be zero",
			    iter);
		return DNS_R_NSEC3ITERRANGE;
	}

	/* Opt-out? */
	obj = cfg_tuple_get(config, "optout");
	if (cfg_obj_isboolean(obj)) {
		optout = cfg_obj_asboolean(obj);
	}

	/* Salt */
	obj = cfg_tuple_get(config, "salt-length");
	if (cfg_obj_isuint32(obj)) {
		saltlen = cfg_obj_asuint32(obj);
	}
	if (saltlen > 0xff) {
		cfg_obj_log(obj, ISC_LOG_ERROR,
			    "dnssec-policy: nsec3 salt length %u too high",
			    saltlen);
		return DNS_R_NSEC3SALTRANGE;
	}

	dns_kasp_setnsec3param(kasp, iter, optout, saltlen);
	return ISC_R_SUCCESS;
}

static isc_result_t
add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest) {
	isc_result_t result = ISC_R_SUCCESS;
	isc_textregion_t r;
	dns_dsdigest_t alg;
	const char *str = cfg_obj_asstring(digest);

	r.base = UNCONST(str);
	r.length = strlen(str);
	result = dns_dsdigest_fromtext(&alg, &r);
	if (result != ISC_R_SUCCESS) {
		cfg_obj_log(digest, ISC_LOG_ERROR,
			    "dnssec-policy: bad cds digest-type %s", str);
		result = DNS_R_BADALG;
	} else if (!dst_ds_digest_supported(alg)) {
		cfg_obj_log(digest, ISC_LOG_ERROR,
			    "dnssec-policy: unsupported cds "
			    "digest-type %s",
			    str);
		result = DST_R_UNSUPPORTEDALG;
	} else {
		if (alg == DNS_DSDIGEST_SHA1) {
			cfg_obj_log(
				digest, ISC_LOG_WARNING,
				"dnssec-policy: deprecated CDS digest-type %s",
				str);
		}
		dns_kasp_adddigest(kasp, alg);
	}
	return result;
}

isc_result_t
cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
		    bool check_algorithms, isc_mem_t *mctx,
		    dns_keystorelist_t *keystorelist, dns_kasplist_t *kasplist,
		    dns_kasp_t **kaspp) {
	isc_result_t result;
	const cfg_obj_t *maps[2];
	const cfg_obj_t *koptions = NULL;
	const cfg_obj_t *keys = NULL;
	const cfg_obj_t *nsec3 = NULL;
	const cfg_obj_t *inlinesigning = NULL;
	const cfg_obj_t *cds = NULL;
	const cfg_obj_t *obj = NULL;
	const char *kaspname = NULL;
	dns_kasp_t *kasp = NULL;
	size_t i = 0;
	uint32_t sigjitter = 0, sigrefresh = 0, sigvalidity = 0;
	uint32_t dnskeyttl = 0, dsttl = 0, maxttl = 0;
	uint32_t publishsafety = 0, retiresafety = 0;
	uint32_t zonepropdelay = 0, parentpropdelay = 0;
	uint32_t ipub = 0, iret = 0;
	uint32_t ksk_min_lifetime = 0, zsk_min_lifetime = 0;
	bool offline_ksk = false;

	REQUIRE(config != NULL);
	REQUIRE(kaspp != NULL && *kaspp == NULL);

	kaspname = cfg_obj_asstring(cfg_tuple_get(config, "name"));
	INSIST(kaspname != NULL);

	cfg_obj_log(config, ISC_LOG_DEBUG(1), "dnssec-policy: load policy '%s'",
		    kaspname);

	result = dns_kasplist_find(kasplist, kaspname, &kasp);

	if (result == ISC_R_SUCCESS) {
		cfg_obj_log(
			config, ISC_LOG_ERROR,
			"dnssec-policy: duplicately named policy found '%s'",
			kaspname);
		dns_kasp_detach(&kasp);
		return ISC_R_EXISTS;
	}
	if (result != ISC_R_NOTFOUND) {
		return result;
	}

	result = ISC_R_SUCCESS;

	/* No kasp with configured name was found in list, create new one. */
	INSIST(kasp == NULL);
	dns_kasp_create(mctx, kaspname, &kasp);
	INSIST(kasp != NULL);

	/* Now configure. */
	INSIST(DNS_KASP_VALID(kasp));

	if (config != NULL) {
		koptions = cfg_tuple_get(config, "options");
		maps[i++] = koptions;
	}
	maps[i] = NULL;

	/* Configuration: Signatures */
	sigjitter = get_duration(maps, "signatures-jitter",
				 DNS_KASP_SIG_JITTER);
	dns_kasp_setsigjitter(kasp, sigjitter);

	sigrefresh = get_duration(maps, "signatures-refresh",
				  DNS_KASP_SIG_REFRESH);
	dns_kasp_setsigrefresh(kasp, sigrefresh);

	sigvalidity = get_duration(maps, "signatures-validity-dnskey",
				   DNS_KASP_SIG_VALIDITY_DNSKEY);
	if (sigrefresh >= (sigvalidity * 0.9)) {
		cfg_obj_log(
			config, ISC_LOG_ERROR,
			"dnssec-policy: policy '%s' signatures-refresh must be "
			"at most 90%% of the signatures-validity-dnskey",
			kaspname);
		result = ISC_R_FAILURE;
	}
	dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity);

	if (sigjitter > sigvalidity) {
		cfg_obj_log(
			config, ISC_LOG_ERROR,
			"dnssec-policy: policy '%s' signatures-jitter cannot "
			"be larger than signatures-validity-dnskey",
			kaspname);
		result = ISC_R_FAILURE;
	}

	sigvalidity = get_duration(maps, "signatures-validity",
				   DNS_KASP_SIG_VALIDITY);
	if (sigrefresh >= (sigvalidity * 0.9)) {
		cfg_obj_log(
			config, ISC_LOG_ERROR,
			"dnssec-policy: policy '%s' signatures-refresh must be "
			"at most 90%% of the signatures-validity",
			kaspname);
		result = ISC_R_FAILURE;
	}
	dns_kasp_setsigvalidity(kasp, sigvalidity);

	if (sigjitter > sigvalidity) {
		cfg_obj_log(
			config, ISC_LOG_ERROR,
			"dnssec-policy: policy '%s' signatures-jitter cannot "
			"be larger than signatures-validity",
			kaspname);
		result = ISC_R_FAILURE;
	}

	if (result != ISC_R_SUCCESS) {
		goto cleanup;
	}

	/* Configuration: Zone settings */
	(void)confget(maps, "inline-signing", &inlinesigning);
	if (inlinesigning != NULL && cfg_obj_isboolean(inlinesigning)) {
		dns_kasp_setinlinesigning(kasp,
					  cfg_obj_asboolean(inlinesigning));
	} else {
		dns_kasp_setinlinesigning(kasp, true);
	}

	maxttl = get_duration(maps, "max-zone-ttl", DNS_KASP_ZONE_MAXTTL);
	dns_kasp_setzonemaxttl(kasp, maxttl);

	zonepropdelay = get_duration(maps, "zone-propagation-delay",
				     DNS_KASP_ZONE_PROPDELAY);
	dns_kasp_setzonepropagationdelay(kasp, zonepropdelay);

	/* Configuration: Parent settings */
	dsttl = get_duration(maps, "parent-ds-ttl", DNS_KASP_DS_TTL);
	dns_kasp_setdsttl(kasp, dsttl);

	parentpropdelay = get_duration(maps, "parent-propagation-delay",
				       DNS_KASP_PARENT_PROPDELAY);
	dns_kasp_setparentpropagationdelay(kasp, parentpropdelay);

	/* Configuration: Keys */
	obj = NULL;
	(void)confget(maps, "offline-ksk", &obj);
	if (obj != NULL) {
		offline_ksk = cfg_obj_asboolean(obj);
	}
	dns_kasp_setofflineksk(kasp, offline_ksk);

	obj = NULL;
	(void)confget(maps, "cdnskey", &obj);
	if (obj != NULL) {
		dns_kasp_setcdnskey(kasp, cfg_obj_asboolean(obj));
	} else {
		dns_kasp_setcdnskey(kasp, true);
	}

	(void)confget(maps, "cds-digest-types", &cds);
	if (cds != NULL) {
		CFG_LIST_FOREACH(cds, element) {
			result = add_digest(kasp, cfg_listelt_value(element));
			if (result != ISC_R_SUCCESS) {
				goto cleanup;
			}
		}
	} else {
		dns_kasp_adddigest(kasp, DNS_DSDIGEST_SHA256);
	}

	dnskeyttl = get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL);
	dns_kasp_setdnskeyttl(kasp, dnskeyttl);

	publishsafety = get_duration(maps, "publish-safety",
				     DNS_KASP_PUBLISH_SAFETY);
	dns_kasp_setpublishsafety(kasp, publishsafety);

	retiresafety = get_duration(maps, "retire-safety",
				    DNS_KASP_RETIRE_SAFETY);
	dns_kasp_setretiresafety(kasp, retiresafety);

	dns_kasp_setpurgekeys(
		kasp, get_duration(maps, "purge-keys", DNS_KASP_PURGE_KEYS));

	ipub = dnskeyttl + publishsafety + zonepropdelay;
	iret = dsttl + retiresafety + parentpropdelay;
	ksk_min_lifetime = ISC_MAX(ipub, iret);

	iret = (sigvalidity - sigrefresh) + maxttl + retiresafety +
	       zonepropdelay;
	zsk_min_lifetime = ISC_MAX(ipub, iret);

	(void)confget(maps, "keys", &keys);
	if (keys != NULL) {
		char role[DST_MAX_ALGS] = { 0 };
		bool warn[DST_MAX_ALGS][2] = { { false } };

		CFG_LIST_FOREACH(keys, element) {
			cfg_obj_t *kobj = cfg_listelt_value(element);
			result = cfg_kaspkey_fromconfig(
				kobj, kasp, check_algorithms, offline_ksk,
				keystorelist, ksk_min_lifetime,
				zsk_min_lifetime);
			if (result != ISC_R_SUCCESS) {
				cfg_obj_log(kobj, ISC_LOG_ERROR,
					    "dnssec-policy: failed to "
					    "configure keys (%s)",
					    isc_result_totext(result));
				goto cleanup;
			}
		}
		dns_kasp_freeze(kasp);
		ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
			uint32_t keyalg = dns_kasp_key_algorithm(kkey);
			INSIST(keyalg < ARRAY_SIZE(role));

			if (dns_kasp_key_zsk(kkey)) {
				if ((role[keyalg] & DNS_KASP_KEY_ROLE_ZSK) != 0)
				{
					warn[keyalg][0] = true;
				}
				role[keyalg] |= DNS_KASP_KEY_ROLE_ZSK;
			}

			if (dns_kasp_key_ksk(kkey)) {
				if ((role[keyalg] & DNS_KASP_KEY_ROLE_KSK) != 0)
				{
					warn[keyalg][1] = true;
				}
				role[keyalg] |= DNS_KASP_KEY_ROLE_KSK;
			}
		}
		dns_kasp_thaw(kasp);
		for (i = 0; i < ARRAY_SIZE(role); i++) {
			if (role[i] == 0) {
				continue;
			}
			if (role[i] !=
			    (DNS_KASP_KEY_ROLE_ZSK | DNS_KASP_KEY_ROLE_KSK))
			{
				cfg_obj_log(keys, ISC_LOG_ERROR,
					    "dnssec-policy: algorithm %zu "
					    "requires both KSK and ZSK roles",
					    i);
				result = ISC_R_FAILURE;
			}
			if (warn[i][0]) {
				cfg_obj_log(keys, ISC_LOG_WARNING,
					    "dnssec-policy: algorithm %zu has "
					    "multiple keys with ZSK role",
					    i);
			}
			if (warn[i][1]) {
				cfg_obj_log(keys, ISC_LOG_WARNING,
					    "dnssec-policy: algorithm %zu has "
					    "multiple keys with KSK role",
					    i);
			}
		}
		if (result != ISC_R_SUCCESS) {
			goto cleanup;
		}
	} else if (default_kasp) {
		/*
		 * If there are no specific keys configured in the policy,
		 * inherit from the default policy (except for the built-in
		 * "insecure" policy).
		 */
		ISC_LIST_FOREACH(dns_kasp_keys(default_kasp), key, link) {
			dns_kasp_key_t *new_key = NULL;

			/* Create a new key reference. */
			dns_kasp_key_create(kasp, &new_key);
			if (dns_kasp_key_ksk(key)) {
				new_key->role |= DNS_KASP_KEY_ROLE_KSK;
			}
			if (dns_kasp_key_zsk(key)) {
				new_key->role |= DNS_KASP_KEY_ROLE_ZSK;
			}
			new_key->lifetime = dns_kasp_key_lifetime(key);
			new_key->algorithm = dns_kasp_key_algorithm(key);
			new_key->length = dns_kasp_key_size(key);
			result = dns_keystorelist_find(
				keystorelist, DNS_KEYSTORE_KEYDIRECTORY,
				&new_key->keystore);
			if (result != ISC_R_SUCCESS) {
				cfg_obj_log(config, ISC_LOG_ERROR,
					    "dnssec-policy: failed to "
					    "find keystore (%s)",
					    isc_result_totext(result));
				goto cleanup;
			}
			dns_kasp_addkey(kasp, new_key);
		}
	}

	if (strcmp(kaspname, "insecure") == 0) {
		/* "dnssec-policy insecure": key list must be empty */
		INSIST(dns_kasp_keylist_empty(kasp));
	} else if (default_kasp != NULL) {
		/* There must be keys configured. */
		INSIST(!(dns_kasp_keylist_empty(kasp)));
	}

	/* Configuration: NSEC3 */
	(void)confget(maps, "nsec3param", &nsec3);
	if (nsec3 == NULL) {
		if (default_kasp != NULL && dns_kasp_nsec3(default_kasp)) {
			dns_kasp_setnsec3param(
				kasp, dns_kasp_nsec3iter(default_kasp),
				dns_kasp_nsec3flags(default_kasp) == 0x01,
				dns_kasp_nsec3saltlen(default_kasp));
		} else {
			dns_kasp_setnsec3(kasp, false);
		}
	} else {
		dns_kasp_setnsec3(kasp, true);
		result = cfg_nsec3param_fromconfig(nsec3, kasp);
		if (result != ISC_R_SUCCESS) {
			goto cleanup;
		}
	}

	/* Append it to the list for future lookups. */
	ISC_LIST_APPEND(*kasplist, kasp, link);
	INSIST(!(ISC_LIST_EMPTY(*kasplist)));

	/* Success: Attach the kasp to the pointer and return. */
	dns_kasp_attach(kasp, kaspp);

	/* Don't detach as kasp is on '*kasplist' */
	return ISC_R_SUCCESS;

cleanup:

	/* Something bad happened, detach (destroys kasp) and return error. */
	dns_kasp_detach(&kasp);
	return result;
}

isc_result_t
cfg_keystore_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx,
			dns_keystorelist_t *keystorelist,
			dns_keystore_t **kspp) {
	isc_result_t result;
	const cfg_obj_t *maps[2];
	const cfg_obj_t *koptions = NULL;
	const char *name = NULL;
	const char *keydirectory = DNS_KEYSTORE_KEYDIRECTORY;
	dns_keystore_t *keystore = NULL;
	int i = 0;

	if (config != NULL) {
		name = cfg_obj_asstring(cfg_tuple_get(config, "name"));
	} else {
		name = keydirectory;
	}
	INSIST(name != NULL);

	result = dns_keystorelist_find(keystorelist, name, &keystore);

	if (result == ISC_R_SUCCESS) {
		cfg_obj_log(config, ISC_LOG_ERROR,
			    "key-store: duplicate key-store found '%s'", name);
		dns_keystore_detach(&keystore);
		return ISC_R_EXISTS;
	}
	if (result != ISC_R_NOTFOUND) {
		cfg_obj_log(config, ISC_LOG_ERROR,
			    "key-store: lookup '%s' failed: %s", name,
			    isc_result_totext(result));
		return result;
	}

	/*
	 * No key-store with configured name was found in list, create new one.
	 */
	INSIST(keystore == NULL);
	dns_keystore_create(mctx, name, &keystore);
	INSIST(keystore != NULL);

	/* Now configure. */
	INSIST(DNS_KEYSTORE_VALID(keystore));

	if (config != NULL) {
		koptions = cfg_tuple_get(config, "options");
		maps[i++] = koptions;
		maps[i] = NULL;
		dns_keystore_setdirectory(keystore,
					  get_string(maps, "directory"));
		dns_keystore_setpkcs11uri(keystore,
					  get_string(maps, "pkcs11-uri"));
	}

	/* Append it to the list for future lookups. */
	ISC_LIST_APPEND(*keystorelist, keystore, link);
	INSIST(!(ISC_LIST_EMPTY(*keystorelist)));

	/* Success: Attach the keystore to the pointer and return. */
	if (kspp != NULL) {
		INSIST(*kspp == NULL);
		dns_keystore_attach(keystore, kspp);
	}

	/* Don't detach as keystore is on '*keystorelist' */
	return ISC_R_SUCCESS;
}
