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

#pragma once

/*! \file dns/dnssec.h */

#include <stdbool.h>

#include <isc/lang.h>
#include <isc/stats.h>
#include <isc/stdtime.h>

#include <dns/diff.h>
#include <dns/kasp.h>
#include <dns/types.h>

#include <dst/dst.h>

ISC_LANG_BEGINDECLS

extern isc_stats_t *dns_dnssec_stats;

/*%< Maximum number of keys supported in a zone. */
#define DNS_MAXZONEKEYS 32

/*
 * Indicates how the signer found this key: in the key repository, at the
 * zone apex, or specified by the user.
 */
typedef enum {
	dns_keysource_unknown,
	dns_keysource_repository,
	dns_keysource_zoneapex,
	dns_keysource_user
} dns_keysource_t;

/*
 * A DNSSEC key and hints about its intended use gleaned from metadata
 */
struct dns_dnsseckey {
	dst_key_t *key;
	bool	   hint_publish;     /*% metadata says to publish */
	bool	   force_publish;    /*% publish regardless of metadata
				      * */
	bool hint_sign;		     /*% metadata says to sign with this
				      * key */
	bool force_sign;	     /*% sign with key regardless of
				      * metadata */
	bool		hint_revoke; /*% metadata says revoke key */
	bool		hint_remove; /*% metadata says *don't* publish */
	bool		is_active;   /*% key is already active */
	bool		first_sign;  /*% key is newly becoming active */
	bool		purge;	     /*% remove key files */
	unsigned int	prepublish;  /*% how long until active? */
	dns_keysource_t source;	     /*% how the key was found */
	bool		ksk;	     /*% this is a key-signing key */
	bool		zsk;	     /*% this is a zone-signing key */
	bool		legacy;	     /*% this is old-style key with no
				      *  metadata (possibly generated by
				      *  an older version of BIND9) and
				      *  should be ignored when searching
				      *  for keys to import into the zone */
	bool	     pubkey;	     /*% public key only */
	unsigned int index;	     /*% position in list */
	ISC_LINK(dns_dnsseckey_t) link;
};

isc_result_t
dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
			isc_mem_t *mctx, dst_key_t **key);
/*%<
 *	Creates a DST key from a DNS record.  Basically a wrapper around
 *	dst_key_fromdns().
 *
 *	Requires:
 *\li		'name' is not NULL
 *\li		'rdata' is not NULL
 *\li		'mctx' is not NULL
 *\li		'key' is not NULL
 *\li		'*key' is NULL
 *
 *	Returns:
 *\li		#ISC_R_SUCCESS
 *\li		#ISC_R_NOMEMORY
 *\li		DST_R_INVALIDPUBLICKEY
 *\li		various errors from dns_name_totext
 */

isc_result_t
dns_dnssec_make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize,
		       dns_rdata_t *target);
/*%<
 *	Convert a DST key into a DNS record.
 *
 *	Requires:
 *\li		'key' is not NULL
 *\li		'buf' is not NULL
 *\li		'bufsize' equals DST_KEY_MAXSIZE
 *\li		'target' is not NULL
 *
 *	Returns:
 *\li		#ISC_R_SUCCESS
 *\li		various errors from dst_key_todns
 */

isc_result_t
dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
		isc_stdtime_t *inception, isc_stdtime_t *expire,
		isc_mem_t *mctx, isc_buffer_t *buffer, dns_rdata_t *sigrdata);
/*%<
 *	Generates a RRSIG record covering this rdataset.  This has no effect
 *	on existing RRSIG records.
 *
 *	Requires:
 *\li		'name' (the owner name of the record) is a valid name
 *\li		'set' is a valid rdataset
 *\li		'key' is a valid key
 *\li		'inception' is not NULL
 *\li		'expire' is not NULL
 *\li		'mctx' is not NULL
 *\li		'buffer' is not NULL
 *\li		'sigrdata' is not NULL
 *
 *	Returns:
 *\li		#ISC_R_SUCCESS
 *\li		#ISC_R_NOMEMORY
 *\li		#ISC_R_NOSPACE
 *\li		#DNS_R_INVALIDTIME - the expiration is before the inception
 *\li		#DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
 *			it is not a zone key or its flags prevent
 *			authentication)
 *\li		DST_R_*
 */

isc_result_t
dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
		  bool ignoretime, unsigned int maxbits, isc_mem_t *mctx,
		  dns_rdata_t *sigrdata, dns_name_t *wild);
/*%<
 *	Verifies the RRSIG record covering this rdataset signed by a specific
 *	key.  This does not determine if the key's owner is authorized to sign
 *	this record, as this requires a resolver or database.
 *	If 'ignoretime' is true, temporal validity will not be checked.
 *
 *	'maxbits' specifies the maximum number of rsa exponent bits accepted.
 *
 *	Requires:
 *\li		'name' (the owner name of the record) is a valid name
 *\li		'set' is a valid rdataset
 *\li		'key' is a valid key
 *\li		'mctx' is not NULL
 *\li		'sigrdata' is a valid rdata containing a SIG record
 *\li		'wild' if non-NULL then is a valid and has a buffer.
 *
 *	Returns:
 *\li		#ISC_R_SUCCESS
 *\li		#ISC_R_NOMEMORY
 *\li		#DNS_R_FROMWILDCARD - the signature is valid and is from
 *			a wildcard expansion.  dns_dnssec_verify2() only.
 *			'wild' contains the name of the wildcard if non-NULL.
 *\li		#DNS_R_SIGINVALID - the signature fails to verify
 *\li		#DNS_R_SIGEXPIRED - the signature has expired
 *\li		#DNS_R_SIGFUTURE - the signature's validity period has not begun
 *\li		#DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
 *			it is not a zone key or its flags prevent
 *			authentication)
 *\li		DST_R_*
 */

bool
dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now);
/*%<
 *
 * 	Returns true if 'key' is active as of the time specified
 * 	in 'now' (i.e., if the activation date has passed, inactivation or
 * 	deletion date has not yet been reached, and the key is not revoked
 * 	-- or if it is a legacy key without metadata). Otherwise returns
 * 	false.
 *
 *	Requires:
 *\li		'key' is a valid key
 */

isc_result_t
dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key);
/*%<
 *	Signs a message with a SIG(0) record.  This is implicitly called by
 *	dns_message_renderend() if msg->sig0key is not NULL.
 *
 *	Requires:
 *\li		'msg' is a valid message
 *\li		'key' is a valid key that can be used for signing
 *
 *	Returns:
 *\li		#ISC_R_SUCCESS
 *\li		#ISC_R_NOMEMORY
 *\li		DST_R_*
 */

isc_result_t
dns_dnssec_verifymessage(isc_buffer_t *source, dns_message_t *msg,
			 dst_key_t *key);
/*%<
 *	Verifies a message signed by a SIG(0) record.  This is not
 *	called implicitly by dns_message_parse().  If dns_message_signer()
 *	is called before dns_dnssec_verifymessage(), it will return
 *	#DNS_R_NOTVERIFIEDYET.  dns_dnssec_verifymessage() will set
 *	the verified_sig0 flag in msg if the verify succeeds, and
 *	the sig0status field otherwise.
 *
 *	Requires:
 *\li		'source' is a valid buffer containing the unparsed message
 *\li		'msg' is a valid message
 *\li		'key' is a valid key
 *
 *	Returns:
 *\li		#ISC_R_SUCCESS
 *\li		#ISC_R_NOMEMORY
 *\li		#ISC_R_NOTFOUND - no SIG(0) was found
 *\li		#DNS_R_SIGINVALID - the SIG record is not well-formed or
 *				   was not generated by the key.
 *\li		DST_R_*
 */

bool
dns_dnssec_selfsigns(dns_rdata_t *rdata, const dns_name_t *name,
		     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
		     bool ignoretime, isc_mem_t *mctx);

bool
dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
		 dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
		 bool ignoretime, isc_mem_t *mctx);
/*%<
 * Verify that 'rdataset' is validly signed in 'sigrdataset' by
 * the key in 'rdata'.
 *
 * dns_dnssec_selfsigns() requires that rdataset be a DNSKEY or KEY
 * rrset.  dns_dnssec_signs() works on any rrset.
 */

void
dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
		     dns_dnsseckey_t **dkp);
/*%<
 * Create and initialize a dns_dnsseckey_t structure.
 *
 *	Requires:
 *\li		'dkp' is not NULL and '*dkp' is NULL.
 */

void
dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp);
/*%<
 * Reclaim a dns_dnsseckey_t structure.
 *
 *	Requires:
 *\li		'dkp' is not NULL and '*dkp' is not NULL.
 *
 *	Ensures:
 *\li		'*dkp' is NULL.
 */

void
dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now);
/*%<
 * Get hints on DNSSEC key whether this key can be published
 * and/or is active.  Timing metadata is compared to 'now'.
 *
 *	Requires:
 *\li		'key' is a pointer to a DNSSEC key and is not NULL.
 */

isc_result_t
dns_dnssec_findmatchingkeys(const dns_name_t *origin, dns_kasp_t *kasp,
			    const char *keydir, dns_keystorelist_t *keystores,
			    isc_stdtime_t now, isc_mem_t *mctx,
			    dns_dnsseckeylist_t *keylist);
/*%<
 * Search for K* key files matching the name in 'origin'. If 'kasp' is not
 * NULL, search in the directories used in 'keystores'. Otherwise search in the
 * key-directory 'keydir'.
 *
 * Append all such keys, along with use hints gleaned from their
 * metadata, onto 'keylist'.  Skip any unsupported algorithms.
 *
 *	Requires:
 *\li		'keylist' is not NULL
 *
 *	Returns:
 *\li		#ISC_R_SUCCESS
 *\li		#ISC_R_NOTFOUND
 *\li		#ISC_R_NOMEMORY
 *\li		any error returned by dns_name_totext(), isc_dir_open(), or
 *              dst_key_fromnamedfile()
 *
 *	Ensures:
 *\li		On error, keylist is unchanged
 */

isc_result_t
dns_dnssec_keylistfromrdataset(const dns_name_t *origin, dns_kasp_t *kasp,
			       const char *directory, isc_mem_t *mctx,
			       dns_rdataset_t *keyset, dns_rdataset_t *keysigs,
			       dns_rdataset_t *soasigs, bool savekeys,
			       bool publickey, dns_dnsseckeylist_t *keylist);
/*%<
 * Append the contents of a DNSKEY rdataset 'keyset' to 'keylist'.
 * Omit duplicates.  If 'publickey' is false, search the key stores referenced
 * in 'kasp', or 'directory' if 'kasp' is NULL, for matching key files, and
 * load the private keys that go with the public ones.  If 'savekeys' is true,
 * mark the keys so they will not be deleted or inactivated regardless of
 * metadata.
 *
 * 'keysigs' and 'soasigs', if not NULL and associated, contain the
 * RRSIGS for the DNSKEY and SOA records respectively and are used to mark
 * whether a key is already active in the zone.
 *
 * Private key files for keys with the KSK role are skipped if kasp is in
 * offline-ksk mode.
 */

isc_result_t
dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys,
		      dns_dnsseckeylist_t *removed, const dns_name_t *origin,
		      dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx,
		      void (*report)(const char *, ...)
			      ISC_FORMAT_PRINTF(1, 2));
/*%<
 * Update the list of keys in 'keys' with new key information in 'newkeys'.
 *
 * For each key in 'newkeys', see if it has a match in 'keys'.
 * - If not, and if the metadata says the key should be published:
 *   add it to 'keys', and place a dns_difftuple into 'diff' so
 *   the key can be added to the DNSKEY set.  If the metadata says it
 *   should be active, set the first_sign flag.
 * - If so, and if the metadata says it should be removed:
 *   remove it from 'keys', and place a dns_difftuple into 'diff' so
 *   the key can be removed from the DNSKEY set.  if 'removed' is non-NULL,
 *   copy the key into that list; otherwise destroy it.
 * - Otherwise, make sure keys has current metadata.
 *
 * 'hint_ttl' is the TTL to use for the DNSKEY RRset if there is no
 * existing RRset, and if none of the keys to be added has a default TTL
 * (in which case we would use the shortest one).  If the TTL is longer
 * than the time until a new key will be activated, then we have to delay
 * the key's activation.
 *
 * 'report' points to a function for reporting status.
 *
 * On completion, any remaining keys in 'newkeys' are freed.
 */

isc_result_t
dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys,
		      dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
		      isc_stdtime_t now, dns_kasp_digestlist_t *digests,
		      bool gencdnskey, dns_ttl_t hint_ttl, dns_diff_t *diff,
		      isc_mem_t *mctx);
/*%<
 * Update the CDS and CDNSKEY RRsets, adding and removing keys as needed.
 *
 * For each key in 'keys', check if corresponding CDS and CDNSKEY records
 * need to be published. If needed and 'gencdnskey' is true, there will be one
 * CDNSKEY record added to the 'cdnskey' RRset. Also one CDS record will be
 * added to the 'cds' RRset for each digest type in 'digests'.
 *
 * For each key in 'rmkeys', remove any associated CDS and CDNSKEY records from
 * the RRsets 'cds' and 'cdnskey'.
 *
 * 'hint_ttl' is the TTL to use for the CDS and CDNSKEY RRsets if there is no
 * existing RRset.
 *
 * Any changes made also cause a dns_difftuple to be added to 'diff'.
 *
 * Requires:
 *\li	'keys' is not NULL.
 *\li	'rmkeys' is not NULL.
 *\li	'digests' is not NULL.
 *
 * Returns:
 *\li   ISC_R_SUCCESS
 *\li   Other values indicate error
 */

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 expect_cds_delete, bool expect_cdnskey_delete);
/*%<
 * Add or remove the CDS DELETE record and the CDNSKEY DELETE record.
 * 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
 *\li   Other values indicate error
 */

isc_result_t
dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata,
		      dns_rdataset_t *keyset, dns_rdata_t *keyrdata);
/*%<
 * Given a DS rdata and a DNSKEY RRset, find the DNSKEY rdata that matches
 * the DS, and place it in 'keyrdata'.
 *
 * Returns:
 *\li	ISC_R_SUCCESS
 *\li	ISC_R_NOTFOUND
 *\li	Other values indicate error
 */
ISC_LANG_ENDDECLS
