/*
 * 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.
 */

#include "includes.h"

#ifdef WITH_OPENSSL
#include "evp-compat.h"
#include <openssl/pem.h>
#include <openssl/err.h>
#ifdef HAVE_EVP_KEYMGMT_FETCH
# include <openssl/provider.h>
# include <openssl/core_names.h>
#endif

#include <string.h>	/*for memcpy*/

#define SSHKEY_INTERNAL
#include "ssh-x509.h"
#include "compat.h"
#include "ssherr.h"
#include "crypto_api.h" /*for some Ed25519 defines */
#include "xmalloc.h"
#include "log.h"


#ifdef ENABLE_KEY_PROVIDERS
extern OSSL_LIB_CTX *pkixssh_libctx;
extern char *pkixssh_propq;
#endif


#if 1
/* compare "non-exportable" keys with mixed management
 * (OpenSSL issue #26394)
 * Function EVP_PKEY_eq() fail to compare key when private
 * part is not exportable and one of them is manageet by provider
 * and another one uses "classical" key method.
 */
# undef EVP_PKEY_EQ_MIXED_BUG
# define EVP_PKEY_EQ_MIXED_BUG
#endif

#if 0
# define TRACE_EVP_ERROR(msg) error_crypto(msg)
#else
# define TRACE_EVP_ERROR(msg)
#endif


#ifndef HAVE_EVP_DIGESTSIGNINIT		/* OpenSSL < 1.0 */
static inline int
EVP_DigestSignUpdate(EVP_MD_CTX *ctx, const void *data, size_t datalen) {
# if OPENSSL_VERSION_NUMBER < 0x00908000L
{
	u_int dlen = datalen;
	if ((size_t)dlen != datalen) return -1;
	return EVP_SignUpdate(ctx, data, dlen);
}
# else
	return EVP_SignUpdate(ctx, data, datalen);
# endif
}
#endif

#ifndef HAVE_EVP_DIGESTSIGNINIT		/* OpenSSL < 1.0 */
static inline int
EVP_DigestVerifyUpdate(EVP_MD_CTX *ctx, const void *data, size_t datalen) {
# if OPENSSL_VERSION_NUMBER < 0x00908000L
	u_int dlen = datalen;
	if ((size_t)dlen != datalen) return -1;
	return EVP_VerifyUpdate(ctx, data, dlen);
# else
	return EVP_VerifyUpdate(ctx, data, datalen);
# endif
}
#endif


#ifndef HAVE_DSA_SIG_GET0		/* OpenSSL < 1.1 */
static inline void
DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) {
	if (pr != NULL) *pr = sig->r;
	if (ps != NULL) *ps = sig->s;
}
#endif /*ndef HAVE_DSA_SIG_GET0	OpenSSL < 1.1 */

#ifndef HAVE_DSA_SIG_SET0		/* OpenSSL < 1.1 */
static inline int/*bool*/
DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
	if (r == NULL || s == NULL) return 0;

	BN_clear_free(sig->r);
	BN_clear_free(sig->s);

	sig->r = r;
	sig->s = s;
	return 1;
}
#endif /*ndef HAVE_DSA_SIG_SET0	OpenSSL < 1.1 */

#ifdef OPENSSL_HAS_ECC
#ifndef HAVE_ECDSA_SIG_GET0		/* OpenSSL < 1.1 */
static inline void
ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) {
    if (pr != NULL) *pr = sig->r;
    if (ps != NULL) *ps = sig->s;
}
#endif /*ndef HAVE_ECDSA_SIG_GET0	OpenSSL < 1.1 */

#ifndef HAVE_ECDSA_SIG_SET0		/* OpenSSL < 1.1 */
static inline int/*bool*/
ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
	if (r == NULL || s == NULL) return 0;

	BN_clear_free(sig->r);
	BN_clear_free(sig->s);

	sig->r = r;
	sig->s = s;
	return 1;
}
#endif /*ndef HAVE_ECDSA_SIG_SET0	OpenSSL < 1.1 */
#endif /*def OPENSSL_HAS_ECC*/

#ifndef HAVE_EVP_DSS1
# ifdef WITH_DSA
/* removed in OpenSSL 1.1 */
static inline const EVP_MD* EVP_dss1(void) { return EVP_sha1(); }
# endif
#endif


void
ssh_EVP_PKEY_print_private_fp(FILE *fp, const EVP_PKEY *pkey) {
#ifdef HAVE_EVP_PKEY_PRINT_PRIVATE_FP		/* OpenSSL >= 3.0 */
/* NOTE OpenSSL 3.0 regression - for some key types like EC and DSA,
 * print_private fail to output key material if private part is missing.
 * For more details see OpenSSL issue #27547.
 * As work-around call "print_public" as well.
 */
	EVP_PKEY_print_private_fp(fp, pkey, 0, NULL);
	EVP_PKEY_print_public_fp(fp, pkey, 0, NULL);
#elif defined(HAVE_EVP_PKEY_PRINT_PARAMS)	/* OpenSSL >= 1.0.0 */
{	/* OpenSSL lacks print to file stream */
	BIO *bio = BIO_new_fp(fp, BIO_NOCLOSE);
#ifdef VMS
	{	BIO *tmpbio = BIO_new(BIO_f_linebuffer());
		bio = BIO_push(tmpbio, bio);
	}
#endif

	EVP_PKEY_print_private(bio, pkey, 0, NULL);
	BIO_free_all(bio);
}
#else /*ndef HAVE_EVP_PKEY_PRINT_PARAMS*/
{
	int evp_id = EVP_PKEY_base_id(pkey);

	switch (evp_id) {
	case EVP_PKEY_RSA: {
		/* use safe cast to simplify code */
		RSA *rsa = EVP_PKEY_get1_RSA((EVP_PKEY*)pkey);
		RSA_print_fp(fp, rsa, 0);
		RSA_free(rsa);
		} break;
#ifdef OPENSSL_HAS_ECC
	case EVP_PKEY_EC: {
		/* use safe cast to simplify code */
		EC_KEY *ec = EVP_PKEY_get1_EC_KEY((EVP_PKEY*)pkey);
		EC_KEY_print_fp(fp, ec, 0);
		EC_KEY_free(ec);
		} break;
#endif /* OPENSSL_HAS_ECC */
#ifdef WITH_DSA
	case EVP_PKEY_DSA: {
		/* use safe cast to simplify code */
		DSA *dsa = EVP_PKEY_get1_DSA((EVP_PKEY*)pkey);
		DSA_print_fp(fp, dsa, 0);
		DSA_free(dsa);
		} break;
#endif
	}
}
#endif /*ndef HAVE_EVP_PKEY_PRINT_PARAMS*/
}

#ifdef DEBUG_PK
static void
sshkey_dump(const char *func, const struct sshkey *key) {
	fprintf(stderr, "dump key %s():\n", func);
	ssh_EVP_PKEY_print_private_fp(stderr, key->pk);
}
#endif /* DEBUG_PK */


#ifdef USE_EVP_PKEY_KEYGEN
int
ssh_pkey_keygen_simple(int type, EVP_PKEY **ret) {
	EVP_PKEY *pk = NULL;
	EVP_PKEY_CTX *ctx = NULL;
	int r;

	ctx = EVP_PKEY_CTX_new_id(type, 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_keygen(ctx, &pk) <= 0) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

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

err:
	EVP_PKEY_CTX_free(ctx);
	return r;
}
#endif /*def USE_EVP_PKEY_KEYGEN*/


#ifdef USE_PKEY_FROMDATA
int
ssh_pkey_fromdata(const char *name, int selection, OSSL_PARAM *params,
    EVP_PKEY **pkeyp)
{
	int r;
	EVP_PKEY_CTX *ctx = NULL;

	ctx = EVP_PKEY_CTX_new_from_name(pkixssh_libctx, name, pkixssh_propq);
	if (ctx == NULL) return SSH_ERR_ALLOC_FAIL;

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

	if (EVP_PKEY_fromdata(ctx, pkeyp, selection, params) <= 0) {
	#if 0	/* silent fuzz unit tests */
		error_crypto("PKEY_FROMDATA");
	#endif
		r = SSH_ERR_LIBCRYPTO_ERROR;
	} else {
		/* success */
		r = 0;
	}

err:
	EVP_PKEY_CTX_free(ctx);
	return r;
}
#endif /*def USE_PKEY_FROMDATA*/


int
sshkey_from_pkey(EVP_PKEY *pk, struct sshkey **keyp) {
	const struct ssh_pkey_impl *impl;

	/* NOTE do not set flags |= SSHKEY_FLAG_EXT !!! */
#ifdef ENABLE_KEY_PROVIDERS
{	const char *s = EVP_PKEY_get0_type_name(pk);
	if (s == NULL) goto try_base_id;

	impl = ssh_pkey_impl_from_type_name(s);
	if (impl == NULL || impl->funcs == NULL) {
		error_f("unsupported pkey name '%s'", s);
		return SSH_ERR_KEY_TYPE_UNKNOWN;
	}
	return impl->funcs->sshkey_from_pkey(pk, keyp);
}
try_base_id:
#endif /*def ENABLE_KEY_PROVIDERS*/
{	int evp_id = EVP_PKEY_base_id(pk);
	impl = ssh_pkey_impl_from_evp_id(evp_id);
	if (impl == NULL || impl->funcs == NULL) {
		error_f("unsupported pkey type %d", evp_id);
		return SSH_ERR_KEY_TYPE_UNKNOWN;
	}
}

	return impl->funcs->sshkey_from_pkey(pk, keyp);
}


int
sshbuf_parse_private_pem(struct sshbuf *blob,
    const char *passphrase, struct sshkey **keyp)
{
	EVP_PKEY *pk = NULL;
	struct sshkey *prv = NULL;
	BIO *bio = NULL;
	int r;

	if (keyp != NULL)
		*keyp = NULL;

	debug3("read PEM private key begin");
	if (sshbuf_len(blob) == 0 || sshbuf_len(blob) > INT_MAX)
		return SSH_ERR_INVALID_ARGUMENT;
	bio = BIO_new_mem_buf(sshbuf_mutable_ptr(blob), sshbuf_len(blob));
	if (bio == NULL )
		return SSH_ERR_ALLOC_FAIL;

	ERR_clear_error();
	if ((pk = PEM_read_bio_PrivateKey(bio, NULL, NULL,
	    (char *)passphrase)) == NULL) {
		debug3("read PEM private key fail");
		do_log_crypto_errors(SYSLOG_LEVEL_DEBUG3);
		r = SSH_ERR_KEY_WRONG_PASSPHRASE;
		goto out;
	}
	r = sshkey_from_pkey(pk, &prv);
	if (r != 0) goto out;
	pk = NULL; /* transferred */

	BIO_free(bio);
	bio = BIO_new_mem_buf(sshbuf_mutable_ptr(blob), sshbuf_len(blob));
	if (bio == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto out;
	}
	x509key_parse_cert(prv, bio);

	r = 0;
	if (keyp != NULL) {
		*keyp = prv;
		prv = NULL;
	}
 out:
	BIO_free(bio);
	EVP_PKEY_free(pk);
	sshkey_free(prv);
	debug3("read PEM private key done: %d", r);
	return r;
}


void
sshkey_clear_pkey(struct sshkey *key) {
	EVP_PKEY_free(key->pk);
	key->pk = NULL;
}


void
sshkey_move_pkey(struct sshkey *from, struct sshkey *to) {
	EVP_PKEY_free(to->pk);
	to->pk = from->pk;
	to->ecdsa_nid = from->ecdsa_nid;
	from->pk = NULL;
	from->ecdsa_nid = -1;
	SSHKEY_DUMP(to);
}


#ifndef HAVE_EVP_PKEY_CMP	/* OpenSSL < 0.9.8 */
extern int ssh_EVP_PKEY_cmp_rsa(const EVP_PKEY *a, const EVP_PKEY *b);
#ifdef WITH_DSA
extern int ssh_EVP_PKEY_cmp_dsa(const EVP_PKEY *a, const EVP_PKEY *b);
#endif

static int
EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b) {
	int evp_id = EVP_PKEY_base_id(a);

	switch (evp_id) {
	case EVP_PKEY_RSA:	return ssh_EVP_PKEY_cmp_rsa(a, b);
#ifdef WITH_DSA
	case EVP_PKEY_DSA:	return ssh_EVP_PKEY_cmp_dsa(a, b);
#endif
	}
	return -2;
}
#endif /*ndef HAVE_EVP_PKEY_CMP*/

int
ssh_EVP_PKEY_eq(const EVP_PKEY *a, const EVP_PKEY *b) {
#ifdef HAVE_EVP_PKEY_EQ			/* OpenSSL >= 3.0 */
#  ifdef EVP_PKEY_EQ_MIXED_BUG
{	int ta = (EVP_PKEY_get0_provider(a) != NULL);

	if (ta == (EVP_PKEY_get0_provider(b) != NULL))
		return EVP_PKEY_eq(a, b);

/* Convert to key managed by default provider and then compare.
 * Note that some providers fail to export public part.
 * Another point is that public public part depends on key type i.e.,
 * it is quite complex to write "compare" for all key types.
 * The code below relies on provider key-management "match.
 */
{	int r;
	int len;
	unsigned char *data = NULL;
	EVP_PKEY *p;

	len = i2d_PUBKEY((ta ? b : a), &data);
	if (len < 0) return -2;

	{
		const unsigned char *q = data;
		p = d2i_PUBKEY(NULL, &q, len);
	}
	if (p == NULL) return -2;

	r = EVP_PKEY_eq((ta ? a : p), (ta ? p : b));

	OPENSSL_free(data);
	EVP_PKEY_free(p);
	return r;
}
}
#  else /*def EVP_PKEY_EQ_MIXED_BUG*/
	return EVP_PKEY_eq(a, b);
#  endif
#else
	return EVP_PKEY_cmp(a, b);
#endif
}

int/*bool*/
sshkey_equal_public_pkey(const struct sshkey *ka, const struct sshkey *kb) {
	const EVP_PKEY *a, *b;

	if (ka == NULL) return 0;
	if (kb == NULL) return 0;

	a = ka->pk;
	if (a == NULL) return 0;

	b = kb->pk;
	if (b == NULL) return 0;

	return ssh_EVP_PKEY_eq(a, b) == 1;
}


static int/*bool*/
sshkey_private_to_bio_traditional(struct sshkey *key, BIO *bio,
    const EVP_CIPHER *cipher, u_char *_passphrase, int len
) {
#ifdef HAVE_PEM_WRITE_BIO_PRIVATEKEY_TRADITIONAL
	return PEM_write_bio_PrivateKey_traditional(bio, key->pk,
	    cipher, _passphrase, len, NULL, NULL);
#else
{	int res;

	switch (key->type) {
	case KEY_RSA: {
		RSA *rsa = EVP_PKEY_get1_RSA(key->pk);
		res = PEM_write_bio_RSAPrivateKey(bio, rsa,
		    cipher, _passphrase, len, NULL, NULL);
		RSA_free(rsa);
		} break;
#ifdef OPENSSL_HAS_ECC
	case KEY_ECDSA: {
		EC_KEY *ec = EVP_PKEY_get1_EC_KEY(key->pk);
		res = PEM_write_bio_ECPrivateKey(bio, ec,
		    cipher, _passphrase, len, NULL, NULL);
		EC_KEY_free(ec);
		} break;
#endif
#ifdef WITH_DSA
	case KEY_DSA: {
		DSA *dsa = EVP_PKEY_get1_DSA(key->pk);
		res = PEM_write_bio_DSAPrivateKey(bio, dsa,
		    cipher, _passphrase, len, NULL, NULL);
		DSA_free(dsa);
		} break;
#endif
	default:
		debug3_f("unsupported key type: %d", key->type);
		res = 0;
	}
	return res;
}
#endif
}


/* write identity in PEM formats - PKCS#8 or Traditional */
int
sshkey_private_to_bio(struct sshkey *key, BIO *bio,
    const char *passphrase, int format)
{
	int res;
	int len = strlen(passphrase);
	const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_256_cbc() : NULL;
	u_char *_passphrase = (len > 0) ? (u_char*)passphrase : NULL;

	if (key->pk == NULL)
		return SSH_ERR_INVALID_ARGUMENT;
	if (len > 0 && len <= 4)
		return SSH_ERR_PASSPHRASE_TOO_SHORT;
	if (len > INT_MAX)
		return SSH_ERR_INVALID_ARGUMENT;

	if (format == SSHKEY_PRIVATE_PEM)
		res = sshkey_private_to_bio_traditional(key, bio, cipher,
		    _passphrase, len);
	else
		res = PEM_write_bio_PKCS8PrivateKey(bio, key->pk, cipher,
		    _passphrase, len, NULL, NULL);

	if (res && sshkey_is_x509(key))
		res = x509key_write_identity_bio_pem(bio, key);

	return res ? 0 : SSH_ERR_LIBCRYPTO_ERROR;
}


/* methods used locally only in ssh-keygen.c */
extern int
sshkey_public_to_fp(struct sshkey *key, FILE *fp, int format);
extern int
sshkey_public_from_fp(FILE *fp, int format, struct sshkey **key);

/* see ssh-rsa.c */
extern int
ssh_rsa_public_to_fp_traditional(struct sshkey *key, FILE *fp);
extern int
ssh_rsa_public_from_fp_traditional(FILE *fp, struct sshkey **key);


int
sshkey_public_to_fp(struct sshkey *key, FILE *fp, int format) {
	int res;

	if ((format != SSHKEY_PRIVATE_PEM) &&
	    (format != SSHKEY_PRIVATE_PKCS8))
		return SSH_ERR_INVALID_ARGUMENT;

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

	if ((format == SSHKEY_PRIVATE_PEM) &&
	    /* Traditional PEM is available only for RSA */
	    (key->type == KEY_RSA)
	)
		res = ssh_rsa_public_to_fp_traditional(key, fp);
	else
		res = PEM_write_PUBKEY(fp, key->pk);

	return res ? 0 : SSH_ERR_LIBCRYPTO_ERROR;
}

int
sshkey_public_from_fp(FILE *fp, int format, struct sshkey **key) {
	int r;

	if (format == SSHKEY_PRIVATE_PKCS8) {
		EVP_PKEY *pk = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
		if (pk != NULL) {
			r = sshkey_from_pkey(pk, key);
			if (r == 0)
				pk = NULL; /* transferred */
		} else
		    r = SSH_ERR_INVALID_FORMAT;
		EVP_PKEY_free(pk);
		return r;
	}

	if (format != SSHKEY_PRIVATE_PEM)
		return SSH_ERR_INVALID_ARGUMENT;

	/* Traditional PEM is available only for RSA */
	return ssh_rsa_public_from_fp_traditional(fp, key);
}


/* digest compatibility */
#undef WRAP_OPENSSL_EC_EVP_SHA256
#undef WRAP_OPENSSL_EC_EVP_SHA384
#undef WRAP_OPENSSL_EC_EVP_SHA512

#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10000000L)
/* work-arounds for limited EVP digests in OpenSSL 0.9.8* ...
 * (missing ecdsa support)
 */

#if defined(OPENSSL_HAS_NISTP256) || defined(OPENSSL_HAS_NISTP384) || defined(OPENSSL_HAS_NISTP521)
static inline void
ssh_EVP_MD_ecdsa_init(EVP_MD *t, const EVP_MD *s) {
    memcpy(t, s, sizeof(*t));
    t->sign = (evp_sign_method*)ECDSA_sign;
    t->verify = (evp_verify_method*)ECDSA_verify;
    t->required_pkey_type[0] = EVP_PKEY_EC;
    t->required_pkey_type[1] = 0;
}
#endif


#ifdef OPENSSL_HAS_NISTP256
# define WRAP_OPENSSL_EC_EVP_SHA256
/* Test for NID_X9_62_prime256v1(nistp256) includes test for EVP_sha256 */
static EVP_MD ecdsa_sha256_md = { NID_undef };

static inline const EVP_MD*
ssh_ecdsa_EVP_sha256(void) {
    if (ecdsa_sha256_md.type == NID_undef)
	ssh_EVP_MD_ecdsa_init(&ecdsa_sha256_md, EVP_sha256());
    return &ecdsa_sha256_md;
}
#endif

#ifdef OPENSSL_HAS_NISTP384
# define WRAP_OPENSSL_EC_EVP_SHA384
/* Test for NID_secp384r1(nistp384) includes test for EVP_sha384 */
static EVP_MD ecdsa_sha384_md = { NID_undef };

static inline const EVP_MD*
ssh_ecdsa_EVP_sha384(void) {
    if (ecdsa_sha384_md.type == NID_undef)
	ssh_EVP_MD_ecdsa_init(&ecdsa_sha384_md, EVP_sha384());
    return &ecdsa_sha384_md;
}
#endif

#ifdef OPENSSL_HAS_NISTP521
# define WRAP_OPENSSL_EC_EVP_SHA512
/* Test for NID_secp521r1(nistp521) includes test for EVP_sha512 */
static EVP_MD ecdsa_sha512_md = { NID_undef };

static inline const EVP_MD*
ssh_ecdsa_EVP_sha512(void) {
    if (ecdsa_sha512_md.type == NID_undef)
	ssh_EVP_MD_ecdsa_init(&ecdsa_sha512_md, EVP_sha512());
    return &ecdsa_sha512_md;
}
#endif

#endif /*defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10000000L)*/

#if defined(OPENSSL_HAS_NISTP256) && !defined(WRAP_OPENSSL_EC_EVP_SHA256)
static inline const EVP_MD* ssh_ecdsa_EVP_sha256(void) { return EVP_sha256(); }
#endif
#if defined(OPENSSL_HAS_NISTP384) && !defined(WRAP_OPENSSL_EC_EVP_SHA384)
static inline const EVP_MD* ssh_ecdsa_EVP_sha384(void) { return EVP_sha384(); }
#endif
#if defined(OPENSSL_HAS_NISTP521) && !defined(WRAP_OPENSSL_EC_EVP_SHA512)
static inline const EVP_MD* ssh_ecdsa_EVP_sha512(void) { return EVP_sha512(); }
#endif



#ifdef HAVE_EVP_DIGESTSIGN
static inline const EVP_MD* ssh_EVP_none(void) { return NULL; }
#endif


#ifdef HAVE_EVP_DIGESTSIGNINIT
static inline int
SSH_SignFinal(EVP_MD_CTX *ctx, unsigned char *sig, size_t *siglen) {
	return EVP_DigestSignFinal(ctx, sig, siglen);
}
static inline int
SSH_VerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sig, size_t siglen) {
	/*safe cast for OpenSSL < 1.1*/
	return EVP_DigestVerifyFinal(ctx, (unsigned char *)sig, siglen);
}
#else
static inline int
SSH_SignFinal(EVP_MD_CTX *ctx, unsigned char *md, size_t *s, EVP_PKEY *pkey) {
	unsigned int t;
	int ret = EVP_SignFinal(ctx, md, &t, pkey);
	*s = t;
	return ret;
}
static inline int
SSH_VerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sigbuf, size_t siglen_t, EVP_PKEY *pkey) {
	unsigned int siglen = siglen_t;
	/* paranoid check */
	if ((size_t)siglen != siglen_t) return -1;
# if OPENSSL_VERSION_NUMBER < 0x00908000L
	return EVP_VerifyFinal(ctx, (unsigned char*)sigbuf, siglen, pkey);
# else
	return EVP_VerifyFinal(ctx, sigbuf, siglen, pkey);
# endif
}
#endif


#ifdef HAVE_EVP_DIGESTSIGNINIT	/* OpenSSL >= 1.0 */
static inline int
buf_EVP_DigestSignFinal(EVP_MD_CTX *ctx,
    unsigned char *sigret, size_t maxlen, size_t *siglen
) {
	int ret = EVP_DigestSignFinal(ctx, NULL, siglen);
	if (ret <= 0) return ret;

	/* paranoid check */
	if (*siglen > maxlen) return -1;

	return EVP_DigestSignFinal(ctx, sigret, siglen);
}
#endif /*def HAVE_EVP_DIGESTSIGNINIT**/


#define SHARAW_DIGEST_LENGTH (2*SHA_DIGEST_LENGTH)

#ifdef WITH_DSA
static int
DSS1RAW_SignFinal(
#ifdef HAVE_EVP_DIGESTSIGNINIT
	EVP_MD_CTX *ctx, unsigned char *sigret, size_t *siglen
#else
	EVP_MD_CTX *ctx, unsigned char *sigret, size_t *siglen, EVP_PKEY *pkey
#endif
) {
	DSA_SIG *sig;
	size_t len;

{	int ret;
	unsigned char buf[20+2*(SHA_DIGEST_LENGTH)];

#ifdef HAVE_EVP_DIGESTSIGNINIT
	ret = buf_EVP_DigestSignFinal(ctx, buf, sizeof(buf), &len);
	if (ret <= 0) return ret;
#else
	/* NOTE: Function EVP_SignFinal() in OpenSSL before 1.0 does not
	 * return signature length if signature argument is NULL.
	 */
{	unsigned int t;
	ret = EVP_SignFinal(ctx, buf, &t, pkey);
	len = t;
}
	if (ret <= 0) return ret;
#endif

{	/* decode DSA signature */
	const unsigned char *psig = buf;
	sig = d2i_DSA_SIG(NULL, &psig, (long)/*safe cast*/len);
}

	if (sig == NULL) return -1;
}

/* encode DSA r&s into SecSH signature blob */
{	u_int rlen, slen;
	const BIGNUM *ps, *pr;

	DSA_SIG_get0(sig, &pr, &ps);

	rlen = BN_num_bytes(pr);
	slen = BN_num_bytes(ps);

	if (rlen > SHA_DIGEST_LENGTH || slen > SHA_DIGEST_LENGTH) {
		error_f("bad sig size %u %u", rlen, slen);
		goto parse_err;
	}

	/* NULL if caller checks for signature buffer size */
	if (sigret != NULL) {
		explicit_bzero(sigret, SHARAW_DIGEST_LENGTH);
		BN_bn2bin(pr, sigret + SHARAW_DIGEST_LENGTH - SHA_DIGEST_LENGTH - rlen);
		BN_bn2bin(ps, sigret + SHARAW_DIGEST_LENGTH - slen);
	}
	*siglen = SHARAW_DIGEST_LENGTH;

	DSA_SIG_free(sig);
	return 1;

parse_err:
	DSA_SIG_free(sig);
	return -1;
}
}
#endif /*ifdef WITH_DSA*/


#ifdef WITH_DSA
static int
DSS1RAW_VerifyFinal(
#ifdef HAVE_EVP_DIGESTSIGNINIT
EVP_MD_CTX *ctx, const unsigned char *sigbuf, size_t siglen
#else
EVP_MD_CTX *ctx, const unsigned char *sigbuf, size_t siglen, EVP_PKEY *pkey
#endif
) {
	DSA_SIG *sig;

	if (siglen != SHARAW_DIGEST_LENGTH) return -1;

/* decode DSA r&s from SecSH signature blob */
{	BIGNUM *ps, *pr;

	pr = BN_bin2bn(sigbuf                  , SHA_DIGEST_LENGTH, NULL);
	ps = BN_bin2bn(sigbuf+SHA_DIGEST_LENGTH, SHA_DIGEST_LENGTH, NULL);
	if ((pr == NULL) || (ps == NULL)) goto parse_err;

	sig = DSA_SIG_new();
	if (sig == NULL) goto parse_err;

	if (DSA_SIG_set0(sig, pr, ps))
		goto process;

	DSA_SIG_free(sig);

parse_err:
	BN_free(pr);
	BN_free(ps);
	return -1;
}

process:
{	int len, slen;
	unsigned char *buf;
	int ret;

	len = i2d_DSA_SIG(sig, NULL);
	if (len <= 0) {
		DSA_SIG_free(sig);
		return -1;
	}

	buf = xmalloc(len);  /*fatal on error*/

{	/* encode DSA signature */
	unsigned char *pbuf = buf;
	slen = i2d_DSA_SIG(sig, &pbuf);
}

	ret = (len == slen)
#ifdef HAVE_EVP_DIGESTSIGNINIT
		? EVP_DigestVerifyFinal(ctx, buf, len)
#else
		? EVP_VerifyFinal(ctx, buf, len, pkey)
#endif
		: -1;

	freezero(buf, len);
	DSA_SIG_free(sig);

	return ret;
}
}
#endif /*def WITH_DSA*/


#ifdef OPENSSL_HAS_ECC
static int
SSH_ECDSA_SignFinal(
#ifdef HAVE_EVP_DIGESTSIGNINIT
EVP_MD_CTX *ctx, unsigned char *sigret, size_t *siglen
#else
EVP_MD_CTX *ctx, unsigned char *sigret, size_t *siglen, EVP_PKEY *pkey
#endif
) {
	ECDSA_SIG *sig;
	size_t len;

{	int ret;
	unsigned char buf[20+2*(SHA512_DIGEST_LENGTH)];

#ifdef HAVE_EVP_DIGESTSIGNINIT
	ret = buf_EVP_DigestSignFinal(ctx, buf, sizeof(buf), &len);
	if (ret <= 0) return ret;
#else
	/* NOTE: Function EVP_SignFinal() in OpenSSL before 1.0 does not
	 * return signature length if signature argument is NULL.
	 */
{	unsigned int t;
	ret = EVP_SignFinal(ctx, buf, &t, pkey);
	len = t;
}
	if (ret <= 0) return ret;
#endif

{	/* decode ECDSA signature */
	const unsigned char *psig = buf;
	sig = d2i_ECDSA_SIG(NULL, &psig, (long)/*safe cast*/len);
}

	if (sig == NULL) return -1;
}

/* encode ECDSA r&s into SecSH signature blob */
{	int r;
	struct sshbuf *buf = NULL;
	const BIGNUM *pr, *ps;

	buf = sshbuf_new();
	if (buf == NULL) goto encode_err;

	ECDSA_SIG_get0(sig, &pr, &ps);

	r = sshbuf_put_bignum2(buf, pr);
	if (r != 0) goto encode_err;

	r = sshbuf_put_bignum2(buf, ps);
	if (r != 0) goto encode_err;

	len = sshbuf_len(buf);
	if (len != sshbuf_len(buf)) goto encode_err;

	/* NULL if caller checks for signature buffer size */
	if (sigret != NULL) {
		memcpy(sigret, sshbuf_ptr(buf), len);
	}
	*siglen = len;

	sshbuf_free(buf);
	ECDSA_SIG_free(sig);
	return 1;

encode_err:
	sshbuf_free(buf);
	ECDSA_SIG_free(sig);
	return -1;
}
}
#endif /*def OPENSSL_HAS_ECC*/


#ifdef OPENSSL_HAS_ECC
static int
SSH_ECDSA_VerifyFinal(
#ifdef HAVE_EVP_DIGESTSIGNINIT
EVP_MD_CTX *ctx, const unsigned char *sigblob, size_t siglen
#else
EVP_MD_CTX *ctx, const unsigned char *sigblob, size_t siglen, EVP_PKEY *pkey
#endif
) {
	ECDSA_SIG *sig;

/* decode ECDSA r&s from SecSH signature blob */
{	int r;
	struct sshbuf *buf;
	BIGNUM *pr = NULL, *ps = NULL;

	buf = sshbuf_from(sigblob, siglen);
	if (buf == NULL) return -1;

	/* extract mpint r */
	r = sshbuf_get_bignum2(buf, &pr);
	if (r != 0) goto parse_err;

	/* extract mpint s */
	r = sshbuf_get_bignum2(buf, &ps);
	if (r != 0) goto parse_err;

	/* unexpected trailing data */
	if (sshbuf_len(buf) != 0) goto parse_err;

	sig = ECDSA_SIG_new();
	if (sig == NULL) goto parse_err;

	if (ECDSA_SIG_set0(sig, pr, ps)) {
		sshbuf_free(buf);
		goto process;
	}

	ECDSA_SIG_free(sig);

parse_err:
	BN_free(ps);
	BN_free(pr);
	sshbuf_free(buf);
	return -1;
}

process:
{	int len, slen;
	unsigned char *buf;
	int ret;

	len = i2d_ECDSA_SIG(sig, NULL);
	if (len <= 0) {
		ECDSA_SIG_free(sig);
		return -1;
	}

	buf = xmalloc(len);  /*fatal on error*/

{	/* encode ECDSA signature */
	unsigned char *pbuf = buf;
	slen = i2d_ECDSA_SIG(sig, &pbuf);
}

	ret = (len == slen)
#ifdef HAVE_EVP_DIGESTSIGNINIT
		? EVP_DigestVerifyFinal(ctx, buf, len)
#else
		? EVP_VerifyFinal(ctx, buf, len, pkey)
#endif
		: -1;

	freezero(buf, len);
	ECDSA_SIG_free(sig);

	return ret;
}
}
#endif /*def OPENSSL_HAS_ECC*/


/* order by usability */
static ssh_evp_md dgsts[] = {
#ifdef HAVE_EVP_SHA256
	{ SSH_MD_RSA_SHA256, EVP_sha256, SSH_SignFinal, SSH_VerifyFinal },
	{ SSH_MD_RSA_SHA512, EVP_sha512, SSH_SignFinal, SSH_VerifyFinal },
#endif /* def HAVE_EVP_SHA256 */
#ifdef OPENSSL_HAS_NISTP256
	{ SSH_MD_EC_SHA256_SSH, ssh_ecdsa_EVP_sha256, SSH_ECDSA_SignFinal, SSH_ECDSA_VerifyFinal },
#endif
#ifdef OPENSSL_HAS_NISTP384
	{ SSH_MD_EC_SHA384_SSH, ssh_ecdsa_EVP_sha384, SSH_ECDSA_SignFinal, SSH_ECDSA_VerifyFinal },
#endif
#ifdef OPENSSL_HAS_NISTP521
	{ SSH_MD_EC_SHA512_SSH, ssh_ecdsa_EVP_sha512, SSH_ECDSA_SignFinal, SSH_ECDSA_VerifyFinal },
#endif

	{ SSH_MD_RSA_SHA1, EVP_sha1, SSH_SignFinal, SSH_VerifyFinal },
	{ SSH_MD_RSA_MD5, EVP_md5, SSH_SignFinal, SSH_VerifyFinal },

#ifdef WITH_DSA
	{ SSH_MD_DSA_SHA1, EVP_dss1, SSH_SignFinal, SSH_VerifyFinal },
	{ SSH_MD_DSA_RAW, EVP_dss1, DSS1RAW_SignFinal, DSS1RAW_VerifyFinal },
#endif

	/* PKIX-SSH pre 10.0 does not implement properly rfc6187 */
#ifdef OPENSSL_HAS_NISTP256
	{ SSH_MD_EC_SHA256, ssh_ecdsa_EVP_sha256, SSH_SignFinal, SSH_VerifyFinal },
#endif
#ifdef OPENSSL_HAS_NISTP384
	{ SSH_MD_EC_SHA384, ssh_ecdsa_EVP_sha384, SSH_SignFinal, SSH_VerifyFinal },
#endif
#ifdef OPENSSL_HAS_NISTP521
	{ SSH_MD_EC_SHA512, ssh_ecdsa_EVP_sha512, SSH_SignFinal, SSH_VerifyFinal },
#endif

#ifdef HAVE_EVP_DIGESTSIGN
	{ SSH_MD_NONE, ssh_EVP_none, NULL, NULL },
#endif
	{ -1, NULL, NULL , NULL }
};


ssh_evp_md*
ssh_evp_md_find(int id) {
	ssh_evp_md *p;

	for (p = dgsts; p->id != -1; p++) {
		if (p->id == id)
			return p;
	}
	return NULL;
}


void
ssh_xkalg_dgst_compat(ssh_evp_md *dest, const ssh_evp_md *src, ssh_compat *compat) {
	dest->id = src->id;
	dest->md = src->md;

#ifdef OPENSSL_HAS_ECC
	if (check_compat_extra(compat, SSHX_RFC6187_ASN1_OPAQUE_ECDSA_SIGNATURE)) {
		if (src->SignFinal == SSH_ECDSA_SignFinal) {
			dest->SignFinal = SSH_SignFinal;
			dest->VerifyFinal = SSH_VerifyFinal;
			return;
		}
	}
#else
	UNUSED(compat);
#endif /*ndef OPENSSL_HAS_ECC*/

	dest->SignFinal = src->SignFinal;
	dest->VerifyFinal = src->VerifyFinal;
}


#ifdef HAVE_EVP_PKEY_CHECK
/* Wrappers for OpenSSL 1.1.1 pkey check functions */
int
ssh_pkey_public_check(EVP_PKEY *pkey) {
	EVP_PKEY_CTX *ctx;
	int r;

#ifdef ENABLE_KEY_PROVIDERS
	ctx = EVP_PKEY_CTX_new_from_pkey(pkixssh_libctx, pkey, pkixssh_propq);
#else
	ctx = EVP_PKEY_CTX_new(pkey, NULL);
#endif
	if (ctx == NULL) return SSH_ERR_ALLOC_FAIL;

{	int ret = EVP_PKEY_public_check(ctx);
	switch (ret) {
	case 1:  r = 0; break;
	case 0:  r = SSH_ERR_INVALID_FORMAT; break;
	case -2:
		debug3("public check unsupported");
		r = 0;
		break;
	default:
		error_crypto("EVP_PKEY_public_check");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		break;
	}
}

	EVP_PKEY_CTX_free(ctx);
	return r;
}

int
ssh_pkey_check(EVP_PKEY *pkey) {
	EVP_PKEY_CTX *ctx;
	int r;

#ifdef ENABLE_KEY_PROVIDERS
	ctx = EVP_PKEY_CTX_new_from_pkey(pkixssh_libctx, pkey, pkixssh_propq);
#else
	ctx = EVP_PKEY_CTX_new(pkey, NULL);
#endif
	if (ctx == NULL) return SSH_ERR_ALLOC_FAIL;
{	int ret;
#ifdef HAVE_EVP_PKEY_PAIRWISE_CHECK
	 ret = EVP_PKEY_pairwise_check(ctx);
#else
	 ret = EVP_PKEY_check(ctx);
#endif
	switch (ret) {
	case 1:  r = 0; break;
	case 0:  r = SSH_ERR_INVALID_FORMAT; break;
	case -2:
		debug3("check unsupported");
		r = 0;
		break;
	default:
		error_crypto("EVP_PKEY_pairwise_check");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		break;
	}
}

	EVP_PKEY_CTX_free(ctx);
	return r;
}
#else /*ndef HAVE_EVP_PKEY_CHECK*/
/* NOTE some implementations refuse to implement recommendations
 * SP800-56A and SP800-56B! These recommendations are required
 * if a cryptographic module runs in FIPS mode!
 */
int
ssh_pkey_public_check(EVP_PKEY *pkey) {
	UNUSED(pkey);
	debug3("public check not implemented");
	return 0;
}

int
ssh_pkey_check(EVP_PKEY *pkey) {
	UNUSED(pkey);
	debug3("check not implemented");
	return 0;
}
#endif /*ndef HAVE_EVP_PKEY_CHECK*/


int
ssh_pkey_allowed(int base_id) {
#if defined(EVP_PKEY_FLAG_FIPS) && defined(HAVE_EVP_PKEY_METH_FIND)
{	/* NOTE EVP_PKEY_meth...() are deprecated in OpenSSL 3.0 */
	const EVP_PKEY_METHOD *pmeth = EVP_PKEY_meth_find(base_id);
	if (pmeth == NULL) return 0;

	if (ssh_is_fips_enabled()) {
		int pkey_id, flags;
		EVP_PKEY_meth_get0_info(&pkey_id, &flags, pmeth);
		/* vendor specific FIPS implementation */
		if ((flags & EVP_PKEY_FLAG_FIPS) == 0)
			return 0;
	}
}
#else
	UNUSED(base_id);
#endif
	return 1;
}

#ifdef HAVE_EVP_KEYMGMT_FETCH
int/*boolean*/
ssh_keytype_provided(const char *algorithm, char **prov_name) {
	EVP_KEYMGMT *km;

	if (prov_name != NULL)
		*prov_name = NULL;

	ERR_set_mark();
	km = EVP_KEYMGMT_fetch(pkixssh_libctx, algorithm, pkixssh_propq);
	/* clear needless errors for unsupported algorithms */
	ERR_pop_to_mark();

	if (km == NULL) return 0;

	if (prov_name == NULL) goto done;

{	const OSSL_PROVIDER *prov;
	const char *prov_name_p = NULL;

{	OSSL_PARAM request[] = {
		{ OSSL_PROV_PARAM_NAME, OSSL_PARAM_UTF8_PTR, &prov_name_p, 0, 0 },
		{ NULL, 0, NULL, 0, 0 }
	};
	prov = EVP_KEYMGMT_get0_provider(km);
	OSSL_PROVIDER_get_params(prov, request);
}

	*prov_name = xstrdup(prov_name_p != NULL ? prov_name_p : "undefined");
}

done:
	EVP_KEYMGMT_free(km);
	return 1;
}
#endif

int
ssh_key_allowed(const char *algorithm, int base_id) {
#ifdef HAVE_EVP_KEYMGMT_FETCH	/* OpenSSL >= 3.0 */
	UNUSED(base_id);
	return ssh_keytype_provided(algorithm, NULL);
#else
	UNUSED(algorithm);
	return ssh_pkey_allowed(base_id);
#endif
}

int
ssh_pkey_copy_public(const EVP_PKEY *from, EVP_PKEY **top) {
	unsigned char *data = NULL;
	int datalen;

	/* NOTE const argument in OpenSSL 3.0 */
	datalen = i2d_PUBKEY((EVP_PKEY*)from, &data);
	if (datalen < 0) return SSH_ERR_INVALID_FORMAT;

{	const unsigned char *q = data;
#ifdef ENABLE_KEY_PROVIDERS
	*top = d2i_PUBKEY_ex(NULL, &q, datalen, pkixssh_libctx, pkixssh_propq);
#else
	*top = d2i_PUBKEY(NULL, &q, datalen);
#endif
}
	OPENSSL_free(data);

	return (*top != NULL) ? 0 : SSH_ERR_LIBCRYPTO_ERROR;
}

int
sshkey_copy_public_pkey(const struct sshkey *from, struct sshkey *to) {
	to->ecdsa_nid = from->ecdsa_nid;
	return ssh_pkey_copy_public(from->pk, &to->pk);
}

int
ssh_pkey_sign(
	const ssh_evp_md *dgst, EVP_PKEY *privkey,
	u_char *sig, size_t *siglen, const u_char *data, size_t datalen
) {
	int ret;
	EVP_MD_CTX *ctx;

	ctx = EVP_MD_CTX_new();
	if (ctx == NULL) {
		error_f("out of memory");
		return -1;
	}

#ifdef EVP_MD_CTX_FLAG_FINALISE
/* This OpenSSL 1.1.0 flag is not required but allows to avoid
 * OpenSSL 3.0-3.1 provider error: context duplication is an optional
 * feature but if is not implemented EVP_DigestSignFinal() fails!
 */
	EVP_MD_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_FINALISE);
#endif
#ifdef HAVE_EVP_DIGESTSIGNINIT
	ret = EVP_DigestSignInit(ctx, NULL, dgst->md(), NULL, privkey);
#else
	ret = EVP_SignInit_ex(ctx, dgst->md(), NULL);
#endif
	if (ret <= 0) {
		error_f("init fail");
		TRACE_EVP_ERROR("SignInit");
		goto done;
	}

	if (dgst->md() == NULL) {
#ifdef HAVE_EVP_DIGESTSIGN
		u_char *sigbuf;
		size_t len;

		ret = EVP_DigestSign(ctx, NULL, &len, data, datalen);
		if (ret <= 0) {
			TRACE_EVP_ERROR("DigestSign/len?");
			goto done;
		}

		sigbuf = OPENSSL_malloc(len);
		if (sigbuf == NULL) {
			ret = -1;
			goto done;
		}
		explicit_bzero(sigbuf, len);

		ret = EVP_DigestSign(ctx, sigbuf, &len, data, datalen);
		if (ret <= 0) {
			OPENSSL_free(sigbuf);
			TRACE_EVP_ERROR("DigestSign");
			goto done;
		}
		/* NULL if caller checks for signature buffer size */
		if (sig != NULL) {
			/*space ensured by caller*/
			memcpy(sig, sigbuf, len);
		}
		*siglen = len;

		explicit_bzero(sigbuf, len);
		OPENSSL_free(sigbuf);
#else
		ret = -1; /*unreachable*/
#endif
		goto done;
	}

	ret = EVP_DigestSignUpdate(ctx, data, datalen);
	if (ret <= 0) {
		error_f("update fail");
		TRACE_EVP_ERROR("SignUpdate");
		goto done;
	}

#ifdef HAVE_EVP_DIGESTSIGNINIT
	ret = dgst->SignFinal(ctx, sig, siglen);
#else
	ret = dgst->SignFinal(ctx, sig, siglen, privkey);
#endif
	if (ret <= 0) {
		TRACE_EVP_ERROR("SignFinal");
	}

done:
	EVP_MD_CTX_free(ctx);
	return ret;
}


int
ssh_pkey_verify(
	const ssh_evp_md *dgst, EVP_PKEY *pubkey,
	const u_char *sig, size_t siglen, const u_char *data, size_t datalen
) {
	int ret;
	EVP_MD_CTX *ctx;

	ctx = EVP_MD_CTX_new();
	if (ctx == NULL) {
		error_f("out of memory");
		return -1;
	}

#ifdef HAVE_EVP_DIGESTSIGNINIT
	ret = EVP_DigestVerifyInit(ctx, NULL, dgst->md(), NULL, pubkey);
#else
	ret = EVP_VerifyInit_ex(ctx, dgst->md(), NULL);
#endif
	if (ret <= 0) {
		error_f("init fail");
		TRACE_EVP_ERROR("VerifyInit");
		ret = -1; /* mark as error */
		goto done;
	}

	if (dgst->md() == NULL) {
#ifdef HAVE_EVP_DIGESTSIGN
		ret = EVP_DigestVerify(ctx, sig, siglen, data, datalen);
		if (ret <= 0) {
			TRACE_EVP_ERROR("DigestVerify");
		}
#else
		ret = -1; /*unreachable*/
#endif
		goto done;
	}

	ret = EVP_DigestVerifyUpdate(ctx, data, datalen);
	if (ret <= 0) {
		error_f("update fail");
		TRACE_EVP_ERROR("VerifyUpdate");
		ret = -1; /* mark as error */
		goto done;
	}

#ifdef HAVE_EVP_DIGESTSIGNINIT
	ret = dgst->VerifyFinal(ctx, sig, siglen);
#else
	ret = dgst->VerifyFinal(ctx, sig, siglen, pubkey);
#endif
	if (ret <= 0) {
		TRACE_EVP_ERROR("VerifyFinal");
	}

done:
	EVP_MD_CTX_free(ctx);
	return ret;
}

int
ssh_pkey_verify_r(
	const ssh_evp_md *dgst, EVP_PKEY *pubkey,
	const u_char *sig, size_t siglen, const u_char *data, size_t datalen
) {
	int ret = ssh_pkey_verify(dgst, pubkey, sig, siglen, data, datalen);
	return (ret > 0)
	    ? SSH_ERR_SUCCESS
	    : (ret == 0)
		? SSH_ERR_SIGNATURE_INVALID
		: SSH_ERR_LIBCRYPTO_ERROR;
}


/* supported pkey types */
extern const struct ssh_pkey_impl ssh_pkey_ed25519_impl;
extern const struct ssh_pkey_impl ssh_pkey_ec_impl;
extern const struct ssh_pkey_impl ssh_pkey_rsa_impl;
extern const struct ssh_pkey_impl ssh_pkey_mldsa44_impl;
extern const struct ssh_pkey_impl ssh_pkey_mldsa65_impl;
extern const struct ssh_pkey_impl ssh_pkey_mldsa87_impl;
extern const struct ssh_pkey_impl ssh_pkey_dsa_impl;

static const struct ssh_pkey_impl * const pkey_impls[] = {
	&ssh_pkey_ed25519_impl,
	&ssh_pkey_ec_impl,
	&ssh_pkey_rsa_impl,
	&ssh_pkey_mldsa44_impl,
	&ssh_pkey_mldsa65_impl,
	&ssh_pkey_mldsa87_impl,
	&ssh_pkey_dsa_impl,
	NULL
};

const struct ssh_pkey_impl*
ssh_pkey_impl_from_type_name(const char *type_name) {
	const struct ssh_pkey_impl *const* p;

	for (p = pkey_impls; *p != NULL; p++) {
		const char *const *q = (*p)->type_name;
		for (; *q != NULL; q++) {
			if (strcmp(*q, type_name) == 0)
				return *p;
		}
	}
	return NULL;
}

const struct ssh_pkey_impl *
ssh_pkey_impl_from_evp_id(int evp_id) {
	const struct ssh_pkey_impl *const* p;

	for (p = pkey_impls; *p != NULL; p++) {
		if ((*p)->evp_id == evp_id)
			return *p;
	}
	return NULL;
}


#ifdef HAVE_EVP_KEM_FETCH
int
ssh_kem_allowed(const char *algorithm) {
	int ret;
	EVP_KEM *kem;

	ERR_set_mark();
	kem = EVP_KEM_fetch(pkixssh_libctx, algorithm, pkixssh_propq);
	/* clear needless errors for unsupported algorithms */
	ERR_pop_to_mark();
	ret = (kem != NULL) ? 1 : 0;
	EVP_KEM_free(kem);

	return ret;
}
#endif


int
ssh_is_fips_provider(void) {
#ifdef ENABLE_KEY_PROVIDERS
	if (OSSL_PROVIDER_available(pkixssh_libctx, "fips"))
		return 1;
#endif
	return 0;
}
#else

typedef int sshkey_crypto_empty_translation_unit;

#endif /* WITH_OPENSSL */
