/*  Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
 *  SPDX-License-Identifier: GPL-3.0-or-later
 */

#pragma once

#include "lib/defines.h"
#include "lib/utils.h"
#include <libknot/packet/pkt.h>

// This is the central place in Knot Resolver which includes the (former) libdnssec.
#if KNOT_VERSION_HEX < 0x030600
#include <libdnssec/dnssec.h>
#include <libdnssec/digest.h>
#else
#include <libknot/libknot.h>
// Now some compatibility
enum {
	// These might not be OK to simply replace everywhere.
	DNSSEC_INVALID_KEY_ALGORITHM = KNOT_INVALID_KEY_ALGORITHM,
	DNSSEC_INVALID_DIGEST_ALGORITHM = KNOT_EALGORITHM,
	DNSSEC_NOT_FOUND = KNOT_ENOENT, // or -ENOENT now
};
#endif

/**
 * Initialise cryptographic back-end.
 */
KR_EXPORT
void kr_crypto_init(void);

/**
 * De-initialise cryptographic back-end.
 */
KR_EXPORT
void kr_crypto_cleanup(void);

/**
 * Re-initialise cryptographic back-end.
 * @note Must be called after fork() in the child.
 */
KR_EXPORT
void kr_crypto_reinit(void);

#define KR_DNSSEC_VFLG_WEXPAND 0x01
#define KR_DNSSEC_VFLG_OPTOUT  0x02

/** DNSSEC validation context. */
struct kr_rrset_validation_ctx {
	const knot_pkt_t *pkt;		/*!< Packet to be validated. */
	ranked_rr_array_t *rrs;		/*!< List of preselected RRs to be validated. */
	knot_section_t section_id;	/*!< Section to work with. */
	knot_rrset_t *keys;		/*!< DNSKEY RRSet; TTLs may get lowered when validating this set. */
        const knot_dname_t *zone_name;	/*!< Name of the zone containing the RRSIG RRSet. */
	uint32_t timestamp;		/*!< Validation time. */
	uint32_t ttl_min;		/*!< See trim_ttl() for details. */
        bool has_nsec3;			/*!< Whether to use NSEC3 validation. */
	uint32_t qry_uid;		/*!< Current query uid. */
	uint32_t flags;			/*!< Output - Flags. */
	uint32_t err_cnt;		/*!< Output - Number of validation failures. */
	uint32_t cname_norrsig_cnt;	/*!< Output - Number of CNAMEs missing RRSIGs. */
	int32_t *limit_crypto_remains;  /*!< Optional pointer to struct kr_query::vld_limit_crypto_remains */

	/** Validation result: kr_error() code.
	 *
	 * ENOENT: the usual, no suitable signature found
	 * EAGAIN: encountered a different signer name
	 * +others
	 */
	int result;
	const struct kr_query *log_qry; /*!< The query; just for logging purposes. */
	struct {
		unsigned int matching_name_type;	/*!< Name + type matches */
		unsigned int expired;			/*!< Number of expired signatures */
		unsigned int notyet;			/*!< Number of signatures not yet valid (inception > now) */
		unsigned int expired_before_inception;	/*!< Number of signatures already expired before inception time */
		unsigned int signer_invalid;		/*!< Signer is not zone apex */
		unsigned int labels_invalid;		/*!< Number of labels in RRSIG */
		unsigned int key_invalid;		/*!< Algorithm/keytag/key owner */
		unsigned int crypto_invalid;
		unsigned int nsec_invalid;
	} rrs_counters;	/*!< Error counters for single RRset validation. */
};

typedef struct kr_rrset_validation_ctx kr_rrset_validation_ctx_t;

/**
 * Validate RRSet.
 * @param vctx    Pointer to validation context.
 * @param covered RRSet covered by a signature. It must be in canonical format.
 * 		  Its TTL may get lowered.
 * @return        0 or kr_error() code, same as vctx->result (see its docs).
 */
int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, knot_rrset_t *covered);

/**
 * Check whether the RRset contains at least one usable DS.
 *
 * See RFC6840 5.2.
 * @param ta    Pointer to TA RRSet.
 * @return      kr_ok() if at least one DS is supported
 *              DNSSEC_INVALID_KEY_ALGORITHM if all DSes are not supported, because of their key algorithm
 *              DNSSEC_INVALID_DIGEST_ALGORITHM if all DSes are not supported, because of their digest algorithm
 * @note        Given that entries are iterated until a supported DS is found, the error refers to the last one.
 */
KR_EXPORT KR_PURE
int kr_ds_algo_support(const knot_rrset_t *ta);

/**
 * Check whether the DNSKEY rrset matches the supplied trust anchor RRSet.
 *
 * @param vctx  Pointer to validation context.  Note that TTL of vctx->keys may get lowered.
 * @param sigs  RRSIGs for this DNSKEY set
 * @param ta    Trusted DS RRSet against which to validate the DNSKEY RRSet.
 * @return      0 or error code, same as vctx->result.
 */
int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rdataset_t *sigs,
			const knot_rrset_t *ta);

// flags: https://www.iana.org/assignments/dnskey-flags/dnskey-flags.xhtml
//        https://datatracker.ietf.org/doc/html/rfc4034#section-2.1

/** Return true if the DNSKEY has the SEP flag/bit set (normally ignored). */
KR_EXPORT inline KR_PURE
bool kr_dnssec_key_sep_flag(const uint8_t *dnskey_rdata)
{
	return dnskey_rdata[1] & 0x01;
}

/** Return true if the DNSKEY has the Zone Key flag/bit set. */
KR_EXPORT inline KR_PURE
bool kr_dnssec_key_zonekey_flag(const uint8_t *dnskey_rdata)
{
	return dnskey_rdata[0] & 0x01;
}

/** Return true if the DNSKEY is revoked. */
KR_EXPORT inline KR_PURE
bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata)
{
	return dnskey_rdata[1] & 0x80;
}

/**
 * Return true if the DNSKEY could be used to validate zone records, meaning
 * it correctly has the Zone Key flag/bit set to 1 and it is not revoked.
 */
static inline KR_PURE
bool kr_dnssec_key_usable(const uint8_t *dnskey_rdata)
{
	return kr_dnssec_key_zonekey_flag(dnskey_rdata) && !kr_dnssec_key_revoked(dnskey_rdata);
}

/** Return DNSKEY tag.
  * @param rrtype RR type (either DS or DNSKEY are supported)
  * @param rdata  Key/digest RDATA.
  * @param rdlen  RDATA length.
  * @return Key tag (positive number), or an error code
  */
KR_EXPORT KR_PURE
int kr_dnssec_key_tag(uint16_t rrtype, const uint8_t *rdata, size_t rdlen);

/** Return 0 if the two keys are identical.
  * @note This compares RDATA only, algorithm and public key must match.
  * @param key_a_rdata First key RDATA
  * @param key_a_rdlen First key RDATA length
  * @param key_b_rdata Second key RDATA
  * @param key_b_rdlen Second key RDATA length
  * @return 0 if they match or an error code
  */
KR_EXPORT KR_PURE
int kr_dnssec_key_match(const uint8_t *key_a_rdata, size_t key_a_rdlen,
                        const uint8_t *key_b_rdata, size_t key_b_rdlen);

/* Opaque DNSSEC key struct; forward declaration from libdnssec. */
struct dnssec_key;

/**
 * Construct a DNSSEC key.
 * @param key   Pointer to be set to newly created DNSSEC key.
 * @param kown  DNSKEY owner name.
 * @param rdata DNSKEY RDATA
 * @param rdlen DNSKEY RDATA length
 * @return 0 or error code; in particular: DNSSEC_INVALID_KEY_ALGORITHM
 */
int kr_dnssec_key_from_rdata(struct dnssec_key **key, const knot_dname_t *kown, const uint8_t *rdata, size_t rdlen);

/**
 * Frees the DNSSEC key.
 * @param key Pointer to freed key.
 */
void kr_dnssec_key_free(struct dnssec_key **key);

/**
 * Checks whether NSEC/NSEC3 RR selected by iterator matches the supplied name and type.
 * @param rrs     Records selected by iterator.
 * @param qry_uid Query unique identifier where NSEC/NSEC3 belongs to.
 * @param name    Name to be checked.
 * @param type    Type to be checked.
 * @return        0 or error code.
 */
int kr_dnssec_matches_name_and_type(const ranked_rr_array_t *rrs, uint32_t qry_uid,
				    const knot_dname_t *name, uint16_t type);


/* Simple validator API.  Main use case: prefill module, i.e. RRs from a zone file. */

/** Opaque context for simple validator. */
struct kr_svldr_ctx;
/**
 * Create new context for validating within a given zone.
 *
 * - `ds` is assumed to be trusted, and it's used to validate `dnskey+dnskey_sigs`.
 * - The TTL of `dnskey` may get trimmed.
 * - The insides are placed on malloc heap (use _free_ctx).
 * - `err_ctx` is optional, for use when error happens (but avoid the inside pointers)
 */
KR_EXPORT
struct kr_svldr_ctx * kr_svldr_new_ctx(const knot_rrset_t *ds, knot_rrset_t *dnskey,
		const knot_rdataset_t *dnskey_sigs, uint32_t timestamp,
		kr_rrset_validation_ctx_t *err_ctx);
/** Free the context.  Passing NULL is OK. */
KR_EXPORT
void kr_svldr_free_ctx(struct kr_svldr_ctx *ctx);
/**
 * Validate an RRset with the associated signatures; assume no wildcard expansions.
 *
 * - It's caller's responsibility that rrsigs have matching owner, class and type.
 * - The TTL of `rrs` may get trimmed.
 * - If it's a wildcard other than in its simple `*.` form, it may fail to validate.
 * - More generally, non-existence proofs are not supported.
 * @return  0 or kr_error() code, same as kr_rrset_validation_ctx::result (see its docs).
 */
KR_EXPORT
int kr_svldr_rrset(knot_rrset_t *rrs, const knot_rdataset_t *rrsigs,
			struct kr_svldr_ctx *ctx);

