/* $OpenBSD: ssh-rsa.c,v 1.82 2025/10/03 00:08:02 djm Exp $ */
/*
 * Copyright (c) 2000, 2003 Markus Friedl <markus@openbsd.org>
 * Copyright (c) 2011 Dr. Stephen Henson.  All rights reserved.
 * Copyright (c) 2011-2026 Roumen Petrov.  All rights reserved.
 *
 * 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.
 */

#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

#include "includes.h"

#ifdef WITH_OPENSSL

#include <sys/types.h>

#include "evp-compat.h"
#include <openssl/pem.h>
#include <openssl/bn.h>
#ifdef ENABLE_KEY_PROVIDERS
# include <openssl/core_names.h>
#endif

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "sshbuf.h"
#include "ssherr.h"
#define SSHKEY_INTERNAL
#include "sshxkey.h"
#include "log.h"
#include "match.h"

#define SSH_RSA_MINIMUM_MODULUS_SIZE	1024
#ifndef OPENSSL_RSA_MAX_MODULUS_BITS
# define OPENSSL_RSA_MAX_MODULUS_BITS	16384
#endif


struct ssh_rsa_alg_st {
	const char *name;
	const char *signame;
	const int id;
};

static struct ssh_rsa_alg_st
ssh_rsa_algs[] = {
#ifdef HAVE_EVP_SHA256
	{ "rsa-sha2-256", "rsa-sha2-256", SSH_MD_RSA_SHA256 },
	{ "rsa-sha2-512", "rsa-sha2-512", SSH_MD_RSA_SHA512 },
#endif
	{ "ssh-rsa", "ssh-rsa", SSH_MD_RSA_SHA1 },
#ifdef HAVE_EVP_SHA256
	{ "rsa-sha2-256-cert-v01@openssh.com", "rsa-sha2-256", SSH_MD_RSA_SHA256 },
	{ "rsa-sha2-512-cert-v01@openssh.com", "rsa-sha2-512", SSH_MD_RSA_SHA512 },
#endif
	{ "ssh-rsa-cert-v01@openssh.com", "ssh-rsa", SSH_MD_RSA_SHA1 },
	{ NULL, NULL, -1 }
};

static struct ssh_rsa_alg_st* ssh_rsa_alg_info(const char *alg);

struct ssh_rsa_alg_st*
ssh_rsa_alg_info(const char *alg) {
	struct ssh_rsa_alg_st* p;

	if (alg == NULL || *alg == '\0')
		return ssh_rsa_alg_info("ssh-rsa");

	for (p = ssh_rsa_algs; p->name != NULL; p++)
		if (strcmp(alg, p->name) == 0)
			return p;

	return NULL;
}

/* global option overridable by configuration */
int required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;

static inline int
sshrsa_verify_length(int bits) {
	return bits < required_rsa_size
	    ? SSH_ERR_KEY_LENGTH : 0;
}


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

#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 RSA key */

#ifndef HAVE_RSA_GENERATE_KEY_EX	/* OpenSSL < 0.9.8 */
static int
RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *bn_e, void *cb)
{
	RSA *new_rsa, tmp_rsa;
	unsigned long e;

	if (cb != NULL)
		fatal_f("callback args not supported");
	e = BN_get_word(bn_e);
	if (e == 0xffffffffL)
		fatal_f("value of e too large");
	new_rsa = RSA_generate_key(bits, e, NULL, NULL);
	if (new_rsa == NULL)
		return 0;
	/* swap rsa/new_rsa then free new_rsa */
	tmp_rsa = *rsa;
	*rsa = *new_rsa;
	*new_rsa = tmp_rsa;
	RSA_free(new_rsa);
	return 1;
}
#endif

#ifndef HAVE_RSA_GET0_KEY
/* opaque RSA key structure */
static inline void
RSA_get0_key(const RSA *rsa, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) {
	if (n != NULL) *n = rsa->n;
	if (e != NULL) *e = rsa->e;
	if (d != NULL) *d = rsa->d;
}

static inline int
RSA_set0_key(RSA *rsa, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
/* If the fields in r are NULL, the corresponding input parameters MUST
 * be non-NULL for n and e.  d may be left NULL (in case only the
 * public key is used).
 *
 * It is an error to give the results from get0 on r as input
 * parameters.
 */
	if (n == rsa->n || e == rsa->e
	|| (rsa->d != NULL && d == rsa->d))
		return 0;

	if (n != NULL) { BN_free(rsa->n); rsa->n = n; }
	if (e != NULL) { BN_free(rsa->e); rsa->e = e; }
	if (d != NULL) { BN_free(rsa->d); rsa->d = d; }

	return 1;
}


static inline void
RSA_get0_crt_params(const RSA *rsa, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp) {
	if (dmp1 != NULL) *dmp1 = rsa->dmp1;
	if (dmq1 != NULL) *dmq1 = rsa->dmq1;
	if (iqmp != NULL) *iqmp = rsa->iqmp;
}

static inline int
RSA_set0_crt_params(RSA *rsa, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) {
/* If the fields in r are NULL, the corresponding input parameters MUST
 * be non-NULL.
 *
 * It is an error to give the results from get0 on r as input
 * parameters.
 */
	if (dmp1 == rsa->dmp1 || dmq1 == rsa->dmq1 || iqmp == rsa->iqmp)
		return 0;

	if (dmp1 != NULL) { BN_free(rsa->dmp1); rsa->dmp1 = dmp1; }
	if (dmq1 != NULL) { BN_free(rsa->dmq1); rsa->dmq1 = dmq1; }
	if (iqmp != NULL) { BN_free(rsa->iqmp); rsa->iqmp = iqmp; }

	return 1;
}


static inline void
RSA_get0_factors(const RSA *rsa, const BIGNUM **p, const BIGNUM **q) {
	if (p != NULL) *p = rsa->p;
	if (q != NULL) *q = rsa->q;
}


static inline int
RSA_set0_factors(RSA *rsa, BIGNUM *p, BIGNUM *q) {
/* If the fields in r are NULL, the corresponding input parameters MUST
 * be non-NULL.
 *
 * It is an error to give the results from get0 on r as input
 * parameters.
 */
	if (p == rsa->p || q == rsa->q)
		return 0;

	if (p != NULL) { BN_free(rsa->p); rsa->p = p; }
	if (q != NULL) { BN_free(rsa->q); rsa->q = q; }

	return 1;
}
#endif /* ndef HAVE_RSA_GET0_KEY */


void
ssh_EVP_PKEY_RSA_get1_key(EVP_PKEY *pkey,
    BIGNUM **np, BIGNUM **ep, BIGNUM **dp
) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pkey) != NULL) {
		if (np != NULL)
			(void)EVP_PKEY_get_bn_param(pkey,
			    OSSL_PKEY_PARAM_RSA_N, np);
		if (ep != NULL)
			(void)EVP_PKEY_get_bn_param(pkey,
			    OSSL_PKEY_PARAM_RSA_E, ep);
		if (dp != NULL)
			(void)EVP_PKEY_get_bn_param(pkey,
			    OSSL_PKEY_PARAM_RSA_D, dp);
		return;
	}
#endif
{	const BIGNUM *n = NULL, *e = NULL, *d = NULL;
	RSA *rsa = EVP_PKEY_get1_RSA(pkey);

	if (rsa == NULL) return;

	RSA_get0_key(rsa, &n, &e, &d);

	if (np != NULL) *np = BN_dup(n);
	if (ep != NULL) *ep = BN_dup(e);
	if (dp != NULL) *dp = BN_dup(d);

	RSA_free(rsa);
}
}

void
ssh_EVP_PKEY_RSA_get1_factors(EVP_PKEY *pkey,
    BIGNUM **pp, BIGNUM **qp
) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pkey) != NULL) {
		if (pp != NULL)
			(void)EVP_PKEY_get_bn_param(pkey,
			    OSSL_PKEY_PARAM_RSA_FACTOR1, pp);
		if (qp != NULL)
			(void)EVP_PKEY_get_bn_param(pkey,
			    OSSL_PKEY_PARAM_RSA_FACTOR2, qp);
		return;
	}
#endif
{	const BIGNUM *p = NULL, *q = NULL;
	RSA *rsa = EVP_PKEY_get1_RSA(pkey);

	if (rsa == NULL) return;

	RSA_get0_factors(rsa, &p, &q);

	if (pp != NULL) *pp = BN_dup(p);
	if (qp != NULL) *qp = BN_dup(q);

	RSA_free(rsa);
}
}


#ifndef BN_FLG_CONSTTIME
#  define BN_FLG_CONSTTIME 0x0 /* OpenSSL < 0.9.8 */
#endif

static int
ssh_calc_crt_params(const BIGNUM *key_d,
    const BIGNUM *p, const BIGNUM *q,
    BIGNUM **pdmp1, BIGNUM **pdmq1
) {
	BN_CTX *ctx;
	BIGNUM *d = NULL, *aux = NULL, *dmp1 = NULL, *dmq1 = NULL;
	int r;

	ctx = BN_CTX_new();
	if (ctx == NULL) return SSH_ERR_ALLOC_FAIL;

	d = BN_dup(key_d);
	if (d == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}
	BN_set_flags(d, BN_FLG_CONSTTIME);

	if ((dmp1 = BN_new()) == NULL ||
	    (dmq1 = BN_new()) == NULL ||
	    (aux = BN_new()) == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}
	BN_set_flags(aux, BN_FLG_CONSTTIME);

	if ((BN_sub(aux, p, BN_value_one()) == 0) ||
	    (BN_mod(dmp1, d, aux, ctx) == 0)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	if ((BN_sub(aux, q, BN_value_one()) == 0) ||
	    (BN_mod(dmq1, d, aux, ctx) == 0)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	*pdmp1 = dmp1;
	*pdmq1 = dmq1;
	dmp1 = dmq1 = NULL; /* transferred */
	r = 0;

err:
	BN_clear_free(dmp1);
	BN_clear_free(dmq1);
	BN_clear_free(aux);
	BN_clear_free(d);
	BN_CTX_free(ctx);
	return r;
}


#ifndef HAVE_EVP_PKEY_CMP	/* OpenSSL < 0.9.8 */
extern int /* see sshkey-crypto.c */
ssh_EVP_PKEY_cmp_rsa(const EVP_PKEY *ka, const EVP_PKEY *kb);

int
ssh_EVP_PKEY_cmp_rsa(const EVP_PKEY *ka, const EVP_PKEY *kb) {
	int ret = -1;
	RSA *a, *b;
	const BIGNUM *a_n, *a_e;
	const BIGNUM *b_n, *b_e;

	a = EVP_PKEY_get1_RSA((EVP_PKEY*)ka);
	b = EVP_PKEY_get1_RSA((EVP_PKEY*)kb);
	if (a == NULL || b == NULL) goto err;

	RSA_get0_key(a, &a_n, &a_e, NULL);
	RSA_get0_key(b, &b_n, &b_e, NULL);

	ret =
	    BN_cmp(a_n, b_n) == 0 &&
	    BN_cmp(a_e, b_e) == 0;

err:
	RSA_free(b);
	RSA_free(a);
	return ret;
}
#endif


#ifdef ENABLE_KEY_PROVIDERS
static int
ssh_pkey_validate_public_rsaprov(const EVP_PKEY *pk) {
	int bits = 0;
	int r;

	if (EVP_PKEY_get_int_param(pk, OSSL_PKEY_PARAM_BITS, &bits) != 1)
		return SSH_ERR_INVALID_ARGUMENT;

	r = sshrsa_verify_length(bits);
	if (r != 0) return r;

	/* other checks ? */
	return 0;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

static int
ssh_rsa_validate_public(const RSA *rsa) {
	int r;
	const BIGNUM *n = NULL;

	RSA_get0_key(rsa, &n, NULL, NULL);

	r = sshrsa_verify_length(BN_num_bits(n));
	if (r != 0) return r;

	/* other checks ? */
	return 0;
}


extern int /* see sshkey.c */
ssh_pkey_validate_public_rsa(EVP_PKEY *pk);

int
ssh_pkey_validate_public_rsa(EVP_PKEY *pk) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return ssh_pkey_validate_public_rsaprov(pk);
#endif
{	RSA *rsa;
	int r;

	rsa = EVP_PKEY_get1_RSA(pk);
	if (rsa == NULL) return SSH_ERR_INVALID_ARGUMENT;

	r = ssh_rsa_validate_public(rsa);

	RSA_free(rsa);
	return r;
}
}


static int
ssh_rsa_complete_public(RSA *rsa) {
	int r;

	r = ssh_rsa_validate_public(rsa);
	if (r != 0) return r;

	if (RSA_blinding_on(rsa, NULL) != 1)
		return SSH_ERR_LIBCRYPTO_ERROR;

	return 0;
}


static int
ssh_pkey_complete_public_rsa(EVP_PKEY *pk) {
#ifdef ENABLE_KEY_PROVIDERS
	/* TODO: blinding on provider managed keys? */
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return ssh_pkey_validate_public_rsaprov(pk);
#endif
{	RSA *rsa;
	int r;

	rsa = EVP_PKEY_get1_RSA(pk);
	if (rsa == NULL) return SSH_ERR_INVALID_ARGUMENT;

	r = ssh_rsa_complete_public(rsa);

	RSA_free(rsa);
	return r;
}
}


/* functions that write/read public key in traditional "PEM" format */
extern int /* see sshkey-crypto.c */
ssh_rsa_public_to_fp_traditional(struct sshkey *key, FILE *fp);
extern int /* see sshkey-crypto.c */
ssh_rsa_public_from_fp_traditional(FILE *fp, struct sshkey **key);

int
ssh_rsa_public_to_fp_traditional(struct sshkey *key, FILE *fp) {
	int res;
	RSA *rsa;

	rsa = EVP_PKEY_get1_RSA(key->pk);
	if (rsa == NULL) return 0;

	res = PEM_write_RSAPublicKey(fp, rsa);
	RSA_free(rsa);
	return res;
}

int
ssh_rsa_public_from_fp_traditional(FILE *fp, struct sshkey **key) {
	RSA *rsa;
	EVP_PKEY *pk = NULL;
	struct sshkey *k = NULL;
	int r;

	rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL);
	if (rsa == NULL) return SSH_ERR_INVALID_FORMAT;

	pk = EVP_PKEY_new();
	if (pk == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}
	if (!EVP_PKEY_set1_RSA(pk, rsa)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	k = sshkey_new(KEY_UNSPEC);
	if (k == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

	k->type = KEY_RSA;
	k->pk = pk;
	RSA_free(rsa);

	*key = k;
	return 0;

err:
	EVP_PKEY_free(pk);
	RSA_free(rsa);
	sshkey_free(k);
	return r;
}


/* functions that read provider keys */
#ifdef USE_PKEY_FROMDATA
static int
sshbuf_read_prov_rsapub(struct sshbuf *buf, EVP_PKEY **pkp) {
	BIGNUM *n = NULL, *e = NULL;
	EVP_PKEY *pk = NULL;
	int r;

	*pkp = NULL;

	if ((r = sshbuf_get_bignum2(buf, &e)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &n)) != 0)
		goto err;

{	unsigned char buf_n[2048], buf_e[2048];
	int selection = OSSL_KEYMGMT_SELECT_PUBLIC_KEY;

{	OSSL_PARAM params[] = {
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_N, buf_n, sizeof(buf_n)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_E, buf_e, sizeof(buf_e)),
		OSSL_PARAM_END
	};

	if (!OSSL_PARAM_set_BN(&params[0], n)) goto err;
	if (!OSSL_PARAM_set_BN(&params[1], e)) goto err;

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

	r = ssh_pkey_validate_public_rsaprov(pk);
	if (r != 0) goto err;

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

err:
	BN_free(n);
	BN_free(e);
	EVP_PKEY_free(pk);
	return r;
}
#endif /*def USE_PKEY_FROMDATA*/

#ifdef USE_PKEY_FROMDATA
static int
sshbuf_read_prov_rsa(struct sshbuf *buf, int with_public, EVP_PKEY **pkp) {
	BIGNUM *n = NULL, *e = NULL;
	BIGNUM *d = NULL, *iqmp = NULL, *p = NULL, *q = NULL;
	BIGNUM *dmp1 = NULL, *dmq1 = NULL;
	EVP_PKEY *pk = NULL;
	int r;

	if (with_public) {
		if (*pkp != NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		if ((r = sshbuf_get_bignum2(buf, &n)) != 0 ||
		    (r = sshbuf_get_bignum2(buf, &e)) != 0)
			goto err;
	} else {
		/* transfer */
		if (*pkp == NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		ssh_EVP_PKEY_RSA_get1_key(*pkp, &n, &e, NULL);
		if ((n == NULL) || (e == NULL)) {
			r = SSH_ERR_INTERNAL_ERROR;
			goto err;
		}
	}

	if ((r = sshbuf_get_bignum2(buf, &d)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &iqmp)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &p)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &q)) != 0)
		goto err;

	r = ssh_calc_crt_params(d, p, q, &dmp1, &dmq1);
	if (r != 0) goto err;

{	unsigned char buf_n[2048], buf_e[2048], buf_d[2048];
	unsigned char buf_p[2048], buf_q[2048];
	unsigned char buf_dmp1[2048], buf_dmq1[2048], buf_iqmp[2048];
	int selection = OSSL_KEYMGMT_SELECT_KEYPAIR | \
	    OSSL_KEYMGMT_SELECT_ALL_PARAMETERS;

{	OSSL_PARAM params[] = {
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_N, buf_n, sizeof(buf_n)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_E, buf_e, sizeof(buf_e)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_D, buf_d, sizeof(buf_d)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_FACTOR1, buf_p, sizeof(buf_p)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_FACTOR2, buf_q, sizeof(buf_q)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_EXPONENT1, buf_dmp1, sizeof(buf_dmp1)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_EXPONENT2, buf_dmq1, sizeof(buf_dmq1)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, buf_iqmp, sizeof(buf_iqmp)),
		OSSL_PARAM_END
	};

	if (!OSSL_PARAM_set_BN(&params[0], n)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[1], e)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[2], d)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[3], p)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[4], q)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[5], dmp1)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[6], dmq1)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[7], iqmp)) goto err_prov;

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

err_prov:
	OPENSSL_cleanse(buf_d, params[2].data_size);
	OPENSSL_cleanse(buf_p, params[3].data_size);
	OPENSSL_cleanse(buf_q, params[4].data_size);
	OPENSSL_cleanse(buf_dmp1, params[5].data_size);
	OPENSSL_cleanse(buf_dmq1, params[6].data_size);
	OPENSSL_cleanse(buf_iqmp, params[7].data_size);
}
	if (r != 0) goto err;
}

	if (with_public) {
		/* check length */
		r = ssh_pkey_validate_public_rsaprov(pk);
		if (r != 0) goto err;
	}

	/* success */
	EVP_PKEY_free(*pkp);
	*pkp = pk;
	pk = NULL;  /* transferred */

err:
	BN_free(n);
	BN_free(e);
	BN_clear_free(d);
	BN_clear_free(iqmp);
	BN_clear_free(dmp1);
	BN_clear_free(dmq1);
	BN_clear_free(p);
	BN_clear_free(q);
	EVP_PKEY_free(pk);
	return r;
}
#endif /*def USE_PKEY_FROMDATA*/


/* functions that write provider keys */
#ifdef ENABLE_KEY_PROVIDERS
static int
sshbuf_write_prov_rsapub(struct sshbuf *buf, const EVP_PKEY *pk) {
	int r;
	BIGNUM *n = NULL, *e = NULL;

	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_N, &n) != 1) {
		r = SSH_ERR_INVALID_ARGUMENT;
		goto err;
	}
	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_E, &e) != 1) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	r = sshbuf_put_bignum2(buf, e);
	if (r != 0) goto err;
	r = sshbuf_put_bignum2(buf, n);

err:
	BN_free(e);
	BN_free(n);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/

#ifdef ENABLE_KEY_PROVIDERS
static int
sshbuf_write_prov_rsa(struct sshbuf *buf, const EVP_PKEY *pk, int with_public) {
	int r = SSH_ERR_LIBCRYPTO_ERROR;
	BIGNUM *n = NULL, *e = NULL;
	BIGNUM *d = NULL, *iqmp = NULL, *p = NULL, *q = NULL;

	if (!with_public) goto skip_public;

	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_N, &n) != 1)
		goto err;
	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_E, &e) != 1)
		goto err;

	r = sshbuf_put_bignum2(buf, n);
	if (r != 0) goto err;
	r = sshbuf_put_bignum2(buf, e);
	if (r != 0) goto err;

skip_public:
	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_D, &d) != 1)
		goto err;
	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp) != 1)
		goto err;
	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_FACTOR1, &p) != 1)
		goto err;
	if (EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_RSA_FACTOR2, &q) != 1)
		goto err;

	r = sshbuf_put_bignum2(buf, d);
	if (r != 0) goto err;
	r = sshbuf_put_bignum2(buf, iqmp);
	if (r != 0) goto err;
	r = sshbuf_put_bignum2(buf, p);
	if (r != 0) goto err;
	r = sshbuf_put_bignum2(buf, q);

err:
	BN_free(n);
	BN_free(e);
	BN_clear_free(d);
	BN_clear_free(iqmp);
	BN_clear_free(p);
	BN_clear_free(q);
	return r;
}
#endif /*def ENABLE_KEY_PROVIDERS*/


/* functions that read low level (rsa) keys */
#ifndef USE_PKEY_FROMDATA
static int
sshbuf_get_rsapub(struct sshbuf *buf, RSA **rsap) {
	BIGNUM *n = NULL, *e = NULL;
	RSA *rsa = NULL;
	int r;

	*rsap = NULL;

	if ((r = sshbuf_get_bignum2(buf, &e)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &n)) != 0)
		goto err;

	rsa = RSA_new();
	if (rsa == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

	/* key attribute allocation */
	if (!RSA_set0_key(rsa, n, e, NULL)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
	if (r != 0) goto err;
	n = e = NULL; /* transferred */

	r = ssh_rsa_complete_public(rsa);
	if (r != 0) goto err;

	/* success */
	*rsap = rsa;
	rsa = NULL; /* transferred */

err:
	BN_free(n);
	BN_free(e);
	RSA_free(rsa);
	return r;
}
#endif /*ndef USE_PKEY_FROMDATA*/

#ifndef USE_PKEY_FROMDATA
static int
sshbuf_set_rsa(struct sshbuf *buf, int with_public, RSA *rsa) {
	BIGNUM *n = NULL, *e = NULL;
	BIGNUM *d = NULL, *iqmp = NULL, *p = NULL, *q = NULL;
	BIGNUM *dmp1 = NULL, *dmq1 = NULL;
	int r;

	if (!with_public) goto skip_public;

	if ((r = sshbuf_get_bignum2(buf, &n)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &e)) != 0)
		goto err;

skip_public:
	if ((r = sshbuf_get_bignum2(buf, &d)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &iqmp)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &p)) != 0 ||
	    (r = sshbuf_get_bignum2(buf, &q)) != 0)
		goto err;

	r = ssh_calc_crt_params(d, p, q, &dmp1, &dmq1);
	if (r != 0) goto err;

	/* key attribute allocation */
	if (!RSA_set0_key(rsa, n, e, d)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
	if (r != 0) goto err;
	n = e = d = NULL; /* transferred */

	if (!RSA_set0_factors(rsa, p, q)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
	p = q = NULL; /* transferred */

	if (!RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
	dmp1 = dmq1 = iqmp = NULL; /* transferred */

	r = ssh_rsa_validate_public(rsa);
	if (r != 0) goto err;

	if (RSA_blinding_on(rsa, NULL) != 1) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

err:
	BN_free(n);
	BN_free(e);
	BN_clear_free(d);
	BN_clear_free(p);
	BN_clear_free(q);
	BN_clear_free(iqmp);
	BN_clear_free(dmp1);
	BN_clear_free(dmq1);
	return r;
}
#endif /*ndef USE_PKEY_FROMDATA*/


/* functions that write low level (rsa) keys */
static inline int
sshbuf_put_rsapub(struct sshbuf *buf, const RSA *rsa) {
	int r;
	const BIGNUM *n = NULL, *e = NULL;

	RSA_get0_key(rsa, &n, &e, NULL);

	r = sshbuf_put_bignum2(buf, e);
	if (r != 0) return r;
	return sshbuf_put_bignum2(buf, n);
}

static int
sshbuf_put_rsa(struct sshbuf *buf, const RSA *rsa, int with_public) {
	int r;
	const BIGNUM *n = NULL, *e = NULL;
	const BIGNUM *d = NULL, *iqmp = NULL, *p = NULL, *q = NULL;

	RSA_get0_key(rsa, &n, &e, &d);
	RSA_get0_crt_params(rsa, NULL, NULL, &iqmp);
	RSA_get0_factors(rsa, &p, &q);

	if (!with_public) goto skip_public;

	r = sshbuf_put_bignum2(buf, n);
	if (r != 0) return r;
	r = sshbuf_put_bignum2(buf, e);
	if (r != 0) return r;

skip_public:
	r = sshbuf_put_bignum2(buf, d);
	if (r != 0) return r;
	r = sshbuf_put_bignum2(buf, iqmp);
	if (r != 0) return r;
	r = sshbuf_put_bignum2(buf, p);
	if (r != 0) return r;
	return sshbuf_put_bignum2(buf, q);
}


/* functions that read pkey(rsa) keys */
static int
sshbuf_read_pkey_rsapub(struct sshbuf *buf, EVP_PKEY **pkp) {
#ifdef USE_PKEY_FROMDATA
	return sshbuf_read_prov_rsapub(buf, pkp);
#else /*ndef USE_PKEY_FROMDATA*/
	EVP_PKEY *pk = NULL;
	RSA *rsa = NULL;
	int r;

	*pkp = NULL;

	r = sshbuf_get_rsapub(buf, &rsa);
	if (r != 0) return r;

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

	if (!EVP_PKEY_set1_RSA(pk, rsa)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

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

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

static int
sshbuf_read_pkey_rsa(struct sshbuf *buf, int with_public, EVP_PKEY **pkp) {
#ifdef USE_PKEY_FROMDATA
	return sshbuf_read_prov_rsa(buf, with_public, pkp);
#else /*ndef USE_PKEY_FROMDATA*/
	RSA *rsa = 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;
		rsa = RSA_new();
		if (rsa == NULL)
			return SSH_ERR_ALLOC_FAIL;
	} else {
		if (*pkp == NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		rsa = EVP_PKEY_get1_RSA(*pkp);
		if (rsa == NULL)
			return SSH_ERR_LIBCRYPTO_ERROR;
	}

	r = sshbuf_set_rsa(buf, with_public, rsa);
	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_RSA(pk, rsa)) {
			r = SSH_ERR_LIBCRYPTO_ERROR;
			EVP_PKEY_free(pk);
			goto err;
		}

		/* success */
		*pkp = pk;
	}

err:
	RSA_free(rsa);
	return r;
#endif /*ndef USE_PKEY_FROMDATA*/
}


/* functions that write pkey(rsa) keys */
static int
sshbuf_write_pkey_rsapub(struct sshbuf *buf, EVP_PKEY *pk) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return sshbuf_write_prov_rsapub(buf, pk);
#endif
{	int r;
	RSA *rsa = EVP_PKEY_get1_RSA(pk);
	if (rsa == NULL)
		return SSH_ERR_INVALID_ARGUMENT;

	r = sshbuf_put_rsapub(buf, rsa);

	RSA_free(rsa);
	return r;
}
}

static int
sshbuf_write_pkey_rsa(struct sshbuf *buf, EVP_PKEY *pk, int with_public) {
#ifdef ENABLE_KEY_PROVIDERS
	if (EVP_PKEY_get0_provider(pk) != NULL)
		return sshbuf_write_prov_rsa(buf, pk, with_public);
#endif
{	int r;
	RSA *rsa = EVP_PKEY_get1_RSA(pk);
	if (rsa == NULL)
		return SSH_ERR_INVALID_ARGUMENT;

	r = sshbuf_put_rsa(buf, rsa, with_public);

	RSA_free(rsa);
	return r;
}
}


extern int /* method used locally only in ssh-keygen.c */
sshbuf_read_custom_rsa(struct sshbuf *buf, struct sshkey *key);

int
sshbuf_read_custom_rsa(struct sshbuf *buf, struct sshkey *key) {
	int r;
	BIGNUM *n = NULL, *e;
	BIGNUM *d = NULL, *iqmp = NULL, *p = NULL, *q = NULL;
	BIGNUM *dmp1 = NULL, *dmq1 = NULL;

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

{	BN_ULONG rsa_e;
	u_char e1, e2, e3;

	if ((r = sshbuf_get_u8(buf, &e1)) != 0 ||
	    (e1 < 30 && (r = sshbuf_get_u8(buf, &e2)) != 0) ||
	    (e1 < 30 && (r = sshbuf_get_u8(buf, &e3)) != 0)) {
		r = SSH_ERR_INVALID_FORMAT;
		goto err;
	}

	rsa_e = e1;
	debug3("e %lx", (unsigned long)rsa_e);
	if (rsa_e < 30) {
		rsa_e <<= 8;
		rsa_e += e2;
		debug3("e %lx", (unsigned long)rsa_e);
		rsa_e <<= 8;
		rsa_e += e3;
		debug3("e %lx", (unsigned long)rsa_e);
	}

	if (!BN_set_word(e, rsa_e)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
}

	if ((r = sshbuf_get_bignum1x(buf, &d)) != 0 ||
	    (r = sshbuf_get_bignum1x(buf, &n)) != 0 ||
	    (r = sshbuf_get_bignum1x(buf, &iqmp)) != 0 ||
	    (r = sshbuf_get_bignum1x(buf, &q)) != 0 ||
	    (r = sshbuf_get_bignum1x(buf, &p)) != 0)
		goto err;

	r = ssh_calc_crt_params(d, p, q, &dmp1, &dmq1);
	if (r != 0) goto err;

#ifdef USE_PKEY_FROMDATA
{	unsigned char buf_n[2048], buf_e[2048], buf_d[2048];
	unsigned char buf_p[2048], buf_q[2048];
	unsigned char buf_dmp1[2048], buf_dmq1[2048], buf_iqmp[2048];

{	OSSL_PARAM params[] = {
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_N, buf_n, sizeof(buf_n)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_E, buf_e, sizeof(buf_e)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_D, buf_d, sizeof(buf_d)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_FACTOR1, buf_p, sizeof(buf_p)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_FACTOR2, buf_q, sizeof(buf_q)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_EXPONENT1, buf_dmp1, sizeof(buf_dmp1)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_EXPONENT2, buf_dmq1, sizeof(buf_dmq1)),
		OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, buf_iqmp, sizeof(buf_iqmp)),
		OSSL_PARAM_END
	};

	if (!OSSL_PARAM_set_BN(&params[0], n)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[1], e)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[2], d)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[3], p)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[4], q)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[5], dmp1)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[6], dmq1)) goto err_prov;
	if (!OSSL_PARAM_set_BN(&params[7], iqmp)) goto err_prov;

	r = ssh_rsa_pkey_fromdata(OSSL_KEYMGMT_SELECT_ALL, params, &key->pk);
	if (r != 0) goto err_prov;

	/* success */
	key->type = KEY_RSA;
	SSHKEY_DUMP(key);

err_prov:
	OPENSSL_cleanse(buf_d, params[2].data_size);
	OPENSSL_cleanse(buf_p, params[3].data_size);
	OPENSSL_cleanse(buf_q, params[4].data_size);
	OPENSSL_cleanse(buf_dmp1, params[5].data_size);
	OPENSSL_cleanse(buf_dmq1, params[6].data_size);
	OPENSSL_cleanse(buf_iqmp, params[7].data_size);
}
}
#else /*ndef USE_PKEY_FROMDATA*/
{	RSA *rsa;

	/* key attribute allocation */
	rsa = RSA_new();
	if (rsa == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

	if (!RSA_set0_key(rsa, n, e, d)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err_rsa;
	}
	n = e = d = NULL; /* transferred */

	r = ssh_rsa_complete_public(rsa);
	if (r != 0) goto err_rsa;

	if (!RSA_set0_factors(rsa, p, q)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err_rsa;
	}
	p = q = NULL; /* transferred */

	if (!RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err_rsa;
	}
	dmp1 = dmq1 = iqmp = NULL; /* transferred */

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

	if (!EVP_PKEY_set1_RSA(pk, rsa)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		EVP_PKEY_free(pk);
		goto err_rsa;
	}

	/* success */
	r = 0;
	key->pk = pk;
}
	key->type = KEY_RSA;
	SSHKEY_DUMP(key);

err_rsa:
	RSA_free(rsa);
}
#endif /*ndef USE_PKEY_FROMDATA*/

err:
	BN_clear_free(n);
	BN_clear_free(e);
	BN_clear_free(d);
	BN_clear_free(p);
	BN_clear_free(q);
	BN_clear_free(iqmp);
	BN_clear_free(dmp1);
	BN_clear_free(dmq1);
	return r;
}
#endif /* def WITH_OPENSSL_4_0_API */


/* pkey implementation */

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

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

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

	key->type = KEY_RSA;
	key->pk = pk;

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

static const struct ssh_pkey_impl_funcs
ssh_pkey_rsa_funcs = {
	ssh_rsa_sshkey_from_pkey,
	ssh_pkey_validate_public_rsa
};

#define SSH_PKEY_RSA_FUNCS	&ssh_pkey_rsa_funcs


/* key implementation */

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

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

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

static int
ssh_rsa_serialize_public(const struct sshkey_impl *impl,
    const struct sshkey *key, struct sshbuf *buf)
{
	UNUSED(impl);
	if (key->pk == NULL) return SSH_ERR_INVALID_ARGUMENT;

	return sshbuf_write_pkey_rsapub(buf, key->pk);
}

static int
ssh_rsa_serialize_private(const struct sshkey_impl *impl,
    const struct sshkey *key, struct sshbuf *buf)
{
	if (key->pk == NULL) return SSH_ERR_INVALID_ARGUMENT;

	return sshbuf_write_pkey_rsa(buf, key->pk, !impl->cert);
}

#ifdef USE_EVP_PKEY_KEYGEN
static int
ssh_pkey_ctx_set_rsa_keygen_pubexp(EVP_PKEY_CTX *ctx) {
	BIGNUM *f4;
	int ret;

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

	if (!BN_set_word(f4, RSA_F4)) {
		BN_free(f4);
		return SSH_ERR_LIBCRYPTO_ERROR;
	}

#ifdef EVP_PKEY_CTX_set_rsa_keygen_pubexp
	/*definition if OpenSSL < 3.0 and OpenSSL >= 1.0*/
	ret = EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx, f4);
#else
	/*OpenSSL >= 3.0*/
	ret = EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, f4);
	BN_free(f4);
#endif
	return (ret > 0) ? SSH_ERR_SUCCESS: SSH_ERR_LIBCRYPTO_ERROR;
}

static int
ssh_pkey_keygen_rsa(int bits, EVP_PKEY **ret) {
	EVP_PKEY *pk = NULL;
	EVP_PKEY_CTX *ctx = NULL;
	int r;

	ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, 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_rsa_keygen_bits(ctx, bits) <= 0) {
		r = SSH_ERR_KEY_LENGTH;
		goto err;
	}

	r = ssh_pkey_ctx_set_rsa_keygen_pubexp(ctx);
	if (r != 0) 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_rsa_generate(int bits, EVP_PKEY **ret) {
	EVP_PKEY *pk;
	RSA *private = NULL;
	BIGNUM *f4 = NULL;
	int r = 0;

	if ((pk = EVP_PKEY_new()) == NULL ||
	    (private = RSA_new()) == NULL ||
	    (f4 = BN_new()) == NULL
	) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

	if (!BN_set_word(f4, RSA_F4)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	if (!RSA_generate_key_ex(private, bits, f4, NULL)) {
		r = SSH_ERR_KEY_LENGTH;
		goto err;
	}

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

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

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

static int
ssh_rsa_generate(struct sshkey *key, int bits) {
	EVP_PKEY *pk;
	int r;

	r = sshrsa_verify_length(bits);
	if (r != 0) return r;

	if (bits > SSHBUF_MAX_BIGNUM * 8)
		return SSH_ERR_KEY_LENGTH;

#ifdef USE_EVP_PKEY_KEYGEN
	r = ssh_pkey_keygen_rsa(bits, &pk);
#else
	r = ssh_pkey_rsa_generate(bits, &pk);
#endif
	if (r == 0)
		key->pk = pk;

	return r;
}

static u_int
ssh_rsa_default_bits(const char* name) {
	UNUSED(name);
	/* NOTE: sinc with keygen manual page */
	return 3072;
}

static void
ssh_rsa_valid_bits(int bits) {
	if (sshrsa_verify_length(bits) != 0)
		fatal("Invalid RSA key length: minimum is %d bits",
		    required_rsa_size);
	else if (bits > OPENSSL_RSA_MAX_MODULUS_BITS)
		fatal("Invalid RSA key length: maximum is %d bits",
		    OPENSSL_RSA_MAX_MODULUS_BITS);
}

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

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

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

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

	SSHKEY_DUMP(key);

	return ssh_pkey_public_check(key->pk);;
}

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

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

	SSHKEY_DUMP(key);

	return ssh_pkey_check(key->pk);
}

static int
ssh_rsa_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;
	size_t slen = 0, len;
	struct ssh_rsa_alg_st *alg_info;
	int ret;

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

	if (key == NULL) return SSH_ERR_INVALID_ARGUMENT;

	alg_info = ssh_rsa_alg_info(ctx->alg);
	if (alg_info == NULL)
		return SSH_ERR_INVALID_ARGUMENT;
	debug3_f("alg=%s/%s", (ctx->alg != NULL ? ctx->alg : "(nil)"), alg_info->name);

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

	dgst = ssh_evp_md_find(alg_info->id);

#ifdef ENABLE_KEY_PROVIDERS
	/* work-around for tpm2-openssl(<1.3) issue #135 */
	if (getenv("TPM2_RSA_SIZE_BUG") != NULL)
		slen = (EVP_PKEY_bits(key->pk)+7)/8;
	else
#endif
	slen = EVP_PKEY_size(key->pk);
	debug3_f("slen=%zu", slen);

{	u_char *sig = malloc(slen);
	if (sig == NULL) return SSH_ERR_ALLOC_FAIL;
#ifdef HAVE_EVP_DIGESTSIGNINIT /* OpenSSL >= 1.0 */
	/* NOTE: Function EVP_SignFinal() in OpenSSL before 1.0 does not
	 * return signature length if signature argument is NULL.
	 */
	if (ssh_pkey_sign(dgst, key->pk, NULL, &len, data, datalen) <= 0) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto out;
	}
	debug3_f("len=%zu", len);
	/* paranoid check */
	if (len > slen) {
		ret = SSH_ERR_INTERNAL_ERROR;
		goto out;
	}
#endif /*def HAVE_EVP_DIGESTSIGNINIT*/
	if (ssh_pkey_sign(dgst, key->pk, sig, &len, data, datalen) <= 0) {
		ret = SSH_ERR_LIBCRYPTO_ERROR;
		goto out;
	}

	if (len < slen) {
		size_t diff = slen - len;
		memmove(sig + diff, sig, len);
		explicit_bzero(sig, diff);
#ifndef HAVE_EVP_DIGESTSIGNINIT
	} else if (len > slen) {
		ret = SSH_ERR_INTERNAL_ERROR;
		goto out;
#endif /*ndef HAVE_EVP_DIGESTSIGNINIT*/
	}

	ret = ssh_encode_signature(sigp, lenp,
	    alg_info->signame, sig, slen);
 out:
	free(sig);
}
	return ret;
}

static int
ssh_rsa_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;
	const char *alg = ctx->alg;
	char *sigtype = NULL;
	struct ssh_rsa_alg_st *alg_info;
	int ret;
	size_t len = 0, diff, modlen;
	struct sshbuf *b = NULL;
	u_char *osigblob, *sigblob = NULL;

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

	if (key == NULL) return SSH_ERR_INVALID_ARGUMENT;

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

	if ((b = sshbuf_from(sig, siglen)) == NULL)
		return SSH_ERR_ALLOC_FAIL;
	if (sshbuf_get_cstring(b, &sigtype, NULL) != 0) {
		ret = SSH_ERR_INVALID_FORMAT;
		goto out;
	}
	/* algorithm for plain keys */
	alg_info = ssh_rsa_alg_info(sigtype);
	if (alg_info == NULL) {
		ret = SSH_ERR_INVALID_ARGUMENT;
		goto out;
	}
	debug3_f("alg=%s/%s", (alg != NULL ? alg : "(nil)"), alg_info->name);
	/*
	 * For legacy reasons allow ssh-rsa-cert-v01 certs to accept SHA2 signatures
	 * but otherwise the signature algorithm should match.
	 */
	if (alg != NULL && strcmp(alg, "ssh-rsa-cert-v01@openssh.com") != 0) {
		struct ssh_rsa_alg_st *want_info;
		want_info = ssh_rsa_alg_info(alg);
		if (want_info == NULL) {
			ret = SSH_ERR_INVALID_ARGUMENT;
			goto out;
		}
		if (alg_info->id != want_info->id) {
			ret = SSH_ERR_SIGNATURE_INVALID;
			goto out;
		}
	}
	if (sshbuf_get_string(b, &sigblob, &len) != 0) {
		ret = SSH_ERR_INVALID_FORMAT;
		goto out;
	}
	if (sshbuf_len(b) != 0) {
		ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
		goto out;
	}

	dgst = ssh_evp_md_find(alg_info->id);

	modlen = EVP_PKEY_size(key->pk);
	if (len > modlen) {
		ret = SSH_ERR_KEY_BITS_MISMATCH;
		goto out;
	} else if (len < modlen) {
		diff = modlen - len;
		osigblob = sigblob;
		if ((sigblob = realloc(sigblob, modlen)) == NULL) {
			sigblob = osigblob; /* put it back for clear/free */
			ret = SSH_ERR_ALLOC_FAIL;
			goto out;
		}
		memmove(sigblob + diff, sigblob, len);
		explicit_bzero(sigblob, diff);
		len = modlen;
	}

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

 out:
	freezero(sigblob, len);
	free(sigtype);
	sshbuf_free(b);
	return ret;
}

static const struct sshkey_impl_funcs sshkey_rsa_funcs = {
	/* .size = */		ssh_rsa_size,
	/* .alloc =		NULL, */
	/* .cleanup = */	ssh_rsa_cleanup,
	/* .equal = */		ssh_rsa_equal,
	/* .serialize_public = */	ssh_rsa_serialize_public,
	/* .deserialize_public = */	ssh_rsa_deserialize_public,
	/* .serialize_private = */	ssh_rsa_serialize_private,
	/* .deserialize_private = */	ssh_rsa_deserialize_private,
	/* .generate = */	ssh_rsa_generate,
	/* .default_bits = */	ssh_rsa_default_bits,
	/* .valid_bits = */	ssh_rsa_valid_bits,
	/* .move_public = */	ssh_rsa_move_public,
	/* .copy_public = */	ssh_rsa_copy_public,
	/* .sign = */		ssh_rsa_sign,
	/* .verify = */		ssh_rsa_verify
};

#ifdef HAVE_EVP_KEYMGMT_FETCH
char *rsa_prov_name = NULL;

int sshkey_rsa_prov_match(const char *pattern);
int
sshkey_rsa_prov_match(const char *pattern) {
	return match_pattern(rsa_prov_name, pattern);
}
#endif

static int
sshkey_rsa_enabled(void) {
#ifdef HAVE_EVP_KEYMGMT_FETCH
	static int flag = -1;

	if (flag >= 0) return flag;
	/* RSA key is available but some vendor FIPS providers
	 * set additional restrictions.
	 */
	flag  = ssh_keytype_provided("RSA", &rsa_prov_name);
	return flag;
#else
	return 1;
#endif
}
static int sshkey_rsa_sha256_enabled(void) {
#ifdef HAVE_EVP_SHA256
	return 1;
#else
	return 0;
#endif
}
#define SSHKEY_RSA_FUNCS	&sshkey_rsa_funcs

#else

static int sshkey_rsa_enabled(void) { return 0; }
static int sshkey_rsa_sha256_enabled(void) { return 0; }
#define SSHKEY_RSA_FUNCS	NULL
#define SSH_PKEY_RSA_FUNCS	NULL

#endif /* WITH_OPENSSL */

static const char *pkey_rsa_typename[] = { "RSA", NULL };

const struct sshkey_impl sshkey_rsa_impl = {
	/* .id = */		{ KEY_RSA, -1,
				  EVP_PKEY_RSA, pkey_rsa_typename },
	/* .name = */		"ssh-rsa",
	/* .shortname = */	"RSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_rsa_enabled,
	/* .funcs = */		SSHKEY_RSA_FUNCS
};

const struct sshkey_impl sshkey_rsa_cert_impl = {
	/* .id = */		{ KEY_RSA_CERT, -1,
				  EVP_PKEY_RSA, pkey_rsa_typename },
	/* .name = */		"ssh-rsa-cert-v01@openssh.com",
	/* .shortname = */	"RSA-CERT",
	/* .sigalg = */		NULL,
	/* .cert = */		1,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_rsa_enabled,
	/* .funcs = */		SSHKEY_RSA_FUNCS
};

const struct sshkey_impl sshkey_rsa_sha256_impl = {
	/* .id = */		{ KEY_RSA, -1,
				  EVP_PKEY_RSA, pkey_rsa_typename },
	/* .name = */		"rsa-sha2-256",
	/* .shortname = */	"RSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	1,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_rsa_sha256_enabled,
	/* .funcs = */		SSHKEY_RSA_FUNCS
};

const struct sshkey_impl sshkey_rsa_sha512_impl = {
	/* .id = */		{ KEY_RSA, -1,
				  EVP_PKEY_RSA, pkey_rsa_typename },
	/* .name = */		"rsa-sha2-512",
	/* .shortname = */	"RSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	1,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_rsa_sha256_enabled,
	/* .funcs = */		SSHKEY_RSA_FUNCS
};

const struct sshkey_impl sshkey_rsa_sha256_cert_impl = {
	/* .id = */		{ KEY_RSA_CERT, -1,
				  EVP_PKEY_RSA, pkey_rsa_typename },
	/* .name = */		"rsa-sha2-256-cert-v01@openssh.com",
	/* .shortname = */	"RSA-CERT",
	/* .sigalg = */		"rsa-sha2-256",
	/* .cert = */		1,
	/* .sigonly = */	1,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_rsa_sha256_enabled,
	/* .funcs = */		SSHKEY_RSA_FUNCS
};

const struct sshkey_impl sshkey_rsa_sha512_cert_impl = {
	/* .id = */		{ KEY_RSA_CERT, -1,
				  EVP_PKEY_RSA, pkey_rsa_typename },
	/* .name = */		"rsa-sha2-512-cert-v01@openssh.com",
	/* .shortname = */	"RSA-CERT",
	/* .sigalg = */		"rsa-sha2-512",
	/* .cert = */		1,
	/* .sigonly = */	1,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_rsa_sha256_enabled,
	/* .funcs = */		SSHKEY_RSA_FUNCS
};

const struct ssh_pkey_impl ssh_pkey_rsa_impl = {
	pkey_rsa_typename,
	EVP_PKEY_RSA,
	SSH_PKEY_RSA_FUNCS
};
