/*
 * Certificate functions and definitions.
 *
 * Copyright IBM Corp. 2022
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */
#ifndef LIBPV_CERT_H
#define LIBPV_CERT_H

#include <openssl/x509v3.h>
#include <openssl/err.h>

#include "libpv/common.h"

#define PV_IBM_Z_SUBJECT_COMMON_NAME "International Business Machines Corporation"
#define PV_IBM_Z_SUBJECT_COUNTRY_NAME "US"
#define PV_IBM_Z_SUBJECT_LOCALITY_NAME "Poughkeepsie"
#define PV_IBM_Z_SUBJECT_ORGANIZATIONAL_UNIT_NAME_SUFFIX "Key Signing Service"
#define PV_IBM_Z_SUBJECT_ORGANIZATION_NAME "International Business Machines Corporation"
#define PV_IBM_Z_SUBJECT_STATE "New York"
#define PV_IMB_Z_SUBJECT_ENTRY_COUNT 6

/* Minimum security level for the keys/certificates used to establish a chain of
 * trust (see https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html
 * for details).
 */
#define PV_CERTS_SECURITY_LEVEL 2

/** pv_cert_init:
 *
 * Should not be called by user.
 * Use pv_init() instead which
 * calls this function during creation.
 *
 * Sets up data structures for caching CRLs.
 */
void pv_cert_init(void);

/** pv_cert_cleanup:
 *
 * Should not be called by user.
 * Use pv_cleanup() instead which
 * calls this function during creation.
 *
 * Cleans up data structures for caching CRLs.
 */
void pv_cert_cleanup(void);

#define PV_CERT_ERROR g_quark_from_static_string("pv-cert-error-quark")
typedef enum {
	PV_CERT_ERROR_CERT_REVOKED,
	PV_CERT_ERROR_CERT_SIGNATURE_INVALID,
	PV_CERT_ERROR_CERT_SUBJECT_ISSUER_MISMATCH,
	PV_CERT_ERROR_CRL_DOWNLOAD_FAILED,
	PV_CERT_ERROR_CRL_SIGNATURE_INVALID,
	PV_CERT_ERROR_CRL_SUBJECT_ISSUER_MISMATCH,
	PV_CERT_ERROR_FAILED_DOWNLOAD_CRL,
	PV_CERT_ERROR_INTERNAL,
	PV_CERT_ERROR_INVALID_PARM,
	PV_CERT_ERROR_INVALID_SIGNATURE_ALGORITHM,
	PV_CERT_ERROR_INVALID_VALIDITY_PERIOD,
	PV_CERT_ERROR_LOAD_CRL,
	PV_CERT_ERROR_LOAD_DEFAULT_CA,
	PV_CERT_ERROR_LOAD_ROOT_CA,
	PV_CERT_ERROR_MALFORMED_CERTIFICATE,
	PV_CERT_ERROR_MALFORMED_ROOT_CA,
	PV_CERT_ERROR_NO_CRL,
	PV_CERT_ERROR_NO_CRLDP,
	PV_CERT_ERROR_NO_IBM_Z_SIGNING_KEY,
	PV_CERT_ERROR_NO_ISSUER_IBM_Z_FOUND,
	PV_CERT_ERROR_NO_PUBLIC_KEY,
	PV_CERT_ERROR_READ_CERTIFICATE,
	PV_CERT_ERROR_READ_CRL,
	PV_CERT_ERROR_SIGNATURE_ALGORITHM_MISMATCH,
	PV_CERT_ERROR_SKID_AKID_MISMATCH,
	PV_CERT_ERROR_VERIFICATION_FAILED,
	PV_CERT_ERROR_WRONG_CA_USED,
} PvCertErrors;

/** PvX509WithPath - X509 certificate associated with a path
 */
typedef struct {
	X509 *cert;
	char *path;
} PvX509WithPath;

/** pv_x509_with_path_new:
 *
 *  @cert: X509 certificate
 *  @path: Path of that X509 certificate
 *
 *  Returns: (nullable) (transfer full): new X509 with path
 */
PvX509WithPath *pv_x509_with_path_new(X509 *cert, const char *path);

/** pv_x509_with_path_free:
 *
 * Frees the path and the PvX509WithPath; Decreases the refcount of the X509
 */
void pv_x509_with_path_free(PvX509WithPath *cert);

typedef STACK_OF(DIST_POINT) STACK_OF_DIST_POINT;
typedef STACK_OF(X509) STACK_OF_X509;
typedef STACK_OF(X509_CRL) STACK_OF_X509_CRL;
typedef GSList PvCertWithPathList;

typedef struct {
	X509 *cert;
	STACK_OF_X509_CRL *crls;
} PvX509Pair;

/** pv_x509_pair_new_take:
 * @cert: ptr to X509
 * @crls: ptr to CRLs
 *
 * Takes a X509 and the associated CRLs and builds a pair.
 * Both, *cert and *crls will be NULL afterwards, and owned by the pair.
 *
 * Returns: (nullable) (transfer full):	New PvX509Pair
 */
PvX509Pair *pv_x509_pair_new_take(X509 **cert, STACK_OF_X509_CRL **crls);

/** pv_x509_pair_free:
 *
 * Decreases the refcount of the X509 and crls.
 * Frees the PvX509Pair.
 */
void pv_x509_pair_free(PvX509Pair *pair);

void STACK_OF_DIST_POINT_free(STACK_OF_DIST_POINT *stack);
void STACK_OF_X509_free(STACK_OF_X509 *stack);
void STACK_OF_X509_CRL_free(STACK_OF_X509_CRL *stack);

/** pv_x509_from_pem_der_data:
 *
 * @data: GBytes containing the cert in PEM format
 * @error: return location for a #GError
 *
 * Returns: (nullable) (transfer full): X509 cert
 */
X509 *pv_x509_from_pem_der_data(GBytes *data, GError **error);

/** pv_x509_get_ec_pubkey:
 *
 * @cert: X509 to extract elliptic curve pubkey from
 * @nid: numerical identifier of the expected curve
 * @error: return location for a #GError
 *
 * Returns: (nullable) (transfer full):	corresponding pupkey for the given certificate
 */
EVP_PKEY *pv_x509_get_ec_pubkey(X509 *cert, int nid, GError **error);

/** pv_get_ec_pubkeys:
 *
 * @certs_with_path: List of PvX509WithPath
 * @nid: numerical identifier of the expected curve
 * @error: return location for a #GError
 *
 * Returns: (nullable) (transfer full):	List of corresponding public keys for the given certificate
 */
GSList *pv_get_ec_pubkeys(PvCertWithPathList *certs_with_path, int nid, GError **error);

/* pv_load_certificates:
 *
 * @cert_paths: list of cert paths.
 * @error: return location for a #GError
 *
 * @cert_paths must contain at least one element, otherwise an error is
 * reported.
 *
 * Returns: (nullable) (transfer full):	List of PvX509WithPath corresponding to the given paths
 */
PvCertWithPathList *pv_load_certificates(char **cert_paths, GError **error);

/* pv_load_first_cert_from_file:
 *
 * @path: location of the x509
 * @error: return location for a #GError
 *
 * This function reads in only the first certificate and ignores all other. This
 * is only relevant for the PEM file format. For the host-key document and the
 * root CA this behavior is expected.
 *
 * Returns: (nullable) (transfer full):	PvX509WithPath corresponding to the given path
 */
X509 *pv_load_first_cert_from_file(const char *path, GError **error);

/* pv_load_first_crl_from_file:
 *
 * @path: location of the x509 CRL
 * @error: return location for a #GError
 *
 * This function reads in only the first CRL and ignores all other. This
 * is only relevant for the PEM file format.
 *
 * Returns: (nullable) (transfer full):	X509_CRL corresponding to the given path
 */
X509_CRL *pv_load_first_crl_from_file(const char *path, GError **error);

/** pv_store_setup_crl_download:
 *
 * @st: X509_STORE
 */
void pv_store_setup_crl_download(X509_STORE *st);

/** pv_load_first_crl_by_cert:
 * @cert: X509 to specify the download location.
 * @error: return location for a #GError
 *
 * This function returns the first X509_CRL found from the CRL distribution
 * points specified in @cert.
 *
 * Returns: (nullable) (transfer full):	x509 CRL corresponding to the given X509
 */
X509_CRL *pv_load_first_crl_by_cert(X509 *cert, GError **error);

/** pv_try_load_crls_by_certs:
 *
 * @certs_with_path: List of PvX509WithPath
 *
 * Returns: (nullable) (transfer full):	Stack of CRLs corresponding to the given X509
 */
STACK_OF_X509_CRL *pv_try_load_crls_by_certs(PvCertWithPathList *certs_with_path);

/** pv_store_setup:
 *
 * @root_ca_path: Location of the rootCA or NULL if SystemRoot CA shall be used
 * @crl_paths: List of CRL paths or NULL
 * @cert_with_crl_paths: List of (untrusted) X509 paths
 * @error: return location for a #GError
 *
 * The untrusted certs need to be verified before actually verifying a Host Key Document.
 *
 * Returns: (nullable) (transfer full):	X509_store with given input data.
 *
 */
X509_STORE *pv_store_setup(char *root_ca_path, char **crl_paths, char **cert_with_crl_paths,
			   GError **error);

/** pv_get_x509_stack:
 *
 * x509_with_path_list: list of PvX509WithPath
 *
 * Returns: (nullable) (transfer full):	Stack of X509 corresponding to the given x509 with path
 */
STACK_OF_X509 *pv_get_x509_stack(const GSList *x509_with_path_list);

/** pv_init_store_ctx:
 *
 * @ctx: a uninitialized Store CTX
 * @trusted: X509_STORE with a trusted rootCA
 * @chain: untrusted X509s
 * @error: return location for a #GError
 *
 * Can be called multiple times on the same context if X509_STORE_CTX_cleanup(ctx)
 * was called before.
 *
 * Returns:
 * 	0 on success
 * 	-1 in failure
 */
int pv_init_store_ctx(X509_STORE_CTX *ctx, X509_STORE *trusted, STACK_OF_X509 *chain,
		      GError **error) PV_NONNULL(1, 2, 3);

/** pv_init_store_ctx:
 *
 * @trusted: X509_STORE with a trusted rootCA
 * @chain: untrusted X509s
 * @error: return location for a #GError
 *
 * Returns: (nullable) (transfer full):	X509_STORE_CTX setup with the input data
 */
X509_STORE_CTX *pv_create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, GError **error)
	PV_NONNULL(1, 2);
/** pv_remove_ibm_signing_certs:
 *
 * @certs: Stack of X509s
 *
 * Returns: (transfer full):
 * 	List of all IBM Z signing key certificates in @certs and remove them
 * 		from the chain.
 * 	Empty stack if no IBM Z signing key is found.
 */
STACK_OF_X509 *pv_remove_ibm_signing_certs(STACK_OF_X509 *certs);

/** pv_c2b_name:
 *
 * Workaround to fix the mismatch between issuer name of the
 * IBM Z signing CRLs and the IBM Z signing key subject name.
 *
 * In RFC 5280 the attributes of a (subject/issuer) name is not mandatory
 * ordered. The problem is that our certificates are not consistent in the order
 * (see https://tools.ietf.org/html/rfc5280#section-4.1.2.4 for details).
 *
 * This function tries to reorder the name attributes such that
 * further OpenSSL calls can work with it.  The caller is
 * responsible to free the returned value.
 */
X509_NAME *pv_c2b_name(const X509_NAME *name);

/** pv_verify_host_key:
 *
 * @host_key: X509 to be verified
 * @issuer_pairs: IBM signing key X509+CRLs Pairs used for verification
 * @level: Security level. see PV_CERTS_SECURITY_LEVEL
 * @error: return location for a #GError
 *
 * Returns:
 * 	0 if Host key could be verified with one of the IBM signing keys
 * 	-1 if no IBM signing key could verify the authenticity of the given host key
 *
 */
int pv_verify_host_key(X509 *host_key, GSList *issuer_pairs, int verify_flags, int level,
		       GError **error);

/** pv_verify_cert:
 *
 * @ctx: trusted store ctx used for verification
 * @cert: X509 to be verified
 * @error: return location for a #GError
 *
 * Cannot be used to verify host keys with IBM signing keys, as IBM signing
 * keys are no intermediate CAs. Use pv_verify_host_key() instead.
 *
 * Returns:
 * 	0 if @cert could be verified
 * 	-1 if @cert could not be verified
 */
int pv_verify_cert(X509_STORE_CTX *ctx, X509 *cert, GError **error) PV_NONNULL(1, 2);

/** pv_check_crl_valid_for_cert:
 *
 * @crl: CRL to be verified
 * @cert: Cert that probably issued the given CRL
 * @verify_flags: X509 Verification flags (X509_V_FLAG_<TYPE>)
 * @error: return location for a #GError
 *
 * Verify whether a revocation list @crl is valid and is issued by @cert. For
 * this multiple steps must be done:
 *
 * 1. verify issuer of the CRL matches with the suject name of @cert
 * 2. verify the validity period of the CRL
 * 3. verify the signature of the CRL
 *
 * Important: This function does not verify whether @cert is allowed to issue a
 * CRL.
 *
 * Returns:
 * 	 0 if @crl is valid and issued by @cert
 * 	 -1 otherwise
 */
int pv_verify_crl(X509_CRL *crl, X509 *cert, int verify_flags, GError **error);

/** pv_check_chain_parameters:
 *
 * @chain: chain of trust to be validated
 * @error: return location for a #GError
 *
 * Verifies that chain has at least a RootCA ans intermediate CA
 * and logs the used ROD CA subject
 *
 * Returns:
 * 	 0 @chain is valid
 * 	 -1 otherwise
 */
int pv_check_chain_parameters(const STACK_OF_X509 *chain, GError **error);

/** pv_store_set_verify_param:
 *
 * @store: X509_STORE to set parameters
 * @error: return location for a #GError
 *
 * Returns:
 * 	0 on success
 * 	-1 on failure
 */
int pv_store_set_verify_param(X509_STORE *store, GError **error);

/** pv_store_ctx_find_valid_crls:
 *
 * @ctx: STORE_CTX for searching CRLs
 * @cert: X509 to match CRLs aggainst
 * @error: return location for a #GError
 *
 * Returns: (nullable) (transfer full):	STACK of CRLs related to given @crl fin @ctx
 */
STACK_OF_X509_CRL *pv_store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, GError **error)
	PV_NONNULL(1, 2);

/** pv_verify_host_key_doc:
 *
 * @host_key_certs_with_path: X509s to be verified
 * @trusted. X509_STORE with a rusted RootCA
 * @untrusted_certs: STACK OF untrusted X509s
 * @online: true if CRLs shall be downloaded
 * @error: return location for a #GError
 *
 * Returns:
 * 	0 if all given HKDs could be verified using the chain of trust.
 * 	-1 otherwise
 */
int pv_verify_host_key_doc(PvCertWithPathList *host_key_certs_with_path, X509_STORE *trusted,
			   STACK_OF_X509 *untrusted_certs, gboolean online, GError **error)
	PV_NONNULL(1, 2, 3);

/** pv_verify_host_key_docs_by_path:
 *
 * @host_key_paths: locations of X509 to be verified
 * @optional_root_ca_path: rootCA location or NULL if Default shall be used
 * @optional_crl_paths: locations of CRLs or NULL
 * @untrusted_cert_paths: locations of IntermediateCAs including the IBM signing key
 * @online: true if CRLs shall be downloaded
 * @error: return location for a #GError
 *
 * Returns:
 * 	0 if all given HKDs could be verfied using the chain of trust.
 * 	-1 otherwise
 */
int pv_verify_host_key_docs_by_path(char **host_key_paths, char *optional_root_ca_path,
				    char **optional_crl_paths, char **untrusted_cert_paths,
				    gboolean online, GError **error) PV_NONNULL(1, 4);

WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(AUTHORITY_KEYID, AUTHORITY_KEYID_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvX509WithPath, pv_x509_with_path_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_DIST_POINT, STACK_OF_DIST_POINT_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509, STACK_OF_X509_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509_CRL, STACK_OF_X509_CRL_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509, X509_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_CRL, X509_CRL_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_LOOKUP, X509_LOOKUP_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_NAME, X509_NAME_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_VERIFY_PARAM, X509_VERIFY_PARAM_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvX509Pair, pv_x509_pair_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE, X509_STORE_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE_CTX, X509_STORE_CTX_free)

#endif /*  LIBPV_CERT_H */
