/*
 * 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.
 */

/*! \file */

#include <string.h>

#include <isc/assertions.h>
#include <isc/buffer.h>
#include <isc/file.h>
#include <isc/hex.h>
#include <isc/log.h>
#include <isc/mem.h>
#include <isc/util.h>

#include <dns/kasp.h>
#include <dns/keyvalues.h>

#include <dst/dst.h>

/* Default TTLsig (maximum zone ttl) */
#define DEFAULT_TTLSIG 604800 /* one week */

void
dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) {
	dns_kasp_t *kasp;
	dns_kasp_t k = {
		.magic = DNS_KASP_MAGIC,
		.digests = ISC_LIST_INITIALIZER,
		.keys = ISC_LIST_INITIALIZER,
		.link = ISC_LINK_INITIALIZER,
	};

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

	kasp = isc_mem_get(mctx, sizeof(*kasp));
	*kasp = k;

	kasp->mctx = NULL;
	isc_mem_attach(mctx, &kasp->mctx);
	kasp->name = isc_mem_strdup(mctx, name);
	isc_mutex_init(&kasp->lock);
	isc_refcount_init(&kasp->references, 1);

	*kaspp = kasp;
}

static void
destroy(dns_kasp_t *kasp) {
	REQUIRE(!ISC_LINK_LINKED(kasp, link));

	ISC_LIST_FOREACH(kasp->keys, key, link) {
		ISC_LIST_UNLINK(kasp->keys, key, link);
		dns_kasp_key_destroy(key);
	}
	INSIST(ISC_LIST_EMPTY(kasp->keys));

	ISC_LIST_FOREACH(kasp->digests, digest, link) {
		ISC_LIST_UNLINK(kasp->digests, digest, link);
		isc_mem_put(kasp->mctx, digest, sizeof(*digest));
	}
	INSIST(ISC_LIST_EMPTY(kasp->digests));

	isc_mutex_destroy(&kasp->lock);
	isc_mem_free(kasp->mctx, kasp->name);
	isc_mem_putanddetach(&kasp->mctx, kasp, sizeof(*kasp));
}

#if DNS_KASP_TRACE
ISC_REFCOUNT_TRACE_IMPL(dns_kasp, destroy);
#else
ISC_REFCOUNT_IMPL(dns_kasp, destroy);
#endif

const char *
dns_kasp_getname(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));

	return kasp->name;
}

void
dns_kasp_freeze(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->frozen = true;
}

void
dns_kasp_thaw(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	kasp->frozen = false;
}

uint32_t
dns_kasp_signdelay(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->signatures_validity - kasp->signatures_refresh;
}

uint32_t
dns_kasp_sigjitter(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->signatures_jitter;
}

void
dns_kasp_setsigjitter(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->signatures_jitter = value;
}

uint32_t
dns_kasp_sigrefresh(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->signatures_refresh;
}

void
dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->signatures_refresh = value;
}

uint32_t
dns_kasp_sigvalidity(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->signatures_validity;
}

void
dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->signatures_validity = value;
}

uint32_t
dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->signatures_validity_dnskey;
}

void
dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->signatures_validity_dnskey = value;
}

dns_ttl_t
dns_kasp_dnskeyttl(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->dnskey_ttl;
}

void
dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->dnskey_ttl = ttl;
}

uint32_t
dns_kasp_purgekeys(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->purge_keys;
}

void
dns_kasp_setpurgekeys(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->purge_keys = value;
}

uint32_t
dns_kasp_publishsafety(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->publish_safety;
}

void
dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->publish_safety = value;
}

uint32_t
dns_kasp_retiresafety(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->retire_safety;
}

void
dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->retire_safety = value;
}

bool
dns_kasp_inlinesigning(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->inline_signing;
}

void
dns_kasp_setinlinesigning(dns_kasp_t *kasp, bool value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->inline_signing = value;
}

bool
dns_kasp_manualmode(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->manual_mode;
}

void
dns_kasp_setmanualmode(dns_kasp_t *kasp, bool value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->manual_mode = value;
}

dns_ttl_t
dns_kasp_zonemaxttl(dns_kasp_t *kasp, bool fallback) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	if (kasp->zone_max_ttl == 0 && fallback) {
		return DEFAULT_TTLSIG;
	}
	return kasp->zone_max_ttl;
}

void
dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->zone_max_ttl = ttl;
}

uint32_t
dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->zone_propagation_delay;
}

void
dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->zone_propagation_delay = value;
}

dns_ttl_t
dns_kasp_dsttl(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->parent_ds_ttl;
}

void
dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->parent_ds_ttl = ttl;
}

uint32_t
dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->parent_propagation_delay;
}

void
dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	kasp->parent_propagation_delay = value;
}

isc_result_t
dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) {
	REQUIRE(kaspp != NULL && *kaspp == NULL);

	if (list == NULL) {
		return ISC_R_NOTFOUND;
	}

	ISC_LIST_FOREACH(*list, kasp, link) {
		if (strcmp(kasp->name, name) == 0) {
			dns_kasp_attach(kasp, kaspp);
			return ISC_R_SUCCESS;
		}
	}

	return ISC_R_NOTFOUND;
}

dns_kasp_keylist_t
dns_kasp_keys(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->keys;
}

bool
dns_kasp_keylist_empty(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));

	return ISC_LIST_EMPTY(kasp->keys);
}

void
dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);
	REQUIRE(key != NULL);

	ISC_LIST_APPEND(kasp->keys, key, link);
}

void
dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) {
	dns_kasp_key_t *key = NULL;
	dns_kasp_key_t k = { .tag_max = 0xffff, .length = -1 };

	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(keyp != NULL && *keyp == NULL);

	key = isc_mem_get(kasp->mctx, sizeof(*key));
	*key = k;

	key->mctx = NULL;
	isc_mem_attach(kasp->mctx, &key->mctx);

	ISC_LINK_INIT(key, link);

	*keyp = key;
}

void
dns_kasp_key_destroy(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);

	if (key->keystore != NULL) {
		dns_keystore_detach(&key->keystore);
	}
	isc_mem_putanddetach(&key->mctx, key, sizeof(*key));
}

uint32_t
dns_kasp_key_algorithm(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);

	return key->algorithm;
}

unsigned int
dns_kasp_key_size(dns_kasp_key_t *key) {
	unsigned int size = 0;
	unsigned int min = 0;

	REQUIRE(key != NULL);

	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:
		min = (key->algorithm == DST_ALG_RSASHA512) ? 1024 : 512;
		if (key->length > -1) {
			size = (unsigned int)key->length;
			if (size < min) {
				size = min;
			}
			if (size > 4096) {
				size = 4096;
			}
		} else {
			size = 2048;
		}
		break;
	case DST_ALG_ECDSA256:
		size = 256;
		break;
	case DST_ALG_ECDSA384:
		size = 384;
		break;
	case DST_ALG_ED25519:
		size = 256;
		break;
	case DST_ALG_ED448:
		size = 456;
		break;
	default:
		/* unsupported */
		break;
	}
	return size;
}

uint32_t
dns_kasp_key_lifetime(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);

	return key->lifetime;
}

dns_keystore_t *
dns_kasp_key_keystore(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);

	return key->keystore;
}

bool
dns_kasp_key_ksk(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);

	return key->role & DNS_KASP_KEY_ROLE_KSK;
}

bool
dns_kasp_key_zsk(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);

	return key->role & DNS_KASP_KEY_ROLE_ZSK;
}

uint16_t
dns_kasp_key_tagmin(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);
	return key->tag_min;
}

uint16_t
dns_kasp_key_tagmax(dns_kasp_key_t *key) {
	REQUIRE(key != NULL);
	return key->tag_min;
}

bool
dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey) {
	isc_result_t result;
	bool role = false;

	REQUIRE(key != NULL);
	REQUIRE(dkey != NULL);

	/* Matching algorithms? */
	if (dst_key_alg(dkey->key) != dns_kasp_key_algorithm(key)) {
		return false;
	}
	/* Matching length? */
	if (dst_key_size(dkey->key) != dns_kasp_key_size(key)) {
		return false;
	}
	/* Matching role? */
	result = dst_key_getbool(dkey->key, DST_BOOL_KSK, &role);
	if (result != ISC_R_SUCCESS || role != dns_kasp_key_ksk(key)) {
		return false;
	}
	result = dst_key_getbool(dkey->key, DST_BOOL_ZSK, &role);
	if (result != ISC_R_SUCCESS || role != dns_kasp_key_zsk(key)) {
		return false;
	}
	/* Valid key tag range? */
	uint16_t id = dst_key_id(dkey->key);
	uint16_t rid = dst_key_rid(dkey->key);
	if (id < key->tag_min || id > key->tag_max) {
		return false;
	}
	if (rid < key->tag_min || rid > key->tag_max) {
		return false;
	}

	/* Found a match. */
	return true;
}

void
dns_kasp_key_format(dns_kasp_key_t *key, char *cp, unsigned int size) {
	REQUIRE(key != NULL);
	REQUIRE(cp != NULL);

	char algstr[DNS_NAME_FORMATSIZE];
	bool csk = dns_kasp_key_ksk(key) && dns_kasp_key_zsk(key);
	const char *rolestr = (csk ? "csk"
				   : (dns_kasp_key_ksk(key) ? "ksk" : "zsk"));

	dst_algorithm_format(key->algorithm, algstr, sizeof(algstr));
	snprintf(cp, size, "%s algorithm:%s length:%u tag-range:%u-%u", rolestr,
		 algstr, dns_kasp_key_size(key), key->tag_min, key->tag_max);
}

uint8_t
dns_kasp_nsec3iter(dns_kasp_t *kasp) {
	REQUIRE(kasp != NULL);
	REQUIRE(kasp->frozen);
	REQUIRE(kasp->nsec3);

	return kasp->nsec3param.iterations;
}

uint8_t
dns_kasp_nsec3flags(dns_kasp_t *kasp) {
	REQUIRE(kasp != NULL);
	REQUIRE(kasp->frozen);
	REQUIRE(kasp->nsec3);

	if (kasp->nsec3param.optout) {
		return 0x01;
	}
	return 0x00;
}

uint8_t
dns_kasp_nsec3saltlen(dns_kasp_t *kasp) {
	REQUIRE(kasp != NULL);
	REQUIRE(kasp->frozen);
	REQUIRE(kasp->nsec3);

	return kasp->nsec3param.saltlen;
}

bool
dns_kasp_nsec3(dns_kasp_t *kasp) {
	REQUIRE(kasp != NULL);
	REQUIRE(kasp->frozen);

	return kasp->nsec3;
}

void
dns_kasp_setnsec3(dns_kasp_t *kasp, bool nsec3) {
	REQUIRE(kasp != NULL);
	REQUIRE(!kasp->frozen);

	kasp->nsec3 = nsec3;
}

void
dns_kasp_setnsec3param(dns_kasp_t *kasp, uint8_t iter, bool optout,
		       uint8_t saltlen) {
	REQUIRE(kasp != NULL);
	REQUIRE(!kasp->frozen);
	REQUIRE(kasp->nsec3);

	kasp->nsec3param.iterations = iter;
	kasp->nsec3param.optout = optout;
	kasp->nsec3param.saltlen = saltlen;
}

bool
dns_kasp_offlineksk(dns_kasp_t *kasp) {
	REQUIRE(kasp != NULL);
	REQUIRE(kasp->frozen);

	return kasp->offlineksk;
}

void
dns_kasp_setofflineksk(dns_kasp_t *kasp, bool offlineksk) {
	REQUIRE(kasp != NULL);
	REQUIRE(!kasp->frozen);

	kasp->offlineksk = offlineksk;
}

bool
dns_kasp_cdnskey(dns_kasp_t *kasp) {
	REQUIRE(kasp != NULL);
	REQUIRE(kasp->frozen);

	return kasp->cdnskey;
}

void
dns_kasp_setcdnskey(dns_kasp_t *kasp, bool cdnskey) {
	REQUIRE(kasp != NULL);
	REQUIRE(!kasp->frozen);

	kasp->cdnskey = cdnskey;
}

dns_kasp_digestlist_t
dns_kasp_digests(dns_kasp_t *kasp) {
	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(kasp->frozen);

	return kasp->digests;
}

void
dns_kasp_adddigest(dns_kasp_t *kasp, dns_dsdigest_t alg) {
	dns_kasp_digest_t *digest;

	REQUIRE(DNS_KASP_VALID(kasp));
	REQUIRE(!kasp->frozen);

	/* Suppress unsupported algorithms */
	if (!dst_ds_digest_supported(alg)) {
		return;
	}

	/* Suppress duplicates */
	ISC_LIST_FOREACH(kasp->digests, d, link) {
		if (d->digest == alg) {
			return;
		}
	}

	digest = isc_mem_get(kasp->mctx, sizeof(*digest));
	digest->digest = alg;
	ISC_LINK_INIT(digest, link);
	ISC_LIST_APPEND(kasp->digests, digest, link);
}
