/* $OpenBSD: ssh-ecdsa.c,v 1.28 2025/07/24 05:44:55 djm Exp $ */
/*
 * Copyright (c) 2000 Markus Friedl.  All rights reserved.
 * Copyright (c) 2010 Damien Miller.  All rights reserved.
 * Copyright (c) 2020-2026 Roumen Petrov.  All rights reserved.
 *
 * 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 ``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 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.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifndef USE_OPENSSL_PROVIDER
/* TODO: implement OpenSSL 4.0 API, as OpenSSL 3.* is quite nonfunctional */
# define OPENSSL_SUPPRESS_DEPRECATED
#endif

#define SSHKEY_INTERNAL
#include "includes.h"

#if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC)

#include <sys/types.h>

#include "evp-compat.h"
#include <openssl/bn.h>
/* for i2d_PUBKEY used in key serialisation work-around */
#include <openssl/x509.h>
#ifdef ENABLE_KEY_PROVIDERS
#include <openssl/core_names.h>
#endif

#include <string.h>

#include "sshxkey.h"
#include "sshbuf.h"
#include "ssherr.h"
#include "log.h"
#include "xmalloc.h"

#if 0
# define USE_AFFINE_COORDINATES_CHECK
#endif

#ifdef USE_AFFINE_COORDINATES_CHECK
#ifndef HAVE_EC_POINT_GET_AFFINE_COORDINATES		/* OpenSSL < 1.1.1 */
/* Functions are available even in 0.9.7* but EC is not activated
 * as NIST curves are not supported yet.
 */
static inline int
EC_POINT_get_affine_coordinates(
    const EC_GROUP *group, const EC_POINT *p,
    BIGNUM *x, BIGNUM *y, BN_CTX *ctx
) {
	return EC_POINT_get_affine_coordinates_GFp(group, p, x, y, ctx);
}
#endif /*ndef HAVE_EC_POINT_GET_AFFINE_COORDINATES*/
#endif /*def USE_AFFINE_COORDINATES_CHECK*/

#ifdef ENABLE_KEY_PROVIDERS
static size_t
ssh_EVP_PKEY_get1_encoded_public_key(EVP_PKEY *pkey, unsigned char **ppub) {
	size_t len;

	len = EVP_PKEY_get1_encoded_public_key(pkey, ppub);
	if (len > 0) return len;

	debug3_f("try a workaround ...");
/* NOTE: "pkcs11-provider" fail on call above.
 * Try a workaround - convert to key that uses default provider.
 */
{	EVP_PKEY *p = NULL;
	if (ssh_pkey_copy_public(pkey, &p) != 0)
		return -1;

	len = EVP_PKEY_get1_encoded_public_key(p, ppub);

	EVP_PKEY_free(p);
}
	return len;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

void
ssh_EVP_PKEY_EC_KEY_get1_private_key(EVP_PKEY *pkey,
    BIGNUM **privp
) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pkey) != NULL) {
		if (privp != NULL)
			(void)EVP_PKEY_get_bn_param(pkey,
			    OSSL_PKEY_PARAM_PRIV_KEY, privp);
		return;
	}
#endif
{	const BIGNUM *priv_key;
	EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);

	if (ec == NULL) return;

	priv_key = EC_KEY_get0_private_key(ec);
	if (privp != NULL) *privp = BN_dup(priv_key);

	EC_KEY_free(ec);
}
}


static int
nid_list[] = {
	NID_X9_62_prime256v1,
	NID_secp384r1,
#ifdef OPENSSL_HAS_NISTP521
	NID_secp521r1,
#endif /* OPENSSL_HAS_NISTP521 */
	-1
};

/* NOTE: keep synchronised with nid_list */
static int
dgst_list[] = {
	SSH_MD_EC_SHA256_SSH,
	SSH_MD_EC_SHA384_SSH,
	SSH_MD_EC_SHA512_SSH,
};

static int
ssh_ecdsa_nid_dgst(int ecdsa_nid) {
	int k;

	for (k = 0; nid_list[k] != -1; k++) {
		if (ecdsa_nid == nid_list[k])
			return dgst_list[k];
	}

	/* NOTE: return digest not applicable for ECDSA */
	return SSH_MD_NONE;
}

static inline const ssh_evp_md*
ssh_ecdsa_dgst(const struct sshkey *key) {
	int id = ssh_ecdsa_nid_dgst(key->ecdsa_nid);
	if (id == SSH_MD_NONE) return NULL;

	return ssh_evp_md_find(id);
}


static inline u_int
sshkey_curve_nid_to_bits(int nid)
{
	switch (nid) {
	case NID_X9_62_prime256v1: return 256;
	case NID_secp384r1: return 384;
#ifdef OPENSSL_HAS_NISTP521
	case NID_secp521r1: return 521;
#endif /* OPENSSL_HAS_NISTP521 */
	}
	return 0;
}

static inline int
sshkey_curve_bits_to_nid(int bits)
{
	switch (bits) {
	case 256: return NID_X9_62_prime256v1;
	case 384: return NID_secp384r1;
#ifdef OPENSSL_HAS_NISTP521
	case 521: return NID_secp521r1;
#endif /* OPENSSL_HAS_NISTP521 */
	}
	return NID_undef;
}


static inline int
sshkey_curve_name_to_nid(const char *name)
{
	if (strcmp(name, "nistp256") == 0)
		return NID_X9_62_prime256v1;
	if (strcmp(name, "nistp384") == 0)
		return NID_secp384r1;
# ifdef OPENSSL_HAS_NISTP521
	if (strcmp(name, "nistp521") == 0)
		return NID_secp521r1;
# endif /* OPENSSL_HAS_NISTP521 */
	return NID_undef;
}

static inline const char *
sshkey_curve_nid_to_name(int nid)
{
	switch (nid) {
	case NID_X9_62_prime256v1:
		return "nistp256";
	case NID_secp384r1:
		return "nistp384";
# ifdef OPENSSL_HAS_NISTP521
	case NID_secp521r1:
		return "nistp521";
# endif /* OPENSSL_HAS_NISTP521 */
	}
	return NULL;
}


#ifdef USE_PKEY_FROMDATA
static inline int
ssh_ec_pkey_fromdata(int selection, OSSL_PARAM *params, EVP_PKEY **pkeyp) {
	return ssh_pkey_fromdata("EC", selection, params, pkeyp);
}
#endif /*def USE_PKEY_FROMDATA*/


/* functions that write provider keys */
#ifdef ENABLE_KEY_PROVIDERS
static int
sshbuf_write_prov_ecpub(struct sshbuf *buf, EVP_PKEY *pk) {
	unsigned char *data = NULL;
	size_t len;
	int r = SSH_ERR_LIBCRYPTO_ERROR;

	len = ssh_EVP_PKEY_get1_encoded_public_key(pk, &data);
	if (len == 0) goto err;

	r = sshbuf_put_string(buf, data, len);

err:
	OPENSSL_free(data);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

#ifdef ENABLE_KEY_PROVIDERS
static int
sshbuf_write_prov_ec(struct sshbuf *buf, EVP_PKEY *pk, int with_public) {
	int r;

	if (!with_public) goto skip_public;

	/* NOTE: let use work-around with encoded key */
	r = sshbuf_write_prov_ecpub(buf, pk);
	if (r != 0) return r;

skip_public:
{	BIGNUM *exponent = NULL;
	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_PRIV_KEY, &exponent) != 1)
		return SSH_ERR_LIBCRYPTO_ERROR;
	return sshbuf_put_bignum2(buf, exponent);
}
}
#endif /*def ENABLE_KEY_PROVIDERS*/


/* functions that write low level (ec) keys */
static int
sshbuf_put_ecpub(struct sshbuf *buf, const EC_KEY *ec)
{
	const EC_GROUP *g;
	const EC_POINT *v;
	u_char d[SSHBUF_MAX_ECPOINT];
	size_t len;
	int ret;

	g = EC_KEY_get0_group(ec);
	v = EC_KEY_get0_public_key(ec);

	if ((len = EC_POINT_point2oct(g, v, POINT_CONVERSION_UNCOMPRESSED,
	    NULL, 0, NULL)) > SSHBUF_MAX_ECPOINT) {
		return SSH_ERR_INVALID_ARGUMENT;
	}
	if (EC_POINT_point2oct(g, v, POINT_CONVERSION_UNCOMPRESSED,
	    d, len, NULL) != len) {
		return SSH_ERR_INTERNAL_ERROR; /* Shouldn't happen */
	}
	ret = sshbuf_put_string(buf, d, len);
	explicit_bzero(d, len);
	return ret;
}

static int
sshbuf_put_ec(struct sshbuf *buf, const EC_KEY *ec, int with_public) {
	int r;

	if (!with_public) goto skip_public;

	r = sshbuf_put_ecpub(buf, ec);
	if (r != 0) return r;

skip_public:
{	const BIGNUM *exponent = EC_KEY_get0_private_key(ec);
	return sshbuf_put_bignum2(buf, exponent);
}
}


/* functions that write pkey(ec) keys */
extern int /* see kexecdh.c */
/* see regress/unittests/sshbuf/test_sshbuf_getput_crypto.c */
sshbuf_write_pkey_ecpub(struct sshbuf *buf, EVP_PKEY *pk);

int
sshbuf_write_pkey_ecpub(struct sshbuf *buf, EVP_PKEY *pk) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return sshbuf_write_prov_ecpub(buf, pk);
#endif
{	int r;
	EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pk);
	if (ec == NULL)
		return SSH_ERR_INVALID_ARGUMENT;

	r = sshbuf_put_ecpub(buf, ec);

	EC_KEY_free(ec);
	return r;
}
}

static int
sshbuf_write_pkey_ec(struct sshbuf *buf, EVP_PKEY *pk, int with_public) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return sshbuf_write_prov_ec(buf, pk, with_public);
#endif
{	int r;
	EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pk);
	if (ec == NULL)
		return SSH_ERR_INVALID_ARGUMENT;

	r = sshbuf_put_ec(buf, ec, with_public);

	EC_KEY_free(ec);
	return r;
}
}


#ifdef WITH_OPENSSL_4_0_API
/* TODO: new methods compatible with OpenSSL 4.0 API.
 * Remark: OpenSSL 3* is too buggy - almost each release fail
 * or crash in regression tests.
 */
#else
/* management of elementary EC key */
/* TODO
 * - reorder
 * - exclude low level keys if "engines" are deprecated
 * - use definition USE_PKEY_FROMDATA is applicable
 */

int
ssh_EC_KEY_preserve_nid(EC_KEY *ec)
{
	const EC_GROUP *g = EC_KEY_get0_group(ec);

	/*
	 * The group may be stored in a ASN.1 encoded private key in one of two
	 * ways: as a "named group", which is reconstituted by ASN.1 object ID
	 * or explicit group parameters encoded into the key blob. Only the
	 * "named group" case sets the group NID for us, but we can figure
	 * it out for the other case by comparing against all the groups that
	 * are supported.
	 */
{	int nid = EC_GROUP_get_curve_name(g);
	if (nid != NID_undef)
		return (ssh_ecdsa_nid_dgst(nid) != SSH_MD_NONE) ? nid : -1;
}
{	int k;
	for (k = 0; nid_list[k] != -1; k++) {
		EC_GROUP *eg = EC_GROUP_new_by_curve_name(nid_list[k]);
		if (eg == NULL) continue;

		if (EC_GROUP_cmp(g, eg, NULL) != 0) {
			EC_GROUP_free(eg);
			continue;
		}

		/* Use the group with the NID attached */
		EC_GROUP_set_asn1_flag(eg, OPENSSL_EC_NAMED_CURVE);
		if (EC_KEY_set_group(ec, eg) != 1) {
			EC_GROUP_free(eg);
			return -1;
		}
		EC_GROUP_free(eg);
		return nid_list[k];
	}
}
	return -1;
}

#ifndef USE_PKEY_FROMDATA
static inline EC_KEY*
ssh_EC_KEY_new_by_curve_name(int nid) {
	EC_KEY *ec;

	ec = EC_KEY_new_by_curve_name(nid);
	if (ec == NULL) return NULL;

#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
    defined(LIBRESSL_VERSION_NUMBER)
	/* Note since 1.1.0 OpenSSL uses named curve parameter encoding by default.
	 * It seems to me default is changed in upcoming 3.0 but key is marked
	 * properly when created by nid.
	 */
	EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE);
#endif
	return ec;
}
#endif /*ndef USE_PKEY_FROMDATA*/

static int
sshbuf_read_ec_curve(struct sshbuf *buf,
    const struct sshkey_impl *impl, struct sshkey *key)
{
	int r, nid = impl->id.subtype;

	debug3_f("impl->name/nid: %s/%d", impl->name, nid);

{	char *curve;

	r = sshbuf_get_cstring(buf, &curve, NULL);
	if (r != 0) return r;

	if (nid != sshkey_curve_name_to_nid(curve))
		r = SSH_ERR_EC_CURVE_MISMATCH;

	free(curve);
}
	if (r == 0)
		key->ecdsa_nid = nid;
	return r;
}

static inline int
sshbuf_write_ec_curve(struct sshbuf *buf, const struct sshkey *key) {
	const char *curve_name = sshkey_curve_nid_to_name(key->ecdsa_nid);
	return sshbuf_put_cstring(buf, curve_name);
}


static int
ssh_EC_GROUP_check_public(const EC_GROUP *group, const EC_POINT *public)
{
	EC_POINT *nq = NULL;
	BIGNUM *order = NULL;
#ifdef USE_AFFINE_COORDINATES_CHECK
	BIGNUM *x = NULL, *y = NULL, *tmp = NULL;
#endif
	int ret = SSH_ERR_KEY_INVALID_EC_VALUE;

	/*
	 * NB. This assumes OpenSSL has already verified that the public
	 * point lies on the curve. This is done by EC_POINT_oct2point()
	 * implicitly calling EC_POINT_is_on_curve(). If this code is ever
	 * reachable with public points not unmarshalled using
	 * EC_POINT_oct2point then the caller will need to explicitly check.
	 */

	/* Q != infinity */
	if (EC_POINT_is_at_infinity(group, public))
		goto out;

#ifdef USE_AFFINE_COORDINATES_CHECK
	if ((x = BN_new()) == NULL ||
	    (y = BN_new()) == NULL ||
	    (tmp = BN_new()) == NULL) {
		ret = SSH_ERR_ALLOC_FAIL;
		goto out;
	}
#endif

	if ((order = BN_new()) == NULL) {
		ret = SSH_ERR_ALLOC_FAIL;
		goto out;
	}

#ifdef USE_AFFINE_COORDINATES_CHECK
	/* log2(x) > log2(order)/2, log2(y) > log2(order)/2 */
	if (EC_GROUP_get_order(group, order, NULL) != 1 ||
	    EC_POINT_get_affine_coordinates(group, public,
	    x, y, NULL) != 1) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto out;
	}
	if (BN_num_bits(x) <= BN_num_bits(order) / 2 ||
	    BN_num_bits(y) <= BN_num_bits(order) / 2)
		goto out;
#endif /*def USE_AFFINE_COORDINATES_CHECK*/

	/* nQ == infinity (n == order of subgroup) */
#ifdef USE_AFFINE_COORDINATES_CHECK
	/* As per NIST SP 800-56A, 5.6.2.3 check may be skipped for curves
	 * with cofactor 1.
	 */
{	BIGNUM *cofactor;
	if ((cofactor = BN_new()) == NULL) {
		ret = SSH_ERR_ALLOC_FAIL;
		goto out;
	}
	if (EC_GROUP_get_cofactor(group, cofactor, NULL) != 1) {
		BN_clear_free(cofactor);
		goto out;
	}
	if (BN_is_one(cofactor)) {
		BN_clear_free(cofactor);
		ret = 0;
		goto out;
	}
	BN_clear_free(cofactor);
}
#endif

	if ((nq = EC_POINT_new(group)) == NULL) {
		ret = SSH_ERR_ALLOC_FAIL;
		goto out;
	}
	if (EC_POINT_mul(group, nq, NULL, public, order, NULL) != 1) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto out;
	}
	if (EC_POINT_is_at_infinity(group, nq) != 1)
		goto out;

#ifdef USE_AFFINE_COORDINATES_CHECK
	/* x < order - 1, y < order - 1 */
	if (!BN_sub(tmp, order, BN_value_one())) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto out;
	}
	if (BN_cmp(x, tmp) >= 0 || BN_cmp(y, tmp) >= 0)
		goto out;
#endif /*def USE_AFFINE_COORDINATES_CHECK*/

	/* other checks ? */

	ret = 0;
 out:
#ifdef USE_AFFINE_COORDINATES_CHECK
	BN_clear_free(x);
	BN_clear_free(y);
	BN_clear_free(tmp);
#endif /*def USE_AFFINE_COORDINATES_CHECK*/
	BN_clear_free(order);
	EC_POINT_free(nq);
	return ret;
}


static int
ssh_EC_GROUP_check_private(const EC_GROUP *group, const BIGNUM *exponent) {
	BIGNUM *order, *tmp = NULL;
	int ret;

	order = BN_new();
	if (order == NULL) return SSH_ERR_ALLOC_FAIL;

	if (EC_GROUP_get_order(group, order, NULL) != 1) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	/* log2(private) > log2(order)/2 */
	if (BN_num_bits(exponent) <= BN_num_bits(order) / 2) {
		ret = SSH_ERR_KEY_INVALID_EC_VALUE;
		goto err;
	}

	tmp = BN_new();
	if (tmp == NULL) {
		ret = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

	/* private < order - 1 */
	if (!BN_sub(tmp, order, BN_value_one())) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
	if (BN_cmp(exponent, tmp) >= 0) {
		ret = SSH_ERR_KEY_INVALID_EC_VALUE;
		goto err;
	}

	/* other checks ? */

	ret = 0;

err:
	BN_clear_free(order);
	BN_clear_free(tmp);
	return ret;
}


#ifdef ENABLE_KEY_PROVIDERS
static EC_GROUP*
ssh_EVP_PKEY_prov_get_EC_GROUP(EVP_PKEY *pkey) {
	char group_name[1024];
	int order;

	if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME,
	    group_name, sizeof(group_name), NULL) != 1) {
		debug3_f("cannot get group parameter");
		return NULL;
	}

	order = OBJ_txt2nid(group_name);
	if (order == NID_undef) {
		error_f("unknown group '%s'", group_name);
		return NULL;
	}
	if (ssh_ecdsa_nid_dgst(order) == SSH_MD_NONE) {
		error_f("unsupported group '%s'", group_name);
		return NULL;
	}

	return EC_GROUP_new_by_curve_name(order);
}
#endif /*def ENABLE_KEY_PROVIDERS*/

#ifdef ENABLE_KEY_PROVIDERS
static int
ssh_EVP_PKEY_get1_public_key_ecprov(EVP_PKEY *pk,
    EC_GROUP **groupp, EC_POINT **pubp
) {
	EC_GROUP *group = NULL;
	EC_POINT *pub_key = NULL;
	int r;

	if (groupp != NULL) *groupp = NULL;
	if (pubp != NULL) *pubp = NULL;

	group = ssh_EVP_PKEY_prov_get_EC_GROUP(pk);
	if (group == NULL)
		return SSH_ERR_ALLOC_FAIL;

	pub_key = EC_POINT_new(group);
	if (pub_key == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

{	unsigned char *data;
	size_t len;

	len = ssh_EVP_PKEY_get1_encoded_public_key(pk, &data);
	if (len == 0) {
		r = SSH_ERR_KEY_INVALID_EC_VALUE;
		goto err;
	}

	/* Only handle uncompressed points */
	if (*data != POINT_CONVERSION_UNCOMPRESSED) {
		r = SSH_ERR_INVALID_FORMAT;
		goto data_err;
	}

	if (EC_POINT_oct2point(group, pub_key, data, len, NULL) != 1) {
		r = SSH_ERR_INVALID_FORMAT;
		goto data_err;
	}

	if (groupp != NULL) {
		*groupp = group;
		group = NULL;
	}
	if (pubp != NULL) {
		*pubp = pub_key;
		pub_key = NULL;
	}

	r = 0;

data_err:
	OPENSSL_free(data);
}

err:
	EC_GROUP_free(group);
	EC_POINT_free(pub_key);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

void
ssh_EVP_PKEY_EC_KEY_get1_public_key(EVP_PKEY *pkey,
    EC_GROUP **groupp, EC_POINT **pubp
) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pkey) != NULL) {
		(void)ssh_EVP_PKEY_get1_public_key_ecprov(pkey, groupp, pubp);
		return;
	}
#endif
{	const EC_GROUP *group;
	const EC_POINT *pub_key;
	EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
	if (ec == NULL) return;

	group = EC_KEY_get0_group(ec);
	pub_key = EC_KEY_get0_public_key(ec);

	if (groupp != NULL) *groupp = EC_GROUP_dup(group);
	if (pubp != NULL) *pubp = EC_POINT_dup(pub_key, group);
	EC_KEY_free(ec);
}
}

#ifdef ENABLE_KEY_PROVIDERS
static int
ssh_EVP_PKEY_check_public_ecprov(EVP_PKEY *pkey, const EC_POINT *public) {
	EC_GROUP *group;
	int r;

	group = ssh_EVP_PKEY_prov_get_EC_GROUP(pkey);
	if (group == NULL) return SSH_ERR_ALLOC_FAIL;

	r = ssh_EC_GROUP_check_public(group, public);

	EC_GROUP_free(group);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

static inline int
ssh_EC_KEY_check_public(const EC_KEY *ec, const EC_POINT *public) {
	const EC_GROUP *group = EC_KEY_get0_group(ec);
	if (group == NULL) return SSH_ERR_INTERNAL_ERROR;

	return ssh_EC_GROUP_check_public(group, public);
}

int
ssh_EVP_PKEY_check_public_ec(EVP_PKEY *pk, const EC_POINT *public) {
	if (pk == NULL) return SSH_ERR_INVALID_ARGUMENT;
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return ssh_EVP_PKEY_check_public_ecprov(pk, public);
#endif
{	EC_KEY *ec;
	int r;

	ec = EVP_PKEY_get1_EC_KEY(pk);
	if (ec == NULL) return SSH_ERR_INVALID_ARGUMENT;

	r = ssh_EC_KEY_check_public(ec, public);

	EC_KEY_free(ec);
	return r;
}
}

#ifdef ENABLE_KEY_PROVIDERS
static int
ssh_EVP_PKEY_validate_public_ecprov(EVP_PKEY *pk) {
	EC_GROUP *group = NULL;
	EC_POINT *pub_key = NULL;
	int r;

	r = ssh_EVP_PKEY_get1_public_key_ecprov(pk, &group, &pub_key);
	if (r != 0) goto err;

	r = ssh_EVP_PKEY_check_public_ecprov(pk, pub_key);

err:
	EC_GROUP_free(group);
	EC_POINT_free(pub_key);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

static inline int
ssh_EC_KEY_validate_public(const EC_KEY *ec) {
	return ssh_EC_KEY_check_public(ec, EC_KEY_get0_public_key(ec));
}


#ifdef ENABLE_KEY_PROVIDERS
static int
ssh_EVP_PKEY_complete_pub_ecprov(EVP_PKEY *pk) {
	char encoding[1024];

	if (EVP_PKEY_get_utf8_string_param(pk, OSSL_PKEY_PARAM_EC_ENCODING,
	    encoding, sizeof(encoding), NULL) != 1) {
		unsigned char *data;
		size_t len;

		len = ssh_EVP_PKEY_get1_encoded_public_key(pk, &data);
		if (len == 0)
			return SSH_ERR_KEY_INVALID_EC_VALUE;

		/* Only handle uncompressed points */
		if (*data != POINT_CONVERSION_UNCOMPRESSED) {
			OPENSSL_free(data);
			return SSH_ERR_EC_CURVE_INVALID;
		}
		OPENSSL_free(data);
	} else {
		if (strcmp(encoding, OSSL_PKEY_EC_ENCODING_GROUP) != 0)
			return SSH_ERR_EC_CURVE_INVALID;
	}

	return ssh_EVP_PKEY_validate_public_ecprov(pk);
}
#endif /*def ENABLE_KEY_PROVIDERS*/

static int
ssh_EVP_PKEY_complete_pub_ecdsa(EVP_PKEY *pk) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return ssh_EVP_PKEY_complete_pub_ecprov(pk);
#endif
{	EC_KEY *ec;
	int r, nid;

	ec = EVP_PKEY_get1_EC_KEY(pk);
	if (ec == NULL)
		return SSH_ERR_INVALID_ARGUMENT;

	nid = ssh_EC_KEY_preserve_nid(ec);
	if (nid < 0) {
		error_f("unsupported elliptic curve");
		r = SSH_ERR_EC_CURVE_INVALID;
		goto err;
	}

	r = ssh_EC_KEY_validate_public(ec);

err:
	EC_KEY_free(ec);
	return r;
}
}


#ifdef ENABLE_KEY_PROVIDERS
static int
sshkey_set_prov_param_nid(const EVP_PKEY *pk, int *pnid) {
	char param[1024];
	size_t param_len;
	int nid;

	if (EVP_PKEY_get_utf8_string_param(pk, OSSL_PKEY_PARAM_GROUP_NAME,
	    param, sizeof(param), &param_len) != 1)
		return SSH_ERR_LIBCRYPTO_ERROR;

	nid = OBJ_txt2nid(param);
	if (nid == NID_undef)
		return SSH_ERR_LIBCRYPTO_ERROR;

	if (sshkey_curve_nid_to_name(nid) == NULL)
		return SSH_ERR_EC_CURVE_INVALID;

	*pnid = nid;
	return 0;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

static int
sshkey_set_nid(EVP_PKEY *pk, int *pnid) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return sshkey_set_prov_param_nid(pk, pnid);
#endif
{	EC_KEY *ec;
	const EC_GROUP *g;

	ec = EVP_PKEY_get1_EC_KEY(pk);
	if (ec == NULL) return SSH_ERR_LIBCRYPTO_ERROR;

	/* indirectly set in sshkey_ecdsa_key_to_nid(if needed)
	   when pkey is completed */
	g = EC_KEY_get0_group(ec);
	EC_KEY_free(ec);
	if (g == NULL) return SSH_ERR_LIBCRYPTO_ERROR;

	*pnid = EC_GROUP_get_curve_name(g);
	return 0;
}
}


#ifdef ENABLE_KEY_PROVIDERS
static int
ssh_EVP_PKEY_check_private_ecprov(EVP_PKEY *pkey, const BIGNUM *exponent) {
	EC_GROUP *group;
	int r;

	group = ssh_EVP_PKEY_prov_get_EC_GROUP(pkey);
	if (group == NULL) return SSH_ERR_ALLOC_FAIL;

	r = ssh_EC_GROUP_check_private(group, exponent);

	EC_GROUP_free(group);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

static inline int
ssh_EC_KEY_check_private(const EC_KEY *ec, const BIGNUM *exponent) {
	const EC_GROUP *group = EC_KEY_get0_group(ec);
	if (group == NULL) return SSH_ERR_INTERNAL_ERROR;

	return ssh_EC_GROUP_check_private(group, exponent);
}


#ifdef ENABLE_KEY_PROVIDERS
static int
sshkey_validate_ecprov_priv(EVP_PKEY *pk, int skip_priv) {
	BIGNUM *exponent = NULL;
	int r;

	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_PRIV_KEY, &exponent) != 1)
		return skip_priv ? 0 : SSH_ERR_INVALID_ARGUMENT;

	r = ssh_EVP_PKEY_check_private_ecprov(pk, exponent);

	BN_free(exponent);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

static inline int
ssh_EC_KEY_validate_private(const EC_KEY *ec, int skip_priv) {
	const BIGNUM *exponent = EC_KEY_get0_private_key(ec);
	if (exponent == NULL)
		return skip_priv ? 0 : SSH_ERR_INVALID_ARGUMENT;

	return ssh_EC_KEY_check_private(ec, exponent);
}


static int
ssh_pkey_validate_private_ecdsa(EVP_PKEY *pk, int skip_priv) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return sshkey_validate_ecprov_priv(pk, skip_priv);
#endif
{	EC_KEY *ec;
	int r;

	ec = EVP_PKEY_get1_EC_KEY(pk);
	if (ec == NULL) return SSH_ERR_INVALID_ARGUMENT;

	r = ssh_EC_KEY_validate_private(ec, skip_priv);

	EC_KEY_free(ec);
	return r;
}
}


static int
get_ecpub(const EC_KEY *key, const u_char *d, size_t len, EC_POINT **valp)
{
	const EC_GROUP *g;
	EC_POINT *v;

	/* Refuse overlong bignums */
	if (len == 0 || len > SSHBUF_MAX_ECPOINT)
		return SSH_ERR_ECPOINT_TOO_LARGE;
	/* Only handle uncompressed points */
	if (*d != POINT_CONVERSION_UNCOMPRESSED)
		return SSH_ERR_INVALID_FORMAT;

	if ((g = EC_KEY_get0_group(key)) == NULL)
		return SSH_ERR_INTERNAL_ERROR;

	if ((v = EC_POINT_new(g)) == NULL) {
		return SSH_ERR_ALLOC_FAIL;
	}
	if (EC_POINT_oct2point(g, v, d, len, NULL) != 1) {
		EC_POINT_clear_free(v);
		return SSH_ERR_INVALID_FORMAT; /* XXX assumption */
	}
	*valp = v;

	return 0;
}


extern int /* see kexecdh.c */
sshbuf_to_ecpub(const struct sshbuf *buf, EVP_PKEY *pk, EC_POINT **valp);

int
sshbuf_to_ecpub(const struct sshbuf *buf, EVP_PKEY *pk, EC_POINT **valp)
{
	EC_KEY *key;
	int r;

	if ((key = EVP_PKEY_get1_EC_KEY(pk)) == NULL)
		return SSH_ERR_INVALID_ARGUMENT;

	r = get_ecpub(key, sshbuf_ptr(buf), sshbuf_len(buf), valp);

	EC_KEY_free(key);
	return r;
}


#ifndef USE_PKEY_FROMDATA
static int
set_ecpub_fromb(struct sshbuf *buf, EC_KEY *v)
{
	EC_POINT *pt = NULL;
	int r;

{	const u_char *d;
	size_t len;

	if ((r = sshbuf_peek_string_direct(buf, &d, &len)) != 0)
		return r;
	if ((r = get_ecpub(v, d, len, &pt)) != 0)
		return r;
	/* Skip string */
	if (sshbuf_get_string_direct(buf, NULL, NULL) != 0) {
		/* Shouldn't happen */
		EC_POINT_free(pt);
		return SSH_ERR_INTERNAL_ERROR;
	}
}
	if (EC_KEY_set_public_key(v, pt) != 1) {
		EC_POINT_free(pt);
		return SSH_ERR_ALLOC_FAIL; /* XXX assumption */
	}
	EC_POINT_free(pt);
	return 0;
}
#endif /*ndef USE_PKEY_FROMDATA*/
#endif /* def WITH_OPENSSL_4_0_API */


/* pkey implementation */

static int
ssh_ecdsa_sshkey_from_pkey(EVP_PKEY *pk, struct sshkey **keyp) {
	int r;
	struct sshkey* key;

	r = ssh_EVP_PKEY_complete_pub_ecdsa(pk);
	if (r != 0) return r;

	key = sshkey_new(KEY_UNSPEC);
	if (key == NULL)
		return SSH_ERR_ALLOC_FAIL;

	key->type = KEY_ECDSA;
	key->pk = pk;

	r = sshkey_set_nid(key->pk, &key->ecdsa_nid);
	if (r != 0) goto err;

	/* private part is not required */
	r = ssh_pkey_validate_private_ecdsa(key->pk, 1);
	if (r != 0) goto err;

	/* success */
	SSHKEY_DUMP(key);
	*keyp = key;
	return 0;

err:
	key->pk = NULL; /* transfer failed */
	sshkey_free(key);
	return r;
}

static int
ssh_ecdsa_validate_public(EVP_PKEY *pk) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return ssh_EVP_PKEY_validate_public_ecprov(pk);
#endif
{	EC_KEY *ec;
	int r;

	ec = EVP_PKEY_get1_EC_KEY(pk);
	if (ec == NULL) return SSH_ERR_INVALID_ARGUMENT;

	r = ssh_EC_KEY_validate_public(ec);

	EC_KEY_free(ec);
	return r;
}
}

static const struct ssh_pkey_impl_funcs
ssh_pkey_ec_funcs = {
	ssh_ecdsa_sshkey_from_pkey,
	ssh_ecdsa_validate_public
};

#define SSH_PKEY_EC_FUNCS	&ssh_pkey_ec_funcs


/* functions that read provider keys */
#ifdef USE_PKEY_FROMDATA
static int
sshbuf_read_prov_ecpub(struct sshbuf *buf, int nid, EVP_PKEY **pkp) {
	EVP_PKEY *pk = NULL;
	size_t pub_len;
	u_char *pub;
	int r;

	*pkp = NULL;

	r = sshbuf_get_string(buf, &pub, &pub_len);
	if (r != 0) return r;

{	const char *curve = OBJ_nid2sn(nid);
	if (curve == NULL) {
		r = SSH_ERR_INVALID_ARGUMENT;
		goto err;
	}

{	OSSL_PARAM params[] = {
		OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (void*)curve, strlen(curve)),
		OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, pub, pub_len),
		OSSL_PARAM_END
	};
	int selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | \
	    OSSL_KEYMGMT_SELECT_PUBLIC_KEY;

	r = ssh_ec_pkey_fromdata(selection, params, &pk);
	if (r != 0) goto err;
}
}

	/* success */
	*pkp = pk;
	pk = NULL;
	r = 0;

err:
	freezero(pub, pub_len);
	EVP_PKEY_free(pk);
	return r;
}
#endif /*def USE_PKEY_FROMDATA*/

#ifdef USE_PKEY_FROMDATA
static int
sshbuf_read_prov_ec(struct sshbuf *buf, int with_public, int nid, EVP_PKEY **pkp) {
	size_t pub_len = 0;
	u_char *pub;
	BIGNUM *exp = NULL;
	EVP_PKEY *pk = NULL;
	int r;

	if (with_public) {
		if (*pkp != NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		r = sshbuf_get_string(buf, &pub, &pub_len);
		if (r != 0) return r;
	} else {
		/* transfer as private versus public match verification
		 * is not reliable and usually fail
		 */
		if (*pkp == NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		pub_len = ssh_EVP_PKEY_get1_encoded_public_key(*pkp, &pub);
		if (pub_len == 0)
			return SSH_ERR_KEY_INVALID_EC_VALUE;
		EVP_PKEY_free(*pkp);
		*pkp = NULL;
	}

	r = sshbuf_get_bignum2(buf, &exp);
	if (r != 0) goto err;

{	const char *curve;
	unsigned char buf_exp[66/*1+(521/8)*/];
	int selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | \
	    OSSL_KEYMGMT_SELECT_KEYPAIR;

	curve = OBJ_nid2sn(nid);
	if (curve == NULL) {
		r = SSH_ERR_INVALID_ARGUMENT;
		goto err;
	}

{	OSSL_PARAM params[] = {
		OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (void*)curve, strlen(curve)),
		OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, pub, pub_len),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_PRIV_KEY, buf_exp, sizeof(buf_exp)),
		OSSL_PARAM_END
	};

	if (!OSSL_PARAM_set_BN(&params[2], exp)) {
		r = SSH_ERR_INVALID_FORMAT;
		goto err_prov;
	}

	r = ssh_ec_pkey_fromdata(selection, params, &pk);

err_prov:
	OPENSSL_cleanse(buf_exp, params[2].data_size);
}
	if (r != 0) goto err;
}

	/* success */
	*pkp = pk;
	pk = NULL;  /* transferred */

err:
	freezero(pub, pub_len);
	BN_clear_free(exp);
	EVP_PKEY_free(pk);
	return r;
}
#endif /*def USE_PKEY_FROMDATA*/


/* functions that read low level (ec) keys */
#ifndef USE_PKEY_FROMDATA
static int
sshbuf_get_ecpub(struct sshbuf *buf, int nid, EC_KEY **ecp)
{
	EC_KEY *ec = NULL;
	int r;

	*ecp = NULL;

	ec = ssh_EC_KEY_new_by_curve_name(nid);
	if (ec == NULL) return SSH_ERR_ALLOC_FAIL;

	r = set_ecpub_fromb(buf, ec);
	if (r != 0) goto err;

	/* do not call ..complete.. as nid is set from curve read and
	 * does not need to be preserved
	 */
	r = ssh_EC_KEY_validate_public(ec);
	if (r != 0) goto err;

	/* success */
	*ecp = ec;
	ec = NULL; /* transferred */

err:
	EC_KEY_free(ec);
	return r;
}
#endif /*ndef USE_PKEY_FROMDATA*/

#ifndef USE_PKEY_FROMDATA
static int
sshbuf_set_ec(struct sshbuf *buf, int with_public, EC_KEY *ec)
{
	BIGNUM *exponent = NULL;
	int r;

	if (!with_public) goto skip_public;

	r = set_ecpub_fromb(buf, ec);
	if (r != 0) goto err;

	/* do not call ..complete.. as nid is set from curve read and
	 * does not need to be preserved
	 */
	r = ssh_EC_KEY_validate_public(ec);
	if (r != 0) goto err;

skip_public:
	r = sshbuf_get_bignum2(buf, &exponent);
	if (r != 0) goto err;

	if (EC_KEY_set_private_key(ec, exponent) != 1) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
	/*no! exponent = NULL; transferred */

	r = ssh_EC_KEY_check_private(ec, exponent);

err:
	BN_clear_free(exponent);
	return r;
}
#endif /*ndef USE_PKEY_FROMDATA*/


/* functions that read pkey(ec) keys */
extern int
/* see regress/unittests/sshbuf/test_sshbuf_getput_crypto.c */
sshbuf_read_pkey_ecpub(struct sshbuf *buf, int nid, EVP_PKEY **pkp);

int
sshbuf_read_pkey_ecpub(struct sshbuf *buf, int nid, EVP_PKEY **pkp)
{
#ifdef USE_PKEY_FROMDATA
	return sshbuf_read_prov_ecpub(buf, nid, pkp);
#else /*ndef USE_PKEY_FROMDATA*/
	EVP_PKEY *pk = NULL;
	EC_KEY *ec = NULL;
	int r;

	*pkp = NULL;

	r = sshbuf_get_ecpub(buf, nid, &ec);
	if (r != 0) goto err;

	pk = EVP_PKEY_new();
	if (pk == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

	if (!EVP_PKEY_set1_EC_KEY(pk, ec)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	/* success */
	*pkp = pk;
	pk = NULL;  /* transferred */

err:
	EC_KEY_free(ec);
	EVP_PKEY_free(pk);
	return r;
#endif /*ndef USE_PKEY_FROMDATA*/
}

static int
sshbuf_read_pkey_ec(struct sshbuf *buf, int with_public, int nid,
    EVP_PKEY **pkp)
{
#ifdef USE_PKEY_FROMDATA
	return sshbuf_read_prov_ec(buf, with_public, nid, pkp);
#else /*ndef USE_PKEY_FROMDATA*/
	EC_KEY *ec = NULL;
	int r;

	/* On OpenSSL before 1.0.2 EVP_PKEY_cmp() crash if one key is
	 * with private part and another is with public part.
	 * Also i2d_PUBKEY() crash as well.
	 * Let use old "split" functionality even on 1.0.2 or newer.
	 */
	if (with_public) {
		if (*pkp != NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		ec = ssh_EC_KEY_new_by_curve_name(nid);
		if (ec == NULL)
			return SSH_ERR_ALLOC_FAIL;
	} else {
		if (*pkp == NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		ec = EVP_PKEY_get1_EC_KEY(*pkp);
		if (ec == NULL)
			return SSH_ERR_LIBCRYPTO_ERROR;
	}

	r = sshbuf_set_ec(buf, with_public, ec);
	if (r != 0) goto err;

	if (with_public) {
		EVP_PKEY *pk = EVP_PKEY_new();
		if (pk == NULL) {
			r = SSH_ERR_ALLOC_FAIL;
			goto err;
		}

		if (!EVP_PKEY_set1_EC_KEY(pk, ec)) {
			r = SSH_ERR_LIBCRYPTO_ERROR;
			EVP_PKEY_free(pk);
			goto err;
		}

		/* success */
		*pkp = pk;
	}

err:
	EC_KEY_free(ec);
	return r;
#endif /*ndef USE_PKEY_FROMDATA*/
}


/* key implementation */

static u_int
ssh_ecdsa_size(const struct sshkey *key)
{
	return (key->pk != NULL) ? EVP_PKEY_bits(key->pk) : 0;
}

static void
ssh_ecdsa_cleanup(struct sshkey *k)
{
	sshkey_clear_pkey(k);
}

static int
ssh_ecdsa_equal(const struct sshkey *a, const struct sshkey *b)
{
	return sshkey_equal_public_pkey(a, b);
}

static int
ssh_ecdsa_serialize_public(const struct sshkey_impl *impl,
    const struct sshkey *key, struct sshbuf *buf)
{
	int r;

	UNUSED(impl);
	if (key->pk == NULL) return SSH_ERR_INVALID_ARGUMENT;

	r = sshbuf_write_ec_curve(buf, key);
	if (r != 0) return r;
	return sshbuf_write_pkey_ecpub(buf, key->pk);
}

static int
ssh_ecdsa_serialize_private(const struct sshkey_impl *impl,
    const struct sshkey *key, struct sshbuf *buf)
{
	int r;

	if (key->pk == NULL) return SSH_ERR_INVALID_ARGUMENT;

	if (!impl->cert) {
		r = sshbuf_write_ec_curve(buf, key);
		if (r != 0) return r;
	}
	return sshbuf_write_pkey_ec(buf, key->pk, !impl->cert);
}

#ifdef USE_EVP_PKEY_KEYGEN
extern int /* see kexecdh.c */
ssh_pkey_keygen_ec(int nid, EVP_PKEY **ret);

int
ssh_pkey_keygen_ec(int nid, EVP_PKEY **ret) {
	EVP_PKEY *pk = NULL;
	EVP_PKEY_CTX *ctx = NULL;
	int r;

	ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
	if (ctx == NULL) return SSH_ERR_ALLOC_FAIL;

	if (EVP_PKEY_keygen_init(ctx) <= 0) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) <= 0) {
		r = SSH_ERR_EC_CURVE_INVALID;
		goto err;
	}

	/* for compatibility reason between OpenSSL releases */
	if (EVP_PKEY_CTX_set_ec_param_enc(ctx, OPENSSL_EC_NAMED_CURVE) <= 0) {
		r = SSH_ERR_EC_CURVE_MISMATCH;
		goto err;
	}

	if (EVP_PKEY_keygen(ctx, &pk) <= 0) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	/* success */
	*ret = pk;
	r = 0;

err:
	EVP_PKEY_CTX_free(ctx);
	return r;
}
#else /*ndef USE_EVP_PKEY_KEYGEN*/
static int
ssh_pkey_ec_generate(int nid, EVP_PKEY **ret) {
	EVP_PKEY *pk;
	EC_KEY *private = NULL;
	int r = 0;

	if ((pk = EVP_PKEY_new()) == NULL ||
	    (private = ssh_EC_KEY_new_by_curve_name(nid)) == NULL
	) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

	if (EC_KEY_generate_key(private) != 1) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	if (!EVP_PKEY_set1_EC_KEY(pk, private)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	/* success */
	*ret = pk;
	pk = NULL;

err:
	EVP_PKEY_free(pk);
	EC_KEY_free(private);
	return r;
}
#endif /*ndef USE_EVP_PKEY_KEYGEN*/

static int
ssh_ecdsa_generate(struct sshkey *key, int bits) {
	EVP_PKEY *pk;
	int r = 0, nid;

	nid = sshkey_curve_bits_to_nid(bits);
	if (nid == NID_undef) return SSH_ERR_KEY_LENGTH;

#ifdef USE_EVP_PKEY_KEYGEN
	r = ssh_pkey_keygen_ec(nid, &pk);
#else
	r = ssh_pkey_ec_generate(nid, &pk);
#endif
	if (r == 0) {
		key->pk = pk;
		key->ecdsa_nid = nid;
	}

	return r;
}

static u_int
ssh_ecdsa_default_bits(const char *name) {
	const struct sshkey_impl *impl;
	int nid;

	impl = (name != NULL)
		? sshkey_impl_from_name(name)
		: NULL;
	/* NOTE: sinc with keygen manual page */
	nid = (impl != NULL)
		? impl->id.subtype
		: NID_X9_62_prime256v1;
	return sshkey_curve_nid_to_bits(nid);
}

static void
ssh_ecdsa_valid_bits(int bits) {
	if (sshkey_curve_bits_to_nid(bits) != NID_undef)
		return;

#ifdef OPENSSL_HAS_NISTP521
	fatal("Invalid ECDSA key length: valid lengths are "
	    "256, 384 or 521 bits");
#else
	fatal("Invalid ECDSA key length: valid lengths are "
	    "256 or 384 bits");
#endif
}

static void
ssh_ecdsa_move_public(struct sshkey *from, struct sshkey *to) {
	sshkey_move_pkey(from, to);
}

static int
ssh_ecdsa_copy_public(const struct sshkey *from, struct sshkey *to) {
	return sshkey_copy_public_pkey(from, to);
}

static int
ssh_ecdsa_deserialize_public(const struct sshkey_impl *impl,
    struct sshbuf *buf, struct sshkey *key)
{
	int r;

	r = sshbuf_read_ec_curve(buf, impl, key);
	if (r != 0) return r;

	r = sshbuf_read_pkey_ecpub(buf, key->ecdsa_nid, &key->pk);
	if (r != 0) return r;

	/* success */
	SSHKEY_DUMP(key);

	return ssh_pkey_public_check(key->pk);
}

static int
ssh_ecdsa_deserialize_private(const struct sshkey_impl *impl,
    struct sshbuf *buf, struct sshkey *key)
{
	int r, with_public = !impl->cert;

	if (with_public) {
		r = sshbuf_read_ec_curve(buf, impl, key);
		if (r != 0) return r;
	}

	r = sshbuf_read_pkey_ec(buf, with_public, key->ecdsa_nid, &key->pk);
	if (r != 0) return r;

	SSHKEY_DUMP(key);

	return ssh_pkey_check(key->pk);
}

static int
ssh_ecdsa_sign(const ssh_sign_ctx *ctx, u_char **sigp, size_t *lenp,
    const u_char *data, size_t datalen)
{
	const struct sshkey *key = ctx->key;
	const ssh_evp_md *dgst;
	u_char sigblob[20+2*64/*SHA512_DIGEST_LENGTH*/];
	size_t siglen = sizeof(sigblob);
	int ret;

	if (lenp != NULL)
		*lenp = 0;
	if (sigp != NULL)
		*sigp = NULL;

	if (key == NULL) return SSH_ERR_INVALID_ARGUMENT;

	dgst = ssh_ecdsa_dgst(key);
	if (dgst == NULL) return SSH_ERR_INTERNAL_ERROR;

	ret = ssh_ecdsa_validate_public(key->pk);
	if (ret != 0) return ret;

	if (ssh_pkey_sign(dgst, key->pk, sigblob, &siglen, data, datalen) <= 0) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto out;
	}

	ret = ssh_encode_signature(sigp, lenp,
	    sshkey_ssh_name_plain(key), sigblob, siglen);

 out:
	return ret;
}

static int
ssh_ecdsa_verify(const ssh_verify_ctx *ctx,
    const u_char *sig, size_t siglen,
    const u_char *data, size_t datalen)
{
	const struct sshkey *key = ctx->key;
	const ssh_evp_md *dgst;
	struct sshbuf *b = NULL;
	char *ktype = NULL;
	const u_char *sigblob;
	size_t len;
	int ret;

	if (sig == NULL || siglen == 0)
		return SSH_ERR_INVALID_ARGUMENT;

	if (key == NULL) return SSH_ERR_INVALID_ARGUMENT;

	dgst = ssh_ecdsa_dgst(key);
	if (dgst == NULL) return SSH_ERR_INTERNAL_ERROR;

	ret = ssh_ecdsa_validate_public(key->pk);
	if (ret != 0) return ret;

	/* fetch signature */
	if ((b = sshbuf_from(sig, siglen)) == NULL)
		return SSH_ERR_ALLOC_FAIL;
	if (sshbuf_get_cstring(b, &ktype, NULL) != 0 ||
	    sshbuf_get_string_direct(b, &sigblob, &len) != 0) {
		ret = SSH_ERR_INVALID_FORMAT;
		goto out;
	}
	if (strcmp(sshkey_ssh_name_plain(key), ktype) != 0) {
		ret = SSH_ERR_KEY_TYPE_MISMATCH;
		goto out;
	}
	if (sshbuf_len(b) != 0) {
		ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
		goto out;
	}

	ret = ssh_pkey_verify_r(dgst, key->pk,
	    sigblob, len, data, datalen);

 out:
	sshbuf_free(b);
	free(ktype);
	return ret;
}

static const struct sshkey_impl_funcs sshkey_ecdsa_funcs = {
	/* .size = */		ssh_ecdsa_size,
	/* .alloc =		NULL, */
	/* .cleanup = */	ssh_ecdsa_cleanup,
	/* .equal = */		ssh_ecdsa_equal,
	/* .serialize_public = */	ssh_ecdsa_serialize_public,
	/* .deserialize_public = */	ssh_ecdsa_deserialize_public,
	/* .serialize_private = */	ssh_ecdsa_serialize_private,
	/* .deserialize_private = */	ssh_ecdsa_deserialize_private,
	/* .generate = */	ssh_ecdsa_generate,
	/* .default_bits = */	ssh_ecdsa_default_bits,
	/* .valid_bits = */	ssh_ecdsa_valid_bits,
	/* .move_public = */	ssh_ecdsa_move_public,
	/* .copy_public = */	ssh_ecdsa_copy_public,
	/* .sign = */		ssh_ecdsa_sign,
	/* .verify = */		ssh_ecdsa_verify
};

static int sshkey_ecdsa_enabled(void) { return 1; }
static int sshkey_ecdsa_nistp521_enabled(void) {
#ifdef OPENSSL_HAS_NISTP521
	return 1;
#else
	return 0;
#endif
}
#define SSHKEY_ECDSA_FUNCS	&sshkey_ecdsa_funcs

#else

#include "sshxkey.h"

#undef NID_X9_62_prime256v1
#define NID_X9_62_prime256v1	NID_undef
#undef NID_secp384r1
#define NID_secp384r1		NID_undef
#undef NID_secp521r1
#define NID_secp521r1		NID_undef

static int sshkey_ecdsa_enabled(void) { return 0; }
static int sshkey_ecdsa_nistp521_enabled(void) { return 0; }
#define SSHKEY_ECDSA_FUNCS	NULL
#define SSH_PKEY_EC_FUNCS	NULL

#endif /* WITH_OPENSSL && OPENSSL_HAS_ECC */

static const char *pkey_ec_typename[] = { "EC", NULL };

const struct sshkey_impl sshkey_ecdsa_nistp256_impl = {
	/* .id = */		{ KEY_ECDSA, NID_X9_62_prime256v1,
				  EVP_PKEY_EC, pkey_ec_typename },
	/* .name = */		"ecdsa-sha2-nistp256",
	/* .shortname = */	"ECDSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_ecdsa_enabled,
	/* .funcs = */		SSHKEY_ECDSA_FUNCS
};

const struct sshkey_impl sshkey_ecdsa_nistp256_cert_impl = {
	/* .id = */		{ KEY_ECDSA_CERT, NID_X9_62_prime256v1,
				  EVP_PKEY_EC, pkey_ec_typename },
	/* .name = */		"ecdsa-sha2-nistp256-cert-v01@openssh.com",
	/* .shortname = */	"ECDSA-CERT",
	/* .sigalg = */		NULL,
	/* .cert = */		1,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_ecdsa_enabled,
	/* .funcs = */		SSHKEY_ECDSA_FUNCS
};

const struct sshkey_impl sshkey_ecdsa_nistp384_impl = {
	/* .id = */		{ KEY_ECDSA, NID_secp384r1,
				  EVP_PKEY_EC, pkey_ec_typename },
	/* .name = */		"ecdsa-sha2-nistp384",
	/* .shortname = */	"ECDSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_ecdsa_enabled,
	/* .funcs = */		SSHKEY_ECDSA_FUNCS
};

const struct sshkey_impl sshkey_ecdsa_nistp384_cert_impl = {
	/* .id = */		{ KEY_ECDSA_CERT, NID_secp384r1,
				  EVP_PKEY_EC, pkey_ec_typename },
	/* .name = */		"ecdsa-sha2-nistp384-cert-v01@openssh.com",
	/* .shortname = */	"ECDSA-CERT",
	/* .sigalg = */		NULL,
	/* .cert = */		1,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_ecdsa_enabled,
	/* .funcs = */		SSHKEY_ECDSA_FUNCS
};

const struct sshkey_impl sshkey_ecdsa_nistp521_impl = {
	/* .id = */		{ KEY_ECDSA, NID_secp521r1,
				  EVP_PKEY_EC, pkey_ec_typename },
	/* .name = */		"ecdsa-sha2-nistp521",
	/* .shortname = */	"ECDSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_ecdsa_nistp521_enabled,
	/* .funcs = */		SSHKEY_ECDSA_FUNCS
};

const struct sshkey_impl sshkey_ecdsa_nistp521_cert_impl = {
	/* .id = */		{ KEY_ECDSA_CERT, NID_secp521r1,
				  EVP_PKEY_EC, pkey_ec_typename },
	/* .name = */		"ecdsa-sha2-nistp521-cert-v01@openssh.com",
	/* .shortname = */	"ECDSA-CERT",
	/* .sigalg = */		NULL,
	/* .cert = */		1,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_ecdsa_nistp521_enabled,
	/* .funcs = */		SSHKEY_ECDSA_FUNCS
};

const struct ssh_pkey_impl ssh_pkey_ec_impl = {
	pkey_ec_typename,
	EVP_PKEY_EC,
	SSH_PKEY_EC_FUNCS
};
