/*
 * Copyright (C) 2024 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see LICENSE file in root directory
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
/*
 * X.509 certificate validation: Function implementing the verification of
 * various constraints the certificate may or must follow. The enforced
 * constraints are compliant to:
 *
 *  * RFC5280
 *  * BSI TR02103
 */

#include "asn1_debug.h"
#include "asym_key.h"
#include "lc_x509_parser.h"
#include "lc_memcmp_secure.h"
#include "ret_checkers.h"
#include "visibility.h"

static lc_x509_pol_ret_t
lc_509_policy_cert_contains_signature(const struct lc_x509_certificate *cert)
{
	const struct lc_public_key_signature *sig;

	if (!cert)
		return -EINVAL;

	sig = &cert->sig;
	if (sig->s && sig->s_size)
		return LC_X509_POL_TRUE;

	return LC_X509_POL_FALSE;
}

static lc_x509_pol_ret_t
lc_x509_policy_version_ge(const struct lc_x509_certificate *cert,
			  uint8_t requested_version)
{
	if (!cert)
		return -EINVAL;

	if (cert->x509_version >= requested_version)
		return LC_X509_POL_TRUE;

	return LC_X509_POL_FALSE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_is_ca,
		      const struct lc_x509_certificate *cert)
{
	const struct lc_public_key *pub;
	int ret = 0;

	if (!cert)
		return -EINVAL;

	CKINT(lc_x509_policy_cert_valid(cert));
	if (ret != LC_X509_POL_TRUE)
		return ret;

	pub = &cert->pub;

	/* RFC5280 section 4.2.1.2: CA must have SKID */
	if (!cert->raw_skid_size) {
		printf_debug("X509 Policy %s: CA does not have an SKID\n",
			     __func__);
		return LC_X509_POL_FALSE;
	}

	/*
	 * RFC 5280 section 4.2.1.3: when key usage is present, the
	 * keyCertSign must be asserted. We do not mark non-conforming
	 * certificates as CA.
	 */
	if ((pub->key_usage & LC_KEY_USAGE_EXTENSION_PRESENT) &&
	    !(pub->key_usage & LC_KEY_USAGE_KEYCERTSIGN))
		return LC_X509_POL_FALSE;

	/* BSI TR02103 chapter 3 */
	CKINT(lc_x509_policy_version_ge(cert, 3));
	if (ret != LC_X509_POL_TRUE)
		return ret;

	/* BSI TR02103 chapter 3 */
	CKINT(lc_509_policy_cert_contains_signature(cert));
	if (ret != LC_X509_POL_TRUE)
		return ret;

	/* RFC 5280 section 4.2.1.9 - check whether it is a CA */
	if ((pub->basic_constraint & LC_KEY_IS_CA) == LC_KEY_IS_CA)
		return LC_X509_POL_TRUE;

	ret = LC_X509_POL_FALSE;

out:
	return ret;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_is_selfsigned,
		      const struct lc_x509_certificate *cert)
{
	if (!cert)
		return -EINVAL;

	if (!cert->self_signed)
		return LC_X509_POL_FALSE;

	return LC_X509_POL_TRUE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_is_root_ca,
		      const struct lc_x509_certificate *cert)
{
	lc_x509_pol_ret_t ret;

	if (!cert)
		return -EINVAL;

	if (cert->raw_akid) {
		/* If there is an AKID, it must match the SKID for a root CA. */
		CKINT(lc_x509_policy_match_akid(cert, cert->raw_skid,
						cert->raw_skid_size));
		if (ret != LC_X509_POL_TRUE) {
			printf_debug(
				"X509 Policy %s: root CA does not have matching SKID and AKID\n",
				__func__);
			return ret;
		}
	}

	/* A root CA must match all other constraints of a CA */
	CKINT(lc_x509_policy_is_ca(cert));
	if (ret != LC_X509_POL_TRUE)
		return ret;

	/* A root CA must be self-signed as it is the end of the chain */
	CKINT(lc_x509_policy_is_selfsigned(cert));
	if (ret != LC_X509_POL_TRUE)
		return ret;

out:
	return ret;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_can_validate_crls,
		      const struct lc_x509_certificate *cert)
{
	const struct lc_public_key *pub;

	if (!cert)
		return -EINVAL;

	pub = &cert->pub;

	/*
	 * RFC 5280 section 4.2.1.3: when key usage is present, the
	 * cRLSign must be asserted. We do not mark non-conforming
	 * certificates as CA.
	 */
	if ((pub->key_usage & LC_KEY_USAGE_EXTENSION_PRESENT) &&
	    !(pub->key_usage & LC_KEY_USAGE_CRLSIGN))
		return LC_X509_POL_FALSE;

	return LC_X509_POL_TRUE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_match_akid,
		      const struct lc_x509_certificate *cert,
		      const uint8_t *reference_akid, size_t reference_akid_len)
{
	const uint8_t *akid;
	size_t akid_len;
	lc_x509_pol_ret_t ret;

	CKNULL(cert, -EINVAL);

	if (!reference_akid)
		return LC_X509_POL_FALSE;

	akid = cert->raw_akid;
	akid_len = cert->raw_akid_size;

	/* CAs may omit the AKID - in this case use the subject key ID */
	if (!akid) {
		CKINT(lc_x509_policy_is_ca(cert));
		if (ret == LC_X509_POL_TRUE) {
			akid = cert->raw_skid;
			akid_len = cert->raw_skid_size;
		}
	}

	if (!akid)
		return LC_X509_POL_FALSE;

	if (lc_memcmp_secure(akid, akid_len, reference_akid,
			     reference_akid_len))
		return LC_X509_POL_FALSE;

	return LC_X509_POL_TRUE;

out:
	return ret;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_match_skid,
		      const struct lc_x509_certificate *cert,
		      const uint8_t *reference_skid, size_t reference_skid_len)
{
	const uint8_t *skid;
	size_t skid_len;
	lc_x509_pol_ret_t ret;

	CKNULL(cert, -EINVAL);

	if (!reference_skid)
		return LC_X509_POL_FALSE;

	skid = cert->raw_skid;
	skid_len = cert->raw_skid_size;

	if (!skid)
		return LC_X509_POL_FALSE;

	if (lc_memcmp_secure(skid, skid_len, reference_skid,
			     reference_skid_len))
		return LC_X509_POL_FALSE;

	return LC_X509_POL_TRUE;

out:
	return ret;
}

static lc_x509_pol_ret_t
lc_x509_policy_match_key_usage_pub(const struct lc_public_key *pub,
				   uint16_t required_key_usage)
{
	uint16_t set_keyusage;

	if (!pub)
		return -EINVAL;

	/* If the caller does not requests the checking, return true */
	if (!required_key_usage)
		return LC_X509_POL_TRUE;

	set_keyusage =
		pub->key_usage & (uint16_t)~LC_KEY_USAGE_EXTENSION_PRESENT;

	/* If extension is not present at all, we do not match any EKU */
	if (!(pub->key_usage & LC_KEY_USAGE_EXTENSION_PRESENT))
		return LC_X509_POL_FALSE;

	/* The required key usage must be a subset of all key usage flags */
	if ((set_keyusage & required_key_usage) == required_key_usage)
		return LC_X509_POL_TRUE;

	return LC_X509_POL_FALSE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_match_key_usage,
		      const struct lc_x509_certificate *cert,
		      uint16_t required_key_usage)
{
	if (!cert)
		return -EINVAL;

	return lc_x509_policy_match_key_usage_pub(&cert->pub,
						  required_key_usage);
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t,
		      lc_x509_policy_match_extended_key_usage,
		      const struct lc_x509_certificate *cert,
		      uint16_t required_eku)
{
	const struct lc_public_key *pub;
	uint16_t eku;

	if (!cert)
		return -EINVAL;

	/* If the caller does not requests the checking, return true */
	if (!required_eku)
		return LC_X509_POL_TRUE;

	pub = &cert->pub;

	eku = pub->key_eku & (uint16_t)~LC_KEY_EKU_EXTENSION_PRESENT;

	/* If extension is not present at all, we do not match any EKU */
	if (!(pub->key_eku & LC_KEY_EKU_EXTENSION_PRESENT))
		return LC_X509_POL_FALSE;

	/* The required EKU must be a subset of all EKU flags */
	if ((eku & required_eku) == required_eku)
		return LC_X509_POL_TRUE;

	return LC_X509_POL_FALSE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_time_valid,
		      const struct lc_x509_certificate *cert,
		      time64_t current_time)
{
	if (!cert || current_time < 0)
		return -EINVAL;

	/*
	 * Reject negative time values in the certificate.
	 */
	if (cert->valid_from < 0 || cert->valid_to < 0)
		return LC_X509_POL_FALSE;

	/*
	 * If we have a valid_from time and the given time is smaller, reject
	 * it.
	 */
	if (cert->valid_from && cert->valid_from > current_time)
		return LC_X509_POL_FALSE;

	/*
	 * If we have a valid_to time and the given time is larger, reject
	 * it.
	 */
	if (cert->valid_to && cert->valid_to < current_time)
		return LC_X509_POL_FALSE;

	/*
	 * The time either falls within the range of the certificate time.
	 * Note, if the certificate does not give either a lower or upper
	 * boundary, there is no match performed (i.e. implicit success).
	 */
	return LC_X509_POL_TRUE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_time_valid_now,
		      const struct lc_x509_certificate *cert)
{
	time64_t time_since_epoch = 0;
	int ret;

	ret = lc_get_time(&time_since_epoch, NULL);
	/*
	 * If gathering of time is not supported on local system, do not check
	 * it.
	 */
	if (ret == -EOPNOTSUPP) {
		return LC_X509_POL_TRUE;
	} else if (ret < 0)
		return ret;

	/*
	 * Certificate validation: Check validity of time (if the underlying
	 * platform offers a time stamp)
	 */
	return lc_x509_policy_time_valid(cert, time_since_epoch);
}

static lc_x509_pol_ret_t
lc_x509_policy_consistency(const struct lc_x509_certificate *cert)
{
	const struct lc_public_key *pub = &cert->pub;

	if (!pub)
		return LC_X509_POL_TRUE;

	switch (pub->pkey_algo) {
	case LC_SIG_ED25519:
	case LC_SIG_ED448:
		/*
		 * RFC9295 a leaf certificate must not have:
		 * keyEncipherment
		 * dataEncipherment
		 * keyAgreement
		 * encipherOnly
		 * decipherOnly
		 */
		if (lc_x509_policy_match_key_usage(
			    cert, LC_KEY_USAGE_KEY_ENCIPHERMENT) ==
		    LC_X509_POL_TRUE) {
			return LC_X509_POL_FALSE;
		}
		if (lc_x509_policy_match_key_usage(
			    cert, LC_KEY_USAGE_DATA_ENCIPHERMENT) ==
		    LC_X509_POL_TRUE) {
			return LC_X509_POL_FALSE;
		}
		if (lc_x509_policy_match_key_usage(
			    cert, LC_KEY_USAGE_KEY_AGREEMENT) ==
		    LC_X509_POL_TRUE) {
			return LC_X509_POL_FALSE;
		}
		if (lc_x509_policy_match_key_usage(
			    cert, LC_KEY_USAGE_ENCIPHER_ONLY) ==
		    LC_X509_POL_TRUE) {
			return LC_X509_POL_FALSE;
		}
		if (lc_x509_policy_match_key_usage(
			    cert, LC_KEY_USAGE_DECIPHER_ONLY) ==
		    LC_X509_POL_TRUE) {
			return LC_X509_POL_FALSE;
		}
		break;

	case LC_SIG_DILITHIUM_44:
	case LC_SIG_DILITHIUM_65:
	case LC_SIG_DILITHIUM_87:
	case LC_SIG_SPHINCS_SHAKE_128F:
	case LC_SIG_SPHINCS_SHAKE_192F:
	case LC_SIG_SPHINCS_SHAKE_256F:
	case LC_SIG_SPHINCS_SHAKE_128S:
	case LC_SIG_SPHINCS_SHAKE_192S:
	case LC_SIG_SPHINCS_SHAKE_256S:
	case LC_SIG_DILITHIUM_44_ED25519:
	case LC_SIG_DILITHIUM_65_ED25519:
	case LC_SIG_DILITHIUM_87_ED25519:
	case LC_SIG_DILITHIUM_44_ED448:
	case LC_SIG_DILITHIUM_65_ED448:
	case LC_SIG_DILITHIUM_87_ED448:
		break;

	case LC_SIG_RSA_PKCS1:
	case LC_SIG_RSA_PKCS1_SHA2_256:
	case LC_SIG_RSA_PKCS1_SHA2_384:
	case LC_SIG_RSA_PKCS1_SHA2_512:
	case LC_SIG_RSA_PKCS1_SHA3_256:
	case LC_SIG_RSA_PKCS1_SHA3_384:
	case LC_SIG_RSA_PKCS1_SHA3_512:
	case LC_SIG_ECDSA_X963:
	case LC_SIG_ECDSA_X963_SHA2_256:
	case LC_SIG_ECDSA_X963_SHA2_384:
	case LC_SIG_ECDSA_X963_SHA2_512:
	case LC_SIG_ECDSA_X963_SHA3_256:
	case LC_SIG_ECDSA_X963_SHA3_384:
	case LC_SIG_ECDSA_X963_SHA3_512:
	case LC_SIG_ECRDSA_PKCS1:
	case LC_SIG_SM2:
	case LC_SIG_UNKNOWN:
	default:
		return -ENOPKG;
	}

	return LC_X509_POL_TRUE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_cert_valid,
		      const struct lc_x509_certificate *cert)
{
	lc_x509_pol_ret_t ret;

	if (!cert)
		return -EINVAL;

	/* A CSR is always valid as it has no policy attached to it */
	if (cert->is_csr)
		return LC_X509_POL_TRUE;

	/*
	 * RFC5280 section 4.2.1.3: SKID must always be present.
	 */
	if (!cert->raw_skid_size) {
		printf_debug(
			"X509 Policy %s: certificate does not have an SKID\n",
			__func__);
		return LC_X509_POL_FALSE;
	}

	/*
	 * RFC5280 section 4.2.1.6: If a SAN is present, a subject may be
	 * omitted.
	 */
	if (!cert->raw_subject && !cert->san_dns && !cert->san_ip) {
		printf_debug(
			"X509 Policy %s: certificate does not have any identification\n",
			__func__);
		return LC_X509_POL_FALSE;
	}

	CKINT(lc_x509_policy_consistency(cert));
	if (ret != LC_X509_POL_TRUE)
		return ret;

out:
	return ret;
}

static int lc_x509_policy_verify_general(const struct lc_public_key *pkey,
					 const struct lc_x509_certificate *cert,
					 uint64_t flags)
{
	int ret;

	(void)flags;

	CKINT(lc_x509_policy_time_valid_now(cert));

	/*
	 * Certificate validation: Check signature
	 */
	CKINT_SIGCHECK(lc_public_key_verify_signature(pkey, &cert->sig));

out:
	return ret;
}

LC_INTERFACE_FUNCTION(int, lc_x509_policy_verify_cert,
		      const struct lc_public_key *pkey,
		      const struct lc_x509_certificate *cert, uint64_t flags)
{
	lc_x509_pol_ret_t ret_pol;
	int ret;

	/* Apply general checks of the certificate */
	CKINT_HARDENED(lc_x509_policy_verify_general(pkey, cert, flags));

	/* Do we have a valid certificate? */
	CKINT_POL(lc_x509_policy_cert_valid(cert));

	/*
	 * A certificate must be allowed for key sign for successfully
	 * validating a certificate. Also, the key usage must be critical.
	 */
	CKINT_POL(lc_x509_policy_match_key_usage_pub(
		pkey, LC_KEY_USAGE_CRITICAL | LC_KEY_USAGE_KEYCERTSIGN));

out:
	return ret;
}

static lc_x509_pol_ret_t lc_x509_policy_cert_name_one(
	const struct lc_x509_certificate_name_component *cert_name,
	const struct lc_x509_certificate_name_component *search_name)
{
	/* If the search is empty, we have a match */
	if (!search_name || !search_name->size || !search_name->value)
		return LC_X509_POL_TRUE;

	/* As the caller searches for the name, require it being present */
	if (!cert_name || !cert_name->size || !cert_name->value)
		return LC_X509_POL_FALSE;

	if (lc_memcmp_secure(search_name->value, search_name->size,
			     cert_name->value, cert_name->size))
		return LC_X509_POL_FALSE;

	return LC_X509_POL_TRUE;
}

static lc_x509_pol_ret_t lc_x509_policy_cert_name_match(
	const struct lc_x509_certificate_name *cert_name,
	const struct lc_x509_certificate_name *search_name)
{
	lc_x509_pol_ret_t ret_pol;

	if (!cert_name || !search_name)
		return -EINVAL;

	ret_pol = lc_x509_policy_cert_name_one(&cert_name->email,
					       &search_name->email);
	if (ret_pol == LC_X509_POL_FALSE)
		return LC_X509_POL_FALSE;
	ret_pol =
		lc_x509_policy_cert_name_one(&cert_name->cn, &search_name->cn);
	if (ret_pol == LC_X509_POL_FALSE)
		return LC_X509_POL_FALSE;
	ret_pol =
		lc_x509_policy_cert_name_one(&cert_name->ou, &search_name->ou);
	if (ret_pol == LC_X509_POL_FALSE)
		return LC_X509_POL_FALSE;
	ret_pol = lc_x509_policy_cert_name_one(&cert_name->o, &search_name->o);
	if (ret_pol == LC_X509_POL_FALSE)
		return LC_X509_POL_FALSE;
	ret_pol =
		lc_x509_policy_cert_name_one(&cert_name->st, &search_name->st);
	if (ret_pol == LC_X509_POL_FALSE)
		return LC_X509_POL_FALSE;
	ret_pol = lc_x509_policy_cert_name_one(&cert_name->c, &search_name->c);
	if (ret_pol == LC_X509_POL_FALSE)
		return LC_X509_POL_FALSE;

	return LC_X509_POL_TRUE;
}

static lc_x509_pol_ret_t lc_x509_policy_cert_subject_match_san_ip(
	const struct lc_x509_certificate *cert,
	const struct lc_x509_certificate_name *search_name)
{
	if (cert->san_ip_len && cert->san_ip) {
		uint8_t ip[16];
		size_t ip_len;
		int ret =
			lc_x509_enc_san_ip(search_name->cn.value, ip, &ip_len);

		if (ret)
			return ret;
		if (!lc_memcmp_secure(ip, ip_len, cert->san_ip,
				      cert->san_ip_len))
			return LC_X509_POL_TRUE;
	}

	return LC_X509_POL_FALSE;
}

static lc_x509_pol_ret_t lc_x509_policy_cert_subject_match_san_dns(
	const struct lc_x509_certificate *cert,
	const struct lc_x509_certificate_name *search_name)
{
	if (cert->san_dns_len && cert->san_dns &&
	    !lc_memcmp_secure(search_name->cn.value, search_name->cn.size,
			      cert->san_dns, cert->san_dns_len))
		return LC_X509_POL_TRUE;

	return LC_X509_POL_FALSE;
}

static lc_x509_pol_ret_t lc_x509_policy_cert_subject_match_san_email(
	const struct lc_x509_certificate *cert,
	const struct lc_x509_certificate_name *search_name)
{
	if (cert->san_email_len && cert->san_email &&
	    !lc_memcmp_secure(search_name->cn.value, search_name->cn.size,
			      cert->san_email, cert->san_email_len))
		return LC_X509_POL_TRUE;

	return LC_X509_POL_FALSE;
}

LC_INTERFACE_FUNCTION(lc_x509_pol_ret_t, lc_x509_policy_cert_subject_match,
		      const struct lc_x509_certificate *cert,
		      const struct lc_x509_certificate_name *search_name,
		      enum lc_x509_policy_cert_subject_match_flag flag)
{
	if (!cert || !search_name)
		return -EINVAL;

	switch (flag) {
	case lc_x509_policy_cert_subject_match_dn_and_san:
		/*
		 * Check whether the SAN matches and if so, we are good,
		 * otherwise fall through to the DN check.
		 */
		if (lc_x509_policy_cert_subject_match_san_dns(
			    cert, search_name) == LC_X509_POL_TRUE)
			return LC_X509_POL_TRUE;

		if (lc_x509_policy_cert_subject_match_san_ip(
			    cert, search_name) == LC_X509_POL_TRUE)
			return LC_X509_POL_TRUE;

		if (lc_x509_policy_cert_name_match(
			    &cert->san_directory_name_segments, search_name) ==
		    LC_X509_POL_TRUE)
			return LC_X509_POL_TRUE;

		fallthrough;
	case lc_x509_policy_cert_subject_match_dn_only:
		/* Check whether the DN matches */
		return lc_x509_policy_cert_name_match(&cert->subject_segments,
						      search_name);

	case lc_x509_policy_cert_subject_match_san_ip_only:
		/* Check whether the SAN IP matches */
		return lc_x509_policy_cert_subject_match_san_ip(cert,
								search_name);

	case lc_x509_policy_cert_subject_match_san_dns_only:
		/* Check whether the SAN DNS matches */
		return lc_x509_policy_cert_subject_match_san_dns(cert,
								 search_name);

	case lc_x509_policy_cert_subject_match_san_email_only:
		/* Check whether the SAN email matches */
		return lc_x509_policy_cert_subject_match_san_email(cert,
								   search_name);

	case lc_x509_policy_cert_subject_match_san_name_only:
		/* Check whether the SAN name components matches */
		return lc_x509_policy_cert_name_match(
			&cert->san_directory_name_segments, search_name);

	case lc_x509_policy_cert_subject_match_issuer_only:
		/* Check whether the issue matches */
		return lc_x509_policy_cert_name_match(&cert->issuer_segments,
						      search_name);

	default:
		return LC_X509_POL_FALSE;
	}

	return LC_X509_POL_FALSE;
}
