/*
 * Copyright © 2025 Mobi - Com Polska Sp. z o.o.
 * Author: Małgorzata Olszówka <Malgorzata.Olszowka@stunnel.org>
 * All rights reserved.
 *
 * This file implements the handling of EdDSA and XDH keys stored on a PKCS11 token.
 * Inside EVP_PKEY, Ed25519/Ed448 and X25519/X448 keys are stored in an ECX_KEY
 * structure.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS 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 ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "libp11-int.h"
#include <string.h>

#if !defined(OPENSSL_NO_ECX) && OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/ec.h>
#include <openssl/bn.h>

#if OPENSSL_VERSION_NUMBER < 0x40000000L
static EVP_PKEY_METHOD *pkcs11_ed25519_method = NULL;
static EVP_PKEY_METHOD *pkcs11_ed448_method = NULL;
static const EVP_PKEY_METHOD *orig_ed25519_method = NULL;
static const EVP_PKEY_METHOD *orig_ed448_method = NULL;

static EVP_PKEY_METHOD *pkcs11_x25519_method = NULL;
static EVP_PKEY_METHOD *pkcs11_x448_method = NULL;
static const EVP_PKEY_METHOD *orig_x25519_method = NULL;
static const EVP_PKEY_METHOD *orig_x448_method = NULL;
#endif /* OPENSSL_VERSION_NUMBER < 0x40000000L */

int (*orig_ed25519_digestsign)(EVP_MD_CTX *ctx, unsigned char *sig, size_t *siglen,
	const unsigned char *tbs, size_t tbslen);

int (*orig_ed448_digestsign)(EVP_MD_CTX *ctx, unsigned char *sig, size_t *siglen,
	const unsigned char *tbs, size_t tbslen);

int (*orig_x25519_derive)(EVP_PKEY_CTX *ctx, unsigned char *secret, size_t *secretlen);

int (*orig_x448_derive)(EVP_PKEY_CTX *ctx, unsigned char *secret, size_t *secretlen);

#if OPENSSL_VERSION_NUMBER < 0x40000000L

/******************************************************************************/
/* EdDSA methods                                                              */
/******************************************************************************/

/*
 * EVP_PKEY method sign wrapper for EdDSA.
 * This function is invoked internally by EVP_PKEY_sign().
 * If the key belongs to PKCS#11, perform signing via pkcs11_evp_pkey_eddsa_sign().
 */
static int pkcs11_eddsa_pmeth_sign(EVP_PKEY_CTX *ctx, unsigned char *sig,
	size_t *siglen, const unsigned char *tbs, size_t tbslen)
{
	EVP_PKEY *pkey;
	PKCS11_OBJECT_private *key;
	PKCS11_SLOT_private *slot;
	CK_SESSION_HANDLE session;

	if (siglen == NULL || *siglen > UINT_MAX)
		return 0;

	pkey = EVP_PKEY_CTX_get0_pkey(ctx);
	if (!pkey)
		return 0;

	key = pkcs11_get_ex_data_pkey(pkey);
	if (!key)
		return 0;

	slot = key->slot;
	if (!slot)
		return 0;

	if (pkcs11_get_session(slot, 0, &session))
		return 0;

	pkcs11_put_session(slot, session);

	if (!pkcs11_evp_pkey_eddsa_sign(key, sig, siglen, tbs, tbslen))
		return 0;

	return 1;
}

/*
 * Custom EVP_PKEY_METHOD digestsign implementation for EdDSA (Ed25519/Ed448)
 *
 * This function supports the two-step signing process used by EVP_DigestSign*():
 *   1. Query the required signature length (sig == NULL).
 *   2. Perform the actual signing when a buffer is provided (sig != NULL).
 *
 * If the key is managed by PKCS#11, the signing is performed via pkcs11_evp_pkey_eddsa_sign().
 * Otherwise, the call is delegated to the original OpenSSL Ed25519/Ed448 implementation.
 */
static int pkcs11_eddsa_pmeth_digestsign(EVP_MD_CTX *ctx, unsigned char *sig,
	size_t *siglen, const unsigned char *tbs, size_t tbslen)
{
	EVP_PKEY *pkey;
	PKCS11_OBJECT_private *key;
	PKCS11_SLOT_private *slot;
	CK_SESSION_HANDLE session;

	if (siglen == NULL)
		return -1;

	pkey = EVP_PKEY_CTX_get0_pkey(EVP_MD_CTX_pkey_ctx(ctx));
	if (!pkey)
		return -1;

	key = pkcs11_get_ex_data_pkey(pkey);
	if (!key)
		return -1;

	slot = key->slot;
	if (!slot)
		return -1;

	if (pkcs11_get_session(slot, 0, &session))
		return -1;

	pkcs11_put_session(slot, session);

	/* Step 1: caller asks for signature length only */
	if (sig == NULL) {
		if (EVP_PKEY_id(pkey) == EVP_PKEY_ED25519)
			*siglen = 64; /* fixed size for Ed25519 */
		else if (EVP_PKEY_id(pkey) == EVP_PKEY_ED448)
			*siglen = 114; /* fixed size for Ed448 */
		else
			return -1;
		/* success: report the expected signature length only,
		 * no signing is performed in this call */
		return 1;
	}

	/* Step 2: actual signing */
	if (!pkcs11_evp_pkey_eddsa_sign(key, sig, siglen, tbs, tbslen))
		return -1;

	return 1;
}


static int pkcs11_pkey_ed25519_digestsign(EVP_MD_CTX *ctx,
		unsigned char *sig, size_t *siglen,
		const unsigned char *tbs, size_t tbslen)
{
	int ret;

	ret = pkcs11_eddsa_pmeth_digestsign(ctx, sig, siglen, tbs, tbslen);
	if (ret < 0)
		/* assume a foreign key */
		ret = (*orig_ed25519_digestsign)(ctx, sig, siglen, tbs, tbslen);
	return ret;
}

static int pkcs11_pkey_ed448_digestsign(EVP_MD_CTX *ctx,
		unsigned char *sig, size_t *siglen,
		const unsigned char *tbs, size_t tbslen)
{
	int ret;

	ret = pkcs11_eddsa_pmeth_digestsign(ctx, sig, siglen, tbs, tbslen);
	if (ret < 0)
		ret = (*orig_ed448_digestsign)(ctx, sig, siglen, tbs, tbslen);
	return ret;
}

/*
 * For Ed25519/Ed448, no digest algorithm can be set.
 * The only valid value here is NULL (PureEdDSA).
 * OpenSSL calls this during DigestSignInit() to check whether the digest is acceptable.
 */
static int pkcs11_eddsa_pmeth_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2)
{
	(void)ctx;
	(void)p1;
	switch (type) {
	case EVP_PKEY_CTRL_MD:
		if (p2 == NULL)
			return 1; /* Accept NULL digest */
		return 0; /* Reject if caller tries to set a digest */
	default:
		return -2; /* command not supported */
	}
}

/* Global initialize ED25519 EVP_PKEY_METHOD */
static int pkcs11_ed25519_method_new(void)
{
	int orig_id, orig_flags;

	if (pkcs11_ed25519_method)
		return 1; /* EVP_PKEY_ED25519 method already initialized */

	orig_ed25519_method = EVP_PKEY_meth_find(EVP_PKEY_ED25519);
	if (!orig_ed25519_method)
		return 0;

	EVP_PKEY_meth_get0_info(&orig_id, &orig_flags, orig_ed25519_method);
	if (orig_id != EVP_PKEY_ED25519 || !(orig_flags & EVP_PKEY_FLAG_SIGCTX_CUSTOM))
		return 0;

	/* The digestsign() method is used to generate a signature in a one-shot mode */
	EVP_PKEY_meth_get_digestsign(orig_ed25519_method, &orig_ed25519_digestsign);
	if (!orig_ed25519_digestsign)
		return 0;

	/* Don't assume any digest related defaults */
	pkcs11_ed25519_method = EVP_PKEY_meth_new(EVP_PKEY_ED25519, EVP_PKEY_FLAG_SIGCTX_CUSTOM);
	if (!pkcs11_ed25519_method)
		return 0;

	/* Duplicate the original method */
	EVP_PKEY_meth_copy(pkcs11_ed25519_method, orig_ed25519_method);

	/* Override selected ED25519 method callbacks with PKCS#11 implementations */
	EVP_PKEY_meth_set_sign(pkcs11_ed25519_method, NULL, pkcs11_eddsa_pmeth_sign);
	EVP_PKEY_meth_set_digestsign(pkcs11_ed25519_method, pkcs11_pkey_ed25519_digestsign);
	EVP_PKEY_meth_set_ctrl(pkcs11_ed25519_method, pkcs11_eddsa_pmeth_ctrl, NULL);

	/* Register the method globally */
	if (!EVP_PKEY_meth_add0(pkcs11_ed25519_method)) {
		EVP_PKEY_meth_free(pkcs11_ed25519_method);
		pkcs11_ed25519_method = NULL;
		return 0;
	}
	return 1;
}

/* Global initialize ED448 EVP_PKEY_METHOD */
static int pkcs11_ed448_method_new(void)
{
	int orig_id, orig_flags;

	if (pkcs11_ed448_method)
		return 1; /* EVP_PKEY_ED448 method already initialized */

	orig_ed448_method = EVP_PKEY_meth_find(EVP_PKEY_ED448);
	if (!orig_ed448_method)
		return 0;

	EVP_PKEY_meth_get0_info(&orig_id, &orig_flags, orig_ed448_method);
	if (orig_id != EVP_PKEY_ED448 || !(orig_flags & EVP_PKEY_FLAG_SIGCTX_CUSTOM))
		return 0;

	/* The digestsign() method is used to generate a signature in a one-shot mode */
	EVP_PKEY_meth_get_digestsign(orig_ed448_method, &orig_ed448_digestsign);
	if (!orig_ed448_digestsign)
		return 0;

	/* Don't assume any digest related defaults */
	pkcs11_ed448_method = EVP_PKEY_meth_new(EVP_PKEY_ED448, EVP_PKEY_FLAG_SIGCTX_CUSTOM);
	if (!pkcs11_ed448_method)
		return 0;

	/* Duplicate the original method */
	EVP_PKEY_meth_copy(pkcs11_ed448_method, orig_ed448_method);

	/* Override selected ED448 method callbacks with PKCS#11 implementations */
	EVP_PKEY_meth_set_sign(pkcs11_ed448_method, NULL, pkcs11_eddsa_pmeth_sign);
	EVP_PKEY_meth_set_digestsign(pkcs11_ed448_method, pkcs11_pkey_ed448_digestsign);
	EVP_PKEY_meth_set_ctrl(pkcs11_ed448_method, pkcs11_eddsa_pmeth_ctrl, NULL);

	/* Register the method globally */
	if (!EVP_PKEY_meth_add0(pkcs11_ed448_method)) {
		EVP_PKEY_meth_free(pkcs11_ed448_method);
		pkcs11_ed448_method = NULL;
		return 0;
	}
	return 1;
}

void pkcs11_ed25519_method_free(void)
{
	if (pkcs11_ed25519_method) {
		free_pkey_ex_index();
		/* Remove an EVP_PKEY_METHOD object added by EVP_PKEY_meth_add0() */
		EVP_PKEY_meth_remove(pkcs11_ed25519_method);
		EVP_PKEY_meth_free(pkcs11_ed25519_method);
		pkcs11_ed25519_method = NULL;
	}
}

void pkcs11_ed448_method_free(void)
{
	if (pkcs11_ed448_method) {
		free_pkey_ex_index();
		EVP_PKEY_meth_remove(pkcs11_ed448_method);
		EVP_PKEY_meth_free(pkcs11_ed448_method);
		pkcs11_ed448_method = NULL;
	}
}

void pkcs11_ed_key_method_free(void)
{
	pkcs11_ed25519_method_free();
	pkcs11_ed448_method_free();
}


/******************************************************************************/
/* XDH methods                                                                */
/******************************************************************************/

static int pkcs11_xdh_pmeth_derive(EVP_PKEY_CTX *ctx, unsigned char *secret,
	size_t *secretlen)
{
	EVP_PKEY *pkey, *peerkey;
	PKCS11_OBJECT_private *key;
	PKCS11_SLOT_private *slot;
	CK_SESSION_HANDLE session;
	unsigned char peer_pub[56];
	size_t peer_pub_len = sizeof(peer_pub);
	size_t expected_len;
	int type;

	if (ctx == NULL || secretlen == NULL)
		return 0;

	pkey = EVP_PKEY_CTX_get0_pkey(ctx);
	if (!pkey)
		return 0;

	key = pkcs11_get_ex_data_pkey(pkey);
	if (!key)
		return -1;

	slot = key->slot;
	if (!slot)
		return -1;

	if (pkcs11_get_session(slot, 0, &session))
		return -1;

	pkcs11_put_session(slot, session);

	type = EVP_PKEY_id(pkey);
	switch (type) {
	case EVP_PKEY_X25519:
		expected_len = 32;
		break;
	case EVP_PKEY_X448:
		expected_len = 56;
		break;
	default:
		return -1; /* not an XDH key handled by this wrapper */
	}

	if (secret == NULL) {
		*secretlen = expected_len;
		return 1;
	}

	if (*secretlen < expected_len) {
		*secretlen = expected_len;
		return 0;
	}

	peerkey = EVP_PKEY_CTX_get0_peerkey(ctx);
	if (peerkey == NULL)
		return 0;

	if (EVP_PKEY_get_raw_public_key(peerkey, NULL, &peer_pub_len) <= 0)
		return 0;

	if (peer_pub_len != expected_len)
		return 0;

	if (EVP_PKEY_get_raw_public_key(peerkey, peer_pub, &peer_pub_len) <= 0)
		return 0;

	if (!pkcs11_evp_pkey_xdh_derive(key, peer_pub, peer_pub_len,
			secret, secretlen))
		return 0;

	return 1;
}

static int pkcs11_pkey_x25519_derive(EVP_PKEY_CTX *ctx, unsigned char *secret,
	size_t *secretlen)
{
	int ret;

	ret = pkcs11_xdh_pmeth_derive(ctx, secret, secretlen);
	if (ret < 0)
		/* assume a foreign key */
		ret = (*orig_x25519_derive)(ctx, secret, secretlen);
	return ret;
}

static int pkcs11_pkey_x448_derive(EVP_PKEY_CTX *ctx, unsigned char *secret,
	size_t *secretlen)
{
	int ret;

	ret = pkcs11_xdh_pmeth_derive(ctx, secret, secretlen);
	if (ret < 0)
		ret = (*orig_x448_derive)(ctx, secret, secretlen);
	return ret;
}

/* Global initialize X25519 EVP_PKEY_METHOD */
static int pkcs11_x25519_method_new(void)
{
	int orig_id, orig_flags;

	if (pkcs11_x25519_method)
		return 1; /* EVP_PKEY_X25519 method already initialized */

	orig_x25519_method = EVP_PKEY_meth_find(EVP_PKEY_X25519);
	if (!orig_x25519_method)
		return 0;

	EVP_PKEY_meth_get0_info(&orig_id, &orig_flags, orig_x25519_method);
	if (orig_id != EVP_PKEY_X25519 || !(orig_flags & EVP_PKEY_FLAG_SIGCTX_CUSTOM))
		return 0;

	EVP_PKEY_meth_get_derive(orig_x25519_method, NULL, &orig_x25519_derive);
	if (!orig_x25519_derive)
		return 0;

	pkcs11_x25519_method = EVP_PKEY_meth_new(EVP_PKEY_X25519, EVP_PKEY_FLAG_AUTOARGLEN);
	if (!pkcs11_x25519_method)
		return 0;

	/* Duplicate the original method */
	EVP_PKEY_meth_copy(pkcs11_x25519_method, orig_x25519_method);

	/* Override selected X25519 method callbacks with PKCS#11 implementations */
	EVP_PKEY_meth_set_derive(pkcs11_x25519_method, NULL, pkcs11_pkey_x25519_derive);

	/* Register the method globally */
	if (!EVP_PKEY_meth_add0(pkcs11_x25519_method)) {
		EVP_PKEY_meth_free(pkcs11_x25519_method);
		pkcs11_x25519_method = NULL;
		return 0;
	}
	return 1;
}

/* Global initialize X448 EVP_PKEY_METHOD */
static int pkcs11_x448_method_new(void)
{
	int orig_id, orig_flags;

	if (pkcs11_x448_method)
		return 1; /* EVP_PKEY_X448 method already initialized */

	orig_x448_method = EVP_PKEY_meth_find(EVP_PKEY_X448);
	if (!orig_x448_method)
		return 0;

	EVP_PKEY_meth_get0_info(&orig_id, &orig_flags, orig_x448_method);
	if (orig_id != EVP_PKEY_X448 || !(orig_flags & EVP_PKEY_FLAG_SIGCTX_CUSTOM))
		return 0;

	EVP_PKEY_meth_get_derive(orig_x448_method, NULL, &orig_x448_derive);
	if (!orig_x448_derive)
		return 0;

	pkcs11_x448_method = EVP_PKEY_meth_new(EVP_PKEY_X448, EVP_PKEY_FLAG_AUTOARGLEN);
	if (!pkcs11_x448_method)
		return 0;

	/* Duplicate the original method */
	EVP_PKEY_meth_copy(pkcs11_x448_method, orig_x448_method);

	/* Override selected X448 method callbacks with PKCS#11 implementations */
	EVP_PKEY_meth_set_derive(pkcs11_x448_method, NULL, pkcs11_pkey_x448_derive);

	/* Register the method globally */
	if (!EVP_PKEY_meth_add0(pkcs11_x448_method)) {
		EVP_PKEY_meth_free(pkcs11_x448_method);
		pkcs11_x448_method = NULL;
		return 0;
	}
	return 1;
}

void pkcs11_x25519_method_free(void)
{
	if (pkcs11_x25519_method) {
		free_pkey_ex_index();
		/* Remove an EVP_PKEY_METHOD object added by EVP_PKEY_meth_add0() */
		EVP_PKEY_meth_remove(pkcs11_x25519_method);
		EVP_PKEY_meth_free(pkcs11_x25519_method);
		pkcs11_x25519_method = NULL;
	}
}

void pkcs11_x448_method_free(void)
{
	if (pkcs11_x448_method) {
		free_pkey_ex_index();
		EVP_PKEY_meth_remove(pkcs11_x448_method);
		EVP_PKEY_meth_free(pkcs11_x448_method);
		pkcs11_x448_method = NULL;
	}
}

void pkcs11_xdh_key_method_free(void)
{
	pkcs11_x25519_method_free();
	pkcs11_x448_method_free();
}

#endif /* OPENSSL_VERSION_NUMBER < 0x40000000L */


/******************************************************************************/
/* Get EVP_PKEY from raw public key bytes                                     */
/******************************************************************************/

/*
 * Decode a DER OCTET STRING and return its contents.
 * If decoding fails, duplicate the input buffer unchanged.
 * Returns 1 on success, 0 on error.
 */
static int strip_der_octet_string_alloc(const unsigned char *in, size_t inlen,
	unsigned char **out, size_t *outlen)
{
	const unsigned char *p = in;
	unsigned char *buf;
	ASN1_OCTET_STRING *os = NULL;

	if (out == NULL || outlen == NULL || in == NULL || inlen == 0)
		return 0;

	*out = NULL;
	*outlen = 0;

	/* For EdDSA (RFC8032) the CKA_EC_POINT attribute may be
	 * encoded as a DER OCTET STRING */
	os = d2i_ASN1_OCTET_STRING(NULL, &p, (long)inlen);
	if (os != NULL && p == in + inlen) {
		const unsigned char *data = ASN1_STRING_get0_data(os);
		int len = ASN1_STRING_length(os);
		
		buf = OPENSSL_malloc((size_t)len);
		if (buf == NULL) {
			ASN1_OCTET_STRING_free(os);
			return 0;
		}
		memcpy(buf, data, (size_t)len);
		*out = buf;
		*outlen = (size_t)len;
		ASN1_OCTET_STRING_free(os);
		return 1;
	}
	ASN1_OCTET_STRING_free(os);

	/* Not DER OCTET STRING -> copy as-is */
	buf = OPENSSL_malloc(inlen);
	if (buf == NULL)
		return 0;
	memcpy(buf, in, inlen);
	*out = buf;
	*outlen = inlen;
	return 1;
}

/*
 * Parse a DER X.509 certificate and return raw Ed25519, Ed448, X25519 or X448
 * public key bytes.
 * Returns 1 on success, 0 on error.
 */
static int extract_ecx_raw_from_x509_der(const unsigned char *der, size_t derlen,
	unsigned char **raw, size_t *rawlen)
{
	const unsigned char *p = der;
	X509 *cert = NULL;
	EVP_PKEY *pkey = NULL;
	unsigned char *buf = NULL;
	size_t len = 0;
	int type, ok = 0;

	if (raw == NULL || rawlen == NULL || der == NULL || derlen == 0)
		return 0;

	*raw = NULL;
	*rawlen = 0;

	cert = d2i_X509(NULL, &p, (long)derlen);
	if (cert == NULL)
		goto end;

	pkey = X509_get_pubkey(cert);
	if (pkey == NULL)
		goto end;

	/* Ensure it is an ECX key with raw public-key support. */
	type = EVP_PKEY_id(pkey);
	if (type != EVP_PKEY_ED25519 && type != EVP_PKEY_ED448 &&
			type != EVP_PKEY_X25519 && type != EVP_PKEY_X448)
		goto end;


	/* Query required size */
	if (EVP_PKEY_get_raw_public_key(pkey, NULL, &len) != 1 || len == 0)
		goto end;

	buf = OPENSSL_malloc(len);
	if (buf == NULL)
		goto end;

	/* Fetch raw public key bytes */
	if (EVP_PKEY_get_raw_public_key(pkey, buf, &len) != 1) {
		OPENSSL_free(buf);
		buf = NULL;
		goto end;
	}

	*raw = buf;
	*rawlen = len;
	buf = NULL;
	ok = 1;

end:
	OPENSSL_free(buf);
	EVP_PKEY_free(pkey);
	X509_free(cert);
	return ok;
}


/*
 * Extract raw EdDSA public key bytes from a CKO_PUBLIC_KEY object using CKA_EC_POINT.
 * Returns 1 on success, 0 on failure.
 */
static int extract_pub_from_public_key_obj(PKCS11_CTX_private *ctx, CK_SESSION_HANDLE session,
	CK_OBJECT_HANDLE obj, unsigned char **raw, size_t *rawlen)
{
	unsigned char *ecpt = NULL;
	size_t ecptlen = 0;
	int ok = 0;

	if (pkcs11_getattr_alloc(ctx, session, obj, CKA_EC_POINT, &ecpt, &ecptlen))
		return 0;

	ok = strip_der_octet_string_alloc(ecpt, ecptlen, raw, rawlen);
	OPENSSL_free(ecpt);
	return ok;
}

/*
 * Extract raw ECX public key bytes from a CKO_CERTIFICATE object.
 * The certificate is read from CKA_VALUE (DER-encoded X.509) and
 * the public key is obtained via X.509 parsing.
 * Returns 1 on success, 0 on failure.
 */
static int extract_pub_from_cert_obj(PKCS11_CTX_private *ctx, CK_SESSION_HANDLE session,
	CK_OBJECT_HANDLE obj, unsigned char **raw, size_t *rawlen)
{
	unsigned char *der = NULL;
	size_t derlen = 0;
	int ok = 0;

	if (pkcs11_getattr_alloc(ctx, session, obj, CKA_VALUE, &der, &derlen))
		return 0;

	ok = extract_ecx_raw_from_x509_der(der, derlen, raw, rawlen);
	OPENSSL_free(der);
	return ok;
}

/*
 * Select an object that can provide public key material.
 *
 * For a private key object, try to locate a matching CKO_PUBLIC_KEY
 * (same CKA_ID). If not found, fall back to CKO_CERTIFICATE.
 *
 * On success, returns a PKCS11_OBJECT_private pointer.
 * If a new object is returned, *needs_free is set to 1 and the caller
 * must free it with pkcs11_object_free().
 *
 * Returns NULL on failure.
 */
static PKCS11_OBJECT_private *pkcs11_choose_public_source(PKCS11_OBJECT_private *key,
	CK_SESSION_HANDLE session, int *needs_free)
{
	PKCS11_OBJECT_private *obj;

	*needs_free = 0;

	if (key->object_class != CKO_PRIVATE_KEY)
		return key;

	obj = pkcs11_object_from_object(key, session, CKO_PUBLIC_KEY);
	if (obj != NULL && obj->object != CK_INVALID_HANDLE) {
		*needs_free = 1;
		return obj;
	}
	if (obj != NULL)
		pkcs11_object_free(obj);

	obj = pkcs11_object_from_object(key, session, CKO_CERTIFICATE);
	if (obj != NULL && obj->object != CK_INVALID_HANDLE) {
		*needs_free = 1;
		return obj;
	}
	if (obj != NULL)
		pkcs11_object_free(obj);

	return NULL;
}

/*
 * Retrieve raw EdDSA public key bytes.
 *
 * Preference order:
 *   1. CKO_PUBLIC_KEY  -> CKA_EC_POINT
 *   2. CKO_CERTIFICATE -> CKA_VALUE (DER) + X.509 parsing
 *
 * The returned buffer is allocated with OPENSSL_malloc()
 * and must be freed by the caller.
 *
 * Returns 0 on success, -1 on failure.
 */
static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key,
	unsigned char **raw, size_t *rawlen)
{
	PKCS11_SLOT_private *slot;
	PKCS11_CTX_private *ctx;
	CK_SESSION_HANDLE session = CK_INVALID_HANDLE;
	PKCS11_OBJECT_private *obj = NULL;
	int obj_needs_free = 0, ok = 0;

	if (key == NULL || key->slot == NULL || raw == NULL || rawlen == NULL)
		return -1;

	*raw = NULL;
	*rawlen = 0;
	slot = key->slot;
	ctx = slot->ctx;

	if (pkcs11_get_session(slot, 0, &session))
		return -1;

	obj = pkcs11_choose_public_source(key, session, &obj_needs_free);
	if (obj == NULL || obj->object == CK_INVALID_HANDLE)
		goto end;

	switch (obj->object_class) {
	case CKO_PUBLIC_KEY:
		ok = extract_pub_from_public_key_obj(ctx, session, obj->object, raw, rawlen);
		break;
	case CKO_CERTIFICATE:
		ok = extract_pub_from_cert_obj(ctx, session, obj->object, raw, rawlen);
		break;
	default:
		ok = 0;
		break;
	}

end:
	pkcs11_put_session(slot, session);

	if (!ok) {
		OPENSSL_free(*raw);
		*raw = NULL;
		*rawlen = 0;
	}
	if (obj_needs_free && obj != NULL)
		pkcs11_object_free(obj);

	return ok ? 0 : -1;
}

static EVP_PKEY *pkcs11_get_evp_key_ed25519(PKCS11_OBJECT_private *key)
{
	EVP_PKEY *pkey = NULL;
	unsigned char *raw = NULL;
	size_t rawlen = 0;

	/* Retrieve the public key in raw format from PKCS#11 */
	if (pkcs11_get_raw_public_key(key, &raw, &rawlen) < 0)
		return NULL;

	/* Build a EVP_PKEY from the raw public key, used only as software public key */
	pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "ED25519", NULL, raw, rawlen);
	OPENSSL_free(raw);

	if (!pkey)
		return NULL;

#if OPENSSL_VERSION_NUMBER < 0x40000000L
	if (key->object_class == CKO_PRIVATE_KEY) {
		if ((key->slot->ctx->flags & PKCS11_FLAG_NO_METHODS) == 0) {
			/* global initialize ED25519 EVP_PKEY_METHOD */
			if (!pkcs11_ed25519_method_new()) {
				EVP_PKEY_free(pkey);
				return NULL;
			}
			/* creates a new EVP_PKEY object which requires its own key object reference */
			key = pkcs11_object_ref(key);
			alloc_pkey_ex_index();
			pkcs11_set_ex_data_pkey(pkey, key);
			atexit(pkcs11_ed25519_method_free);
		}
	}
#endif /* OPENSSL_VERSION_NUMBER < 0x40000000L */
	return pkey;
}

static EVP_PKEY *pkcs11_get_evp_key_ed448(PKCS11_OBJECT_private *key)
{
	EVP_PKEY *pkey = NULL;
	unsigned char *raw = NULL;
	size_t rawlen = 0;

	/* Retrieve the public key in raw format from PKCS#11 */
	if (pkcs11_get_raw_public_key(key, &raw, &rawlen) < 0)
		return NULL;

	/* Build a EVP_PKEY from the raw public key, used only as software public key */
	pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "ED448", NULL, raw, rawlen);
	OPENSSL_free(raw);

	if (!pkey)
		return NULL;

#if OPENSSL_VERSION_NUMBER < 0x40000000L
	if (key->object_class == CKO_PRIVATE_KEY) {
		if ((key->slot->ctx->flags & PKCS11_FLAG_NO_METHODS) == 0) {
			/* global initialize ED448 EVP_PKEY_METHOD */
			if (!pkcs11_ed448_method_new()) {
				EVP_PKEY_free(pkey);
				return NULL;
			}
			/* create a new EVP_PKEY object which requires its own key object reference */
			key = pkcs11_object_ref(key);
			alloc_pkey_ex_index();
			pkcs11_set_ex_data_pkey(pkey, key);
			atexit(pkcs11_ed25519_method_free);
		}
	}
#endif /* OPENSSL_VERSION_NUMBER < 0x40000000L */
	return pkey;
}

static EVP_PKEY *pkcs11_get_evp_key_x25519(PKCS11_OBJECT_private *key)
{
	EVP_PKEY *pkey = NULL;
	unsigned char *raw = NULL;
	size_t rawlen = 0;

	/* Retrieve the public key in raw format from PKCS#11 */
	if (pkcs11_get_raw_public_key(key, &raw, &rawlen) < 0)
		return NULL;

	/* Build a EVP_PKEY from the raw public key, used only as software public key */
	pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "X25519", NULL, raw, rawlen);
	OPENSSL_free(raw);

	if (!pkey)
		return NULL;

#if OPENSSL_VERSION_NUMBER < 0x40000000L
	if (key->object_class == CKO_PRIVATE_KEY) {
		if ((key->slot->ctx->flags & PKCS11_FLAG_NO_METHODS) == 0) {
			/* global initialize X25519 EVP_PKEY_METHOD */
			if (!pkcs11_x25519_method_new()) {
				EVP_PKEY_free(pkey);
				return NULL;
			}
			/* creates a new EVP_PKEY object which requires its own key object reference */
			key = pkcs11_object_ref(key);
			alloc_pkey_ex_index();
			pkcs11_set_ex_data_pkey(pkey, key);
			atexit(pkcs11_x25519_method_free);
		}
	}
#endif /* OPENSSL_VERSION_NUMBER < 0x40000000L */
	return pkey;
}

static EVP_PKEY *pkcs11_get_evp_key_x448(PKCS11_OBJECT_private *key)
{
	EVP_PKEY *pkey = NULL;
	unsigned char *raw = NULL;
	size_t rawlen = 0;

	/* Retrieve the public key in raw format from PKCS#11 */
	if (pkcs11_get_raw_public_key(key, &raw, &rawlen) < 0)
		return NULL;

	/* Build a EVP_PKEY from the raw public key, used only as software public key */
	pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "X448", NULL, raw, rawlen);
	OPENSSL_free(raw);

	if (!pkey)
		return NULL;

#if OPENSSL_VERSION_NUMBER < 0x40000000L
	if (key->object_class == CKO_PRIVATE_KEY) {
		if ((key->slot->ctx->flags & PKCS11_FLAG_NO_METHODS) == 0) {
			/* global initialize X448 EVP_PKEY_METHOD */
			if (!pkcs11_x448_method_new()) {
				EVP_PKEY_free(pkey);
				return NULL;
			}
			/* create a new EVP_PKEY object which requires its own key object reference */
			key = pkcs11_object_ref(key);
			alloc_pkey_ex_index();
			pkcs11_set_ex_data_pkey(pkey, key);
			atexit(pkcs11_x448_method_free);
		}
	}
#endif /* OPENSSL_VERSION_NUMBER < 0x40000000L */
	return pkey;
}


PKCS11_OBJECT_ops pkcs11_ed25519_ops = {
	EVP_PKEY_ED25519,
	pkcs11_get_evp_key_ed25519,
};

PKCS11_OBJECT_ops pkcs11_ed448_ops = {
	EVP_PKEY_ED448,
	pkcs11_get_evp_key_ed448,
};

PKCS11_OBJECT_ops pkcs11_x25519_ops = {
	EVP_PKEY_X25519,
	pkcs11_get_evp_key_x25519,
};

PKCS11_OBJECT_ops pkcs11_x448_ops = {
	EVP_PKEY_X448,
	pkcs11_get_evp_key_x448,
};

#else /* !defined(OPENSSL_NO_ECX) && OPENSSL_VERSION_NUMBER >= 0x30000000L */

/*
 * EdDSA (Ed25519/Ed448) and XDH (X5519/X448) support are not available:
 * - either OpenSSL was built without ECX (no-ecx), or
 * - OpenSSL version is older than 3.0.
 *
 * Provide stubs so engine_pkcs11 can still be built without
 * requiring conditional changes elsewhere.
 */
#warning "EdDSA support not built with libp11"

#endif /* !defined(OPENSSL_NO_ECX) && OPENSSL_VERSION_NUMBER >= 0x30000000L */
