Edit

IABSD.fr/src/lib/libcrypto/ec/ecx_methods.c

Branch :

  • Show log

    Commit

  • Author : tb
    Date : 2025-05-10 05:54:38
    Hash : 27854fe4
    Message : Use err_local.h rather than err.h in most places ok jsing

  • lib/libcrypto/ec/ecx_methods.c
  • /*	$OpenBSD: ecx_methods.c,v 1.15 2025/05/10 05:54:38 tb Exp $ */
    /*
     * Copyright (c) 2022 Joel Sing <jsing@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <string.h>
    
    #include <openssl/cms.h>
    #include <openssl/curve25519.h>
    #include <openssl/ec.h>
    #include <openssl/evp.h>
    #include <openssl/x509.h>
    
    #include "asn1_local.h"
    #include "bytestring.h"
    #include "curve25519_internal.h"
    #include "err_local.h"
    #include "evp_local.h"
    #include "x509_local.h"
    
    /*
     * EVP PKEY and PKEY ASN.1 methods Ed25519 and X25519.
     *
     * RFC 7748 - Elliptic Curves for Security.
     * RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA).
     */
    
    #define ED25519_BITS		253
    #define ED25519_SECURITY_BITS	128
    #define ED25519_SIG_SIZE	64
    
    #define X25519_BITS		253
    #define X25519_SECURITY_BITS	128
    
    static int
    ecx_key_len(int nid)
    {
    	switch (nid) {
    	case NID_ED25519:
    		return ED25519_KEYLEN;
    	case NID_X25519:
    		return X25519_KEYLEN;
    	}
    
    	return 0;
    }
    
    static struct ecx_key_st *
    ecx_key_new(int nid)
    {
    	struct ecx_key_st *ecx_key;
    	int key_len;
    
    	if ((key_len = ecx_key_len(nid)) == 0)
    		return NULL;
    
    	if ((ecx_key = calloc(1, sizeof(*ecx_key))) == NULL)
    		return NULL;
    
    	ecx_key->nid = nid;
    	ecx_key->key_len = key_len;
    
    	return ecx_key;
    }
    
    static void
    ecx_key_clear(struct ecx_key_st *ecx_key)
    {
    	freezero(ecx_key->priv_key, ecx_key->priv_key_len);
    	ecx_key->priv_key = NULL;
    	ecx_key->priv_key_len = 0;
    
    	freezero(ecx_key->pub_key, ecx_key->pub_key_len);
    	ecx_key->pub_key = NULL;
    	ecx_key->pub_key_len = 0;
    }
    
    static void
    ecx_key_free(struct ecx_key_st *ecx_key)
    {
    	if (ecx_key == NULL)
    		return;
    
    	ecx_key_clear(ecx_key);
    
    	freezero(ecx_key, sizeof(*ecx_key));
    }
    
    static int
    ecx_key_generate(struct ecx_key_st *ecx_key)
    {
    	uint8_t *pub_key = NULL, *priv_key = NULL;
    	int ret = 0;
    
    	ecx_key_clear(ecx_key);
    
    	if ((pub_key = calloc(1, ecx_key->key_len)) == NULL)
    		goto err;
    	if ((priv_key = calloc(1, ecx_key->key_len)) == NULL)
    		goto err;
    
    	switch (ecx_key->nid) {
    	case NID_ED25519:
    		ED25519_keypair(pub_key, priv_key);
    		break;
    	case NID_X25519:
    		X25519_keypair(pub_key, priv_key);
    		break;
    	default:
    		goto err;
    	}
    
    	ecx_key->priv_key = priv_key;
    	ecx_key->priv_key_len = ecx_key->key_len;
    	priv_key = NULL;
    
    	ecx_key->pub_key = pub_key;
    	ecx_key->pub_key_len = ecx_key->key_len;
    	pub_key = NULL;
    
    	ret = 1;
    
     err:
    	freezero(pub_key, ecx_key->key_len);
    	freezero(priv_key, ecx_key->key_len);
    
    	return ret;
    }
    
    static int
    ecx_key_set_priv(struct ecx_key_st *ecx_key, const uint8_t *priv_key,
        size_t priv_key_len)
    {
    	uint8_t *pub_key = NULL;
    	CBS cbs;
    
    	ecx_key_clear(ecx_key);
    
    	if (priv_key_len != ecx_key->key_len)
    		goto err;
    
    	if ((pub_key = calloc(1, ecx_key->key_len)) == NULL)
    		goto err;
    
    	switch (ecx_key->nid) {
    	case NID_ED25519:
    		ED25519_public_from_private(pub_key, priv_key);
    		break;
    	case NID_X25519:
    		X25519_public_from_private(pub_key, priv_key);
    		break;
    	default:
    		goto err;
    	}
    
    	CBS_init(&cbs, priv_key, priv_key_len);
    	if (!CBS_stow(&cbs, &ecx_key->priv_key, &ecx_key->priv_key_len))
    		goto err;
    
    	ecx_key->pub_key = pub_key;
    	ecx_key->pub_key_len = ecx_key->key_len;
    	pub_key = NULL;
    
     err:
    	freezero(pub_key, ecx_key->key_len);
    
    	return 1;
    }
    
    static int
    ecx_key_set_pub(struct ecx_key_st *ecx_key, const uint8_t *pub_key,
        size_t pub_key_len)
    {
    	CBS cbs;
    
    	ecx_key_clear(ecx_key);
    
    	if (pub_key_len != ecx_key->key_len)
    		return 0;
    
    	CBS_init(&cbs, pub_key, pub_key_len);
    	if (!CBS_stow(&cbs, &ecx_key->pub_key, &ecx_key->pub_key_len))
    		return 0;
    
    	return 1;
    }
    
    static int
    ecx_pub_decode(EVP_PKEY *pkey, X509_PUBKEY *xpubkey)
    {
    	struct ecx_key_st *ecx_key = NULL;
    	X509_ALGOR *algor;
    	int algor_type;
    	const uint8_t *param;
    	int param_len;
    	int ret = 0;
    
    	if (!X509_PUBKEY_get0_param(NULL, &param, &param_len, &algor, xpubkey))
    		goto err;
    
    	/* Ensure that parameters have not been specified in the encoding. */
    	if (algor != NULL) {
    		X509_ALGOR_get0(NULL, &algor_type, NULL, algor);
    		if (algor_type != V_ASN1_UNDEF) {
    			ECerror(EC_R_INVALID_ENCODING);
    			goto err;
    		}
    	}
    
    	if (param == NULL || param_len != ecx_key_len(pkey->ameth->pkey_id)) {
    		ECerror(EC_R_INVALID_ENCODING);
    		goto err;
    	}
    
    	if ((ecx_key = ecx_key_new(pkey->ameth->pkey_id)) == NULL)
    		goto err;
    	if (!ecx_key_set_pub(ecx_key, param, param_len))
    		goto err;
    	if (!EVP_PKEY_assign(pkey, pkey->ameth->pkey_id, ecx_key))
    		goto err;
    	ecx_key = NULL;
    
    	ret = 1;
    
     err:
    	ecx_key_free(ecx_key);
    
    	return ret;
    }
    
    static int
    ecx_pub_encode(X509_PUBKEY *xpubkey, const EVP_PKEY *pkey)
    {
    	const struct ecx_key_st *ecx_key = pkey->pkey.ecx;
    	uint8_t *pub_key = NULL;
    	size_t pub_key_len = 0;
    	ASN1_OBJECT *aobj;
    	CBS cbs;
    	int ret = 0;
    
    	if (ecx_key == NULL) {
    		ECerror(EC_R_INVALID_KEY);
    		goto err;
    	}
    
    	if (ecx_key->pub_key_len != ecx_key->key_len)
    		goto err;
    
    	if ((aobj = OBJ_nid2obj(pkey->ameth->pkey_id)) == NULL)
    		goto err;
    
    	CBS_init(&cbs, ecx_key->pub_key, ecx_key->pub_key_len);
    	if (!CBS_stow(&cbs, &pub_key, &pub_key_len))
    		goto err;
    
    	if (!X509_PUBKEY_set0_param(xpubkey, aobj, V_ASN1_UNDEF, NULL,
    	    pub_key, pub_key_len))
    		goto err;
    
    	pub_key = NULL;
    	pub_key_len = 0;
    
    	ret = 1;
    
     err:
    	free(pub_key);
    
    	return ret;
    }
    
    static int
    ecx_pub_cmp(const EVP_PKEY *pkey1, const EVP_PKEY *pkey2)
    {
    	if (pkey1->pkey.ecx == NULL || pkey1->pkey.ecx->pub_key == NULL)
    		return -2;
    	if (pkey2->pkey.ecx == NULL || pkey2->pkey.ecx->pub_key == NULL)
    		return -2;
    	if (pkey1->pkey.ecx->pub_key_len != pkey2->pkey.ecx->pub_key_len)
    		return -2;
    
    	return timingsafe_memcmp(pkey1->pkey.ecx->pub_key, pkey2->pkey.ecx->pub_key,
    	    pkey1->pkey.ecx->pub_key_len) == 0;
    }
    
    /* Reimplementation of ASN1_buf_print() that adds a secondary indent of 4. */
    static int
    ecx_buf_print(BIO *bio, const uint8_t *buf, size_t buf_len, int indent)
    {
    	uint8_t u8;
    	size_t octets = 0;
    	const char *sep = ":", *nl = "";
    	CBS cbs;
    
    	if (indent > 60)
    		indent = 60;
    	indent += 4;
    	if (indent < 0)
    		indent = 0;
    
    	CBS_init(&cbs, buf, buf_len);
    	while (CBS_len(&cbs) > 0) {
    		if (!CBS_get_u8(&cbs, &u8))
    			return 0;
    		if (octets++ % 15 == 0) {
    			if (BIO_printf(bio, "%s%*s", nl, indent, "") < 0)
    				return 0;
    			nl = "\n";
    		}
    		if (CBS_len(&cbs) == 0)
    			sep = "";
    		if (BIO_printf(bio, "%02x%s", u8, sep) <= 0)
    			return 0;
    	}
    
    	if (BIO_printf(bio, "\n") <= 0)
    		return 0;
    
    	return 1;
    }
    
    static int
    ecx_pub_print(BIO *bio, const EVP_PKEY *pkey, int indent, ASN1_PCTX *ctx)
    {
    	struct ecx_key_st *ecx_key = pkey->pkey.ecx;
    	const char *name;
    
    	if ((name = OBJ_nid2ln(pkey->ameth->pkey_id)) == NULL)
    		return 0;
    
    	if (ecx_key == NULL || ecx_key->pub_key == NULL)
    		return BIO_printf(bio, "%*s<INVALID PUBLIC KEY>\n",
    		    indent, "") > 0;
    
    	if (BIO_printf(bio, "%*s%s Public-Key:\n", indent, "", name) <= 0)
    		return 0;
    	if (BIO_printf(bio, "%*spub:\n", indent, "") <= 0)
    		return 0;
    	if (!ecx_buf_print(bio, ecx_key->pub_key, ecx_key->pub_key_len, indent))
    		return 0;
    
    	return 1;
    }
    
    static int
    ecx_priv_decode(EVP_PKEY *pkey, const PKCS8_PRIV_KEY_INFO *p8pki)
    {
    	struct ecx_key_st *ecx_key = NULL;
    	ASN1_OCTET_STRING *aos = NULL;
    	const X509_ALGOR *algor;
    	int algor_type;
    	const uint8_t *param;
    	int param_len;
    	int ret = 0;
    
    	if (!PKCS8_pkey_get0(NULL, &param, &param_len, &algor, p8pki))
    		goto err;
    	if ((aos = d2i_ASN1_OCTET_STRING(NULL, &param, param_len)) == NULL)
    		goto err;
    
    	/* Ensure that parameters have not been specified in the encoding. */
    	if (algor != NULL) {
    		X509_ALGOR_get0(NULL, &algor_type, NULL, algor);
    		if (algor_type != V_ASN1_UNDEF) {
    			ECerror(EC_R_INVALID_ENCODING);
    			goto err;
    		}
    	}
    
    	if (ASN1_STRING_get0_data(aos) == NULL ||
    	    ASN1_STRING_length(aos) != ecx_key_len(pkey->ameth->pkey_id)) {
    		ECerror(EC_R_INVALID_ENCODING);
    		goto err;
    	}
    
    	if ((ecx_key = ecx_key_new(pkey->ameth->pkey_id)) == NULL)
    		goto err;
    	if (!ecx_key_set_priv(ecx_key, ASN1_STRING_get0_data(aos),
    	    ASN1_STRING_length(aos)))
    		goto err;
    	if (!EVP_PKEY_assign(pkey, pkey->ameth->pkey_id, ecx_key))
    		goto err;
    	ecx_key = NULL;
    
    	ret = 1;
    
     err:
    	ASN1_OCTET_STRING_free(aos);
    	ecx_key_free(ecx_key);
    
    	return ret;
    }
    
    static int
    ecx_priv_encode(PKCS8_PRIV_KEY_INFO *p8pki, const EVP_PKEY *pkey)
    {
    	struct ecx_key_st *ecx_key = pkey->pkey.ecx;
    	ASN1_OCTET_STRING *aos = NULL;
    	ASN1_OBJECT *aobj;
    	uint8_t *der = NULL;
    	int der_len = 0;
    	int ret = 0;
    
    	if (ecx_key == NULL || ecx_key->priv_key == NULL) {
    		ECerror(EC_R_INVALID_PRIVATE_KEY);
    		goto err;
    	}
    
    	if ((aobj = OBJ_nid2obj(pkey->ameth->pkey_id)) == NULL)
    		goto err;
    
    	if ((aos = ASN1_OCTET_STRING_new()) == NULL)
    		goto err;
    	if (!ASN1_OCTET_STRING_set(aos, ecx_key->priv_key,
    	    ecx_key->priv_key_len))
    		goto err;
    	if ((der_len = i2d_ASN1_OCTET_STRING(aos, &der)) < 0)
    		goto err;
    	if (!PKCS8_pkey_set0(p8pki, aobj, 0, V_ASN1_UNDEF, NULL, der, der_len))
    		goto err;
    
    	der = NULL;
    	der_len = 0;
    
    	ret = 1;
    
     err:
    	freezero(der, der_len);
    	ASN1_OCTET_STRING_free(aos);
    
    	return ret;
    }
    
    static int
    ecx_priv_print(BIO *bio, const EVP_PKEY *pkey, int indent, ASN1_PCTX *ctx)
    {
    	struct ecx_key_st *ecx_key = pkey->pkey.ecx;
    	const char *name;
    
    	if ((name = OBJ_nid2ln(pkey->ameth->pkey_id)) == NULL)
    		return 0;
    
    	if (ecx_key == NULL || ecx_key->priv_key == NULL)
    		return BIO_printf(bio, "%*s<INVALID PRIVATE KEY>\n",
    		    indent, "") > 0;
    
    	if (BIO_printf(bio, "%*s%s Private-Key:\n", indent, "", name) <= 0)
    		return 0;
    	if (BIO_printf(bio, "%*spriv:\n", indent, "") <= 0)
    		return 0;
    	if (!ecx_buf_print(bio, ecx_key->priv_key, ecx_key->priv_key_len, indent))
    		return 0;
    	if (BIO_printf(bio, "%*spub:\n", indent, "") <= 0)
    		return 0;
    	if (!ecx_buf_print(bio, ecx_key->pub_key, ecx_key->pub_key_len, indent))
    		return 0;
    
    	return 1;
    }
    
    static int
    ecx_size(const EVP_PKEY *pkey)
    {
    	return ecx_key_len(pkey->ameth->pkey_id);
    }
    
    static int
    ecx_sig_size(const EVP_PKEY *pkey)
    {
    	switch (pkey->ameth->pkey_id) {
    	case EVP_PKEY_ED25519:
    		return ED25519_SIG_SIZE;
    	}
    	return 0;
    }
    
    static int
    ecx_bits(const EVP_PKEY *pkey)
    {
    	switch (pkey->ameth->pkey_id) {
    	case EVP_PKEY_ED25519:
    		return ED25519_BITS;
    	case EVP_PKEY_X25519:
    		return X25519_BITS;
    	}
    	return 0;
    }
    
    static int
    ecx_security_bits(const EVP_PKEY *pkey)
    {
    	switch (pkey->ameth->pkey_id) {
    	case EVP_PKEY_ED25519:
    		return ED25519_SECURITY_BITS;
    	case EVP_PKEY_X25519:
    		return X25519_SECURITY_BITS;
    	}
    	return 0;
    }
    
    static int
    ecx_signature_info(const X509_ALGOR *algor, int *md_nid, int *pkey_nid,
        int *security_bits, uint32_t *flags)
    {
    	const ASN1_OBJECT *aobj;
    
    	X509_ALGOR_get0(&aobj, NULL, NULL, algor);
    	if (OBJ_obj2nid(aobj) != EVP_PKEY_ED25519)
    		return 0;
    
    	*md_nid = NID_undef;
    	*pkey_nid = NID_ED25519;
    	*security_bits = ED25519_SECURITY_BITS;
    	*flags = X509_SIG_INFO_TLS | X509_SIG_INFO_VALID;
    
    	return 1;
    }
    
    static int
    ecx_param_cmp(const EVP_PKEY *pkey1, const EVP_PKEY *pkey2)
    {
    	/* No parameters, so always equivalent. */
    	return 1;
    }
    
    static void
    ecx_free(EVP_PKEY *pkey)
    {
    	struct ecx_key_st *ecx_key = pkey->pkey.ecx;
    
    	ecx_key_free(ecx_key);
    }
    
    static int
    ecx_ctrl(EVP_PKEY *pkey, int op, long arg1, void *arg2)
    {
    	/* Not supported. */
    	return -2;
    }
    
    #ifndef OPENSSL_NO_CMS
    static int
    ecx_cms_sign_or_verify(EVP_PKEY *pkey, long verify, CMS_SignerInfo *si)
    {
    	X509_ALGOR *digestAlgorithm, *signatureAlgorithm;
    
    	if (verify != 0 && verify != 1)
    		return -1;
    
    	/* Check that we have an Ed25519 public key. */
    	if (EVP_PKEY_id(pkey) != NID_ED25519)
    		return -1;
    
    	CMS_SignerInfo_get0_algs(si, NULL, NULL, &digestAlgorithm,
    	    &signatureAlgorithm);
    
    	/* RFC 8419, section 2.3: digestAlgorithm MUST be SHA-512. */
    	if (digestAlgorithm == NULL)
    		return -1;
    	if (OBJ_obj2nid(digestAlgorithm->algorithm) != NID_sha512)
    		return -1;
    
    	/*
    	 * RFC 8419, section 2.4: signatureAlgorithm MUST be Ed25519, and the
    	 * parameters MUST be absent. For verification check that this is the
    	 * case, for signing set the signatureAlgorithm accordingly.
    	 */
    	if (verify) {
    		const ASN1_OBJECT *obj;
    		int param_type;
    
    		if (signatureAlgorithm == NULL)
    			return -1;
    
    		X509_ALGOR_get0(&obj, &param_type, NULL, signatureAlgorithm);
    		if (OBJ_obj2nid(obj) != NID_ED25519)
    			return -1;
    		if (param_type != V_ASN1_UNDEF)
    			return -1;
    
    		return 1;
    	}
    
    	if (!X509_ALGOR_set0_by_nid(signatureAlgorithm, NID_ED25519,
    	    V_ASN1_UNDEF, NULL))
    		return -1;
    
    	return 1;
    }
    #endif
    
    static int
    ecx_sign_ctrl(EVP_PKEY *pkey, int op, long arg1, void *arg2)
    {
    	switch (op) {
    #ifndef OPENSSL_NO_CMS
    	case ASN1_PKEY_CTRL_CMS_SIGN:
    		return ecx_cms_sign_or_verify(pkey, arg1, arg2);
    #endif
    	case ASN1_PKEY_CTRL_DEFAULT_MD_NID:
    		/* PureEdDSA does its own hashing. */
    		*(int *)arg2 = NID_undef;
    		return 2;
    	}
    	return -2;
    }
    
    static int
    ecx_set_priv_key(EVP_PKEY *pkey, const uint8_t *priv, size_t len)
    {
    	struct ecx_key_st *ecx_key = NULL;
    	int ret = 0;
    
    	if (priv == NULL || len != ecx_key_len(pkey->ameth->pkey_id)) {
    		ECerror(EC_R_INVALID_ENCODING);
    		goto err;
    	}
    
    	if ((ecx_key = ecx_key_new(pkey->ameth->pkey_id)) == NULL)
    		goto err;
    	if (!ecx_key_set_priv(ecx_key, priv, len))
    		goto err;
    	if (!EVP_PKEY_assign(pkey, pkey->ameth->pkey_id, ecx_key))
    		goto err;
    	ecx_key = NULL;
    
    	ret = 1;
    
     err:
    	ecx_key_free(ecx_key);
    
    	return ret;
    }
    
    static int
    ecx_set_pub_key(EVP_PKEY *pkey, const uint8_t *pub, size_t len)
    {
    	struct ecx_key_st *ecx_key = NULL;
    	int ret = 0;
    
    	if (pub == NULL || len != ecx_key_len(pkey->ameth->pkey_id)) {
    		ECerror(EC_R_INVALID_ENCODING);
    		goto err;
    	}
    
    	if ((ecx_key = ecx_key_new(pkey->ameth->pkey_id)) == NULL)
    		goto err;
    	if (!ecx_key_set_pub(ecx_key, pub, len))
    		goto err;
    	if (!EVP_PKEY_assign(pkey, pkey->ameth->pkey_id, ecx_key))
    		goto err;
    	ecx_key = NULL;
    
    	ret = 1;
    
     err:
    	ecx_key_free(ecx_key);
    
    	return ret;
    }
    
    static int
    ecx_get_priv_key(const EVP_PKEY *pkey, unsigned char *out_priv, size_t *out_len)
    {
    	struct ecx_key_st *ecx_key = pkey->pkey.ecx;
    	CBS cbs;
    
    	if (out_priv == NULL) {
    		*out_len = ecx_key_len(pkey->ameth->pkey_id);
    		return 1;
    	}
    
    	if (ecx_key == NULL || ecx_key->priv_key == NULL)
    		return 0;
    
    	CBS_init(&cbs, ecx_key->priv_key, ecx_key->priv_key_len);
    	if (!CBS_write_bytes(&cbs, out_priv, *out_len, out_len))
    		return 0;
    
    	return 1;
    }
    
    static int
    ecx_get_pub_key(const EVP_PKEY *pkey, unsigned char *out_pub, size_t *out_len)
    {
    	struct ecx_key_st *ecx_key = pkey->pkey.ecx;
    	CBS cbs;
    
    	if (out_pub == NULL) {
    		*out_len = ecx_key_len(pkey->ameth->pkey_id);
    		return 1;
    	}
    
    	if (ecx_key == NULL || ecx_key->pub_key == NULL)
    		return 0;
    
    	CBS_init(&cbs, ecx_key->pub_key, ecx_key->pub_key_len);
    	if (!CBS_write_bytes(&cbs, out_pub, *out_len, out_len))
    		return 0;
    
    	return 1;
    }
    
    static int
    pkey_ecx_keygen(EVP_PKEY_CTX *pkey_ctx, EVP_PKEY *pkey)
    {
    	struct ecx_key_st *ecx_key = NULL;
    	int ret = 0;
    
    	if ((ecx_key = ecx_key_new(pkey_ctx->pmeth->pkey_id)) == NULL)
    		goto err;
    	if (!ecx_key_generate(ecx_key))
    		goto err;
    	if (!EVP_PKEY_assign(pkey, pkey_ctx->pmeth->pkey_id, ecx_key))
    		goto err;
    	ecx_key = NULL;
    
    	ret = 1;
    
     err:
    	ecx_key_free(ecx_key);
    
    	return ret;
    }
    
    static int
    pkey_ecx_derive(EVP_PKEY_CTX *pkey_ctx, unsigned char *out_key,
        size_t *out_key_len)
    {
    	struct ecx_key_st *ecx_key, *ecx_peer_key;
    
    	if (pkey_ctx->pkey == NULL || pkey_ctx->peerkey == NULL) {
    		ECerror(EC_R_KEYS_NOT_SET);
    		return 0;
    	}
    
    	if ((ecx_key = pkey_ctx->pkey->pkey.ecx) == NULL) {
    		ECerror(EC_R_INVALID_PRIVATE_KEY);
    		return 0;
    	}
    	if (ecx_key->priv_key == NULL) {
    		ECerror(EC_R_INVALID_PRIVATE_KEY);
    		return 0;
    	}
    
    	if ((ecx_peer_key = pkey_ctx->peerkey->pkey.ecx) == NULL) {
    		ECerror(EC_R_INVALID_PEER_KEY);
    		return 0;
    	}
    
    	if (out_key != NULL) {
    		if (!X25519(out_key, ecx_key->priv_key, ecx_peer_key->pub_key))
    			return 0;
    	}
    
    	*out_key_len = X25519_KEYLEN;
    
    	return 1;
    }
    
    static int
    pkey_ecx_ctrl(EVP_PKEY_CTX *pkey_ctx, int op, int arg1, void *arg2)
    {
    	if (op == EVP_PKEY_CTRL_PEER_KEY)
    		return 1;
    
    	return -2;
    }
    
    static int
    ecx_item_verify(EVP_MD_CTX *md_ctx, const ASN1_ITEM *it, void *asn,
       X509_ALGOR *algor, ASN1_BIT_STRING *abs, EVP_PKEY *pkey)
    {
    	const ASN1_OBJECT *aobj;
    	int nid, param_type;
    
    	X509_ALGOR_get0(&aobj, &param_type, NULL, algor);
    
    	nid = OBJ_obj2nid(aobj);
    
    	if (nid != NID_ED25519 || param_type != V_ASN1_UNDEF) {
    		ECerror(EC_R_INVALID_ENCODING);
    		return -1;
    	}
    
    	if (!EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, pkey))
    		return -1;
    
    	return 2;
    }
    
    static int
    ecx_item_sign(EVP_MD_CTX *md_ctx, const ASN1_ITEM *it, void *asn,
        X509_ALGOR *algor1, X509_ALGOR *algor2, ASN1_BIT_STRING *abs)
    {
    	if (!X509_ALGOR_set0_by_nid(algor1, NID_ED25519, V_ASN1_UNDEF, NULL))
    		return 0;
    
    	if (algor2 != NULL) {
    		if (!X509_ALGOR_set0_by_nid(algor2, NID_ED25519, V_ASN1_UNDEF,
    		    NULL))
    			return 0;
    	}
    
    	/* Tell ASN1_item_sign_ctx() that identifiers are set and it needs to sign. */
    	return 3;
    }
    
    static int
    pkey_ecx_digestsign(EVP_MD_CTX *md_ctx, unsigned char *out_sig,
        size_t *out_sig_len, const unsigned char *message, size_t message_len)
    {
    	struct ecx_key_st *ecx_key;
    	EVP_PKEY_CTX *pkey_ctx;
    
    	pkey_ctx = EVP_MD_CTX_pkey_ctx(md_ctx);
    	ecx_key = pkey_ctx->pkey->pkey.ecx;
    
    	if (out_sig == NULL) {
    		*out_sig_len = ecx_sig_size(pkey_ctx->pkey);
    		return 1;
    	}
    	if (*out_sig_len < ecx_sig_size(pkey_ctx->pkey)) {
    		ECerror(EC_R_BUFFER_TOO_SMALL);
    		return 0;
    	}
    
    	if (ecx_key == NULL)
    		return 0;
    	if (ecx_key->priv_key == NULL || ecx_key->pub_key == NULL)
    		return 0;
    
    	if (!ED25519_sign(out_sig, message, message_len, ecx_key->pub_key,
    	    ecx_key->priv_key))
    		return 0;
    
    	*out_sig_len = ecx_sig_size(pkey_ctx->pkey);
    
    	return 1;
    }
    
    static int
    pkey_ecx_digestverify(EVP_MD_CTX *md_ctx, const unsigned char *sig,
       size_t sig_len, const unsigned char *message, size_t message_len)
    {
    	struct ecx_key_st *ecx_key;
    	EVP_PKEY_CTX *pkey_ctx;
    
    	pkey_ctx = EVP_MD_CTX_pkey_ctx(md_ctx);
    	ecx_key = pkey_ctx->pkey->pkey.ecx;
    
    	if (ecx_key == NULL || ecx_key->pub_key == NULL)
    		return -1;
    	if (sig_len != ecx_sig_size(pkey_ctx->pkey))
    		return -1;
    
    	return ED25519_verify(message, message_len, sig, ecx_key->pub_key);
    }
    
    static int
    pkey_ecx_ed_ctrl(EVP_PKEY_CTX *pkey_ctx, int op, int arg1, void *arg2)
    {
    	switch (op) {
    	case EVP_PKEY_CTRL_MD:
    		/* PureEdDSA does its own hashing. */
    		if (arg2 != NULL && (const EVP_MD *)arg2 != EVP_md_null()) {
    			ECerror(EC_R_INVALID_DIGEST_TYPE);
    			return 0;
    		}
    		return 1;
    
    #ifndef OPENSSL_NO_CMS
    	case EVP_PKEY_CTRL_CMS_SIGN:
    #endif
    	case EVP_PKEY_CTRL_DIGESTINIT:
    		return 1;
    	}
    	return -2;
    }
    
    const EVP_PKEY_ASN1_METHOD x25519_asn1_meth = {
    	.base_method = &x25519_asn1_meth,
    	.pkey_id = EVP_PKEY_X25519,
    	.pkey_flags = 0,
    	.pem_str = "X25519",
    	.info = "OpenSSL X25519 algorithm",
    
    	.pub_decode = ecx_pub_decode,
    	.pub_encode = ecx_pub_encode,
    	.pub_cmp = ecx_pub_cmp,
    	.pub_print = ecx_pub_print,
    
    	.priv_decode = ecx_priv_decode,
    	.priv_encode = ecx_priv_encode,
    	.priv_print = ecx_priv_print,
    
    	.pkey_size = ecx_size,
    	.pkey_bits = ecx_bits,
    	.pkey_security_bits = ecx_security_bits,
    
    	.param_cmp = ecx_param_cmp,
    
    	.pkey_free = ecx_free,
    	.pkey_ctrl = ecx_ctrl,
    
    	.set_priv_key = ecx_set_priv_key,
    	.set_pub_key = ecx_set_pub_key,
    	.get_priv_key = ecx_get_priv_key,
    	.get_pub_key = ecx_get_pub_key,
    };
    
    const EVP_PKEY_METHOD x25519_pkey_meth = {
    	.pkey_id = EVP_PKEY_X25519,
    	.keygen = pkey_ecx_keygen,
    	.derive = pkey_ecx_derive,
    	.ctrl = pkey_ecx_ctrl,
    };
    
    const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth = {
    	.base_method = &ed25519_asn1_meth,
    	.pkey_id = EVP_PKEY_ED25519,
    	.pkey_flags = 0,
    	.pem_str = "ED25519",
    	.info = "OpenSSL ED25519 algorithm",
    
    	.pub_decode = ecx_pub_decode,
    	.pub_encode = ecx_pub_encode,
    	.pub_cmp = ecx_pub_cmp,
    	.pub_print = ecx_pub_print,
    
    	.priv_decode = ecx_priv_decode,
    	.priv_encode = ecx_priv_encode,
    	.priv_print = ecx_priv_print,
    
    	.pkey_size = ecx_sig_size,
    	.pkey_bits = ecx_bits,
    	.pkey_security_bits = ecx_security_bits,
    
    	.signature_info = ecx_signature_info,
    
    	.param_cmp = ecx_param_cmp,
    
    	.pkey_free = ecx_free,
    	.pkey_ctrl = ecx_sign_ctrl,
    
    	.item_verify = ecx_item_verify,
    	.item_sign = ecx_item_sign,
    
    	.set_priv_key = ecx_set_priv_key,
    	.set_pub_key = ecx_set_pub_key,
    	.get_priv_key = ecx_get_priv_key,
    	.get_pub_key = ecx_get_pub_key,
    };
    
    const EVP_PKEY_METHOD ed25519_pkey_meth = {
    	.pkey_id = EVP_PKEY_ED25519,
    	.flags = EVP_PKEY_FLAG_SIGCTX_CUSTOM,
    	.keygen = pkey_ecx_keygen,
    	.ctrl = pkey_ecx_ed_ctrl,
    	.digestsign = pkey_ecx_digestsign,
    	.digestverify = pkey_ecx_digestverify,
    };