/*
 * pki_crypto.c - PKI infrastructure using OpenSSL
 *
 * This file is part of the SSH Library
 *
 * Copyright (c) 2003-2009 by Aris Adamantiadis
 * Copyright (c) 2009-2013 by Andreas Schneider <asn@cryptomilk.org>
 * Copyright (c) 2019      by Sahana Prasad     <sahana@redhat.com>
 *
 * The SSH Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * The SSH Library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SSH Library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

#ifndef _PKI_CRYPTO_H
#define _PKI_CRYPTO_H

#include "config.h"

#include "libssh/priv.h"
#include "libcrypto-compat.h"

#include <openssl/pem.h>
#include <openssl/evp.h>
#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER)
#include <openssl/engine.h>
#endif
#include <openssl/err.h>
#if OPENSSL_VERSION_NUMBER < 0x30000000L
#include <openssl/dsa.h>
#include <openssl/rsa.h>
#else
#include <openssl/params.h>
#include <openssl/core_names.h>
#include <openssl/param_build.h>
#if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER)
#include <openssl/store.h>
#endif
#endif /* OPENSSL_VERSION_NUMBER */

#ifdef HAVE_OPENSSL_EC_H
#include <openssl/ec.h>
#endif
#ifdef HAVE_OPENSSL_ECDSA_H
#include <openssl/ecdsa.h>
#endif

#include "libssh/libssh.h"
#include "libssh/buffer.h"
#include "libssh/session.h"
#include "libssh/pki.h"
#include "libssh/pki_priv.h"
#include "libssh/bignum.h"

struct pem_get_password_struct {
    ssh_auth_callback fn;
    void *data;
};

static int pem_get_password(char *buf, int size, int rwflag, void *userdata) {
    struct pem_get_password_struct *pgp = userdata;

    (void) rwflag; /* unused */

    if (buf == NULL) {
        return 0;
    }

    memset(buf, '\0', size);
    if (pgp) {
        int rc;

        rc = pgp->fn("Passphrase for private key:",
                     buf, size, 0, 0,
                     pgp->data);
        if (rc == 0) {
            return strlen(buf);
        }
    }

    return 0;
}

void pki_key_clean(ssh_key key)
{
    if (key == NULL)
        return;
    EVP_PKEY_free(key->key);
    key->key = NULL;
}

#ifdef HAVE_OPENSSL_ECC
#if OPENSSL_VERSION_NUMBER < 0x30000000L
static int pki_key_ecdsa_to_nid(EC_KEY *k)
{
    const EC_GROUP *g = EC_KEY_get0_group(k);
    int nid;

    if (g == NULL) {
        return -1;
    }
    nid = EC_GROUP_get_curve_name(g);
    if (nid) {
        return nid;
    }

    return -1;
}
#else
static int pki_key_ecdsa_to_nid(EVP_PKEY *k)
{
    char gname[25] = { 0 };
    int rc;

    rc = EVP_PKEY_get_utf8_string_param(k,
                                        OSSL_PKEY_PARAM_GROUP_NAME,
                                        gname,
                                        25,
                                        NULL);
    if (rc != 1) {
        return -1;
    }

    return pki_key_ecgroup_name_to_nid(gname);
}
#endif /* OPENSSL_VERSION_NUMBER */

#if OPENSSL_VERSION_NUMBER < 0x30000000L
static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EC_KEY *k)
#else
static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EVP_PKEY *k)
#endif /* OPENSSL_VERSION_NUMBER */
{
    int nid;

    nid = pki_key_ecdsa_to_nid(k);

    switch (nid) {
        case NID_X9_62_prime256v1:
            return SSH_KEYTYPE_ECDSA_P256;
        case NID_secp384r1:
            return SSH_KEYTYPE_ECDSA_P384;
        case NID_secp521r1:
            return SSH_KEYTYPE_ECDSA_P521;
        default:
            return SSH_KEYTYPE_UNKNOWN;
    }
}

const char *pki_key_ecdsa_nid_to_name(int nid)
{
    switch (nid) {
        case NID_X9_62_prime256v1:
            return "ecdsa-sha2-nistp256";
        case NID_secp384r1:
            return "ecdsa-sha2-nistp384";
        case NID_secp521r1:
            return "ecdsa-sha2-nistp521";
        default:
            break;
    }

    return "unknown";
}

static const char *pki_key_ecdsa_nid_to_char(int nid)
{
    switch (nid) {
        case NID_X9_62_prime256v1:
            return "nistp256";
        case NID_secp384r1:
            return "nistp384";
        case NID_secp521r1:
            return "nistp521";
        default:
            break;
    }

    return "unknown";
}

int pki_key_ecdsa_nid_from_name(const char *name)
{
    if (strcmp(name, "nistp256") == 0) {
        return NID_X9_62_prime256v1;
    } else if (strcmp(name, "nistp384") == 0) {
        return NID_secp384r1;
    } else if (strcmp(name, "nistp521") == 0) {
        return NID_secp521r1;
    }

    return -1;
}

int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp)
{
    int rc = 0;
    BIGNUM *bexp = NULL;

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    EC_POINT *p = NULL;
    const EC_GROUP *g = NULL;
    EC_KEY *ecdsa = NULL;
#else
    const char *group_name = OSSL_EC_curve_nid2name(nid);
    OSSL_PARAM_BLD *param_bld = NULL;

    if (group_name == NULL) {
        return -1;
    }
#endif /* OPENSSL_VERSION_NUMBER */

    bexp = ssh_make_string_bn(exp);
    if (bexp == NULL) {
        return -1;
    }

    key->ecdsa_nid = nid;
    key->type_c = pki_key_ecdsa_nid_to_name(nid);

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid);
    if (ecdsa == NULL) {
        rc = -1;
        goto cleanup;
    }

    g = EC_KEY_get0_group(ecdsa);

    p = EC_POINT_new(g);
    if (p == NULL) {
        rc = -1;
        goto cleanup;
    }

    rc = EC_POINT_oct2point(g,
                            p,
                            ssh_string_data(e),
                            ssh_string_len(e),
                            NULL);
    if (rc != 1) {
        rc = -1;
        goto cleanup;
    }

    /* EC_KEY_set_public_key duplicates p */
    rc = EC_KEY_set_public_key(ecdsa, p);
    if (rc != 1) {
        rc = -1;
        goto cleanup;
    }

    /* EC_KEY_set_private_key duplicates exp */
    rc = EC_KEY_set_private_key(ecdsa, bexp);
    if (rc != 1) {
        rc = -1;
        goto cleanup;
    }

    key->key = EVP_PKEY_new();
    if (key->key == NULL) {
        rc = -1;
        goto cleanup;
    }

    /* ecdsa will be freed when the EVP_PKEY key->key is freed */
    rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa);
    if (rc != 1) {
        rc = -1;
        goto cleanup;
    }
    /* ssh_key is now the owner of this memory */
    ecdsa = NULL;

    /* set rc to 0 if everything went well */
    rc = 0;

cleanup:
    EC_KEY_free(ecdsa);
    EC_POINT_free(p);
    BN_free(bexp);
    return rc;
#else
    param_bld = OSSL_PARAM_BLD_new();
    if (param_bld == NULL){
        rc = -1;
        goto cleanup;
    }

    rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME,
                                         group_name, strlen(group_name));
    if (rc != 1) {
        rc = -1;
        goto cleanup;
    }

    rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY,
                                          ssh_string_data(e), ssh_string_len(e));
    if (rc != 1) {
        rc = -1;
        goto cleanup;
    }

    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, bexp);
    if (rc != 1) {
        rc = -1;
        goto cleanup;
    }

    rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_KEYPAIR);

cleanup:
    OSSL_PARAM_BLD_free(param_bld);
    BN_free(bexp);
    return rc;
#endif /* OPENSSL_VERSION_NUMBER */
}

int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e)
{
    int rc;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
    EC_POINT *p = NULL;
    const EC_GROUP *g = NULL;
    EC_KEY *ecdsa = NULL;
    int ok;
#else
    const char *group_name = OSSL_EC_curve_nid2name(nid);
    OSSL_PARAM_BLD *param_bld = NULL;
#endif /* OPENSSL_VERSION_NUMBER */

    key->ecdsa_nid = nid;
    key->type_c = pki_key_ecdsa_nid_to_name(nid);

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid);
    if (ecdsa == NULL) {
        return -1;
    }

    g = EC_KEY_get0_group(ecdsa);

    p = EC_POINT_new(g);
    if (p == NULL) {
        EC_KEY_free(ecdsa);
        return -1;
    }

    ok = EC_POINT_oct2point(g,
                            p,
                            ssh_string_data(e),
                            ssh_string_len(e),
                            NULL);
    if (!ok) {
        EC_KEY_free(ecdsa);
        EC_POINT_free(p);
        return -1;
    }

    /* EC_KEY_set_public_key duplicates p */
    ok = EC_KEY_set_public_key(ecdsa, p);
    EC_POINT_free(p);
    if (!ok) {
        EC_KEY_free(ecdsa);
        return -1;
    }

    key->key = EVP_PKEY_new();
    if (key->key == NULL) {
        EC_KEY_free(ecdsa);
        return -1;
    }

    rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa);
    if (rc != 1) {
        EC_KEY_free(ecdsa);
        return -1;
    }

    return 0;
#else
    param_bld = OSSL_PARAM_BLD_new();
    if (param_bld == NULL)
        goto err;

    rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME,
                                         group_name, strlen(group_name));
    if (rc != 1)
        goto err;
    rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY,
                                          ssh_string_data(e), ssh_string_len(e));
    if (rc != 1)
        goto err;

    rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY);
    OSSL_PARAM_BLD_free(param_bld);

    return rc;
err:
    OSSL_PARAM_BLD_free(param_bld);
    return -1;
#endif /* OPENSSL_VERSION_NUMBER */
}
#endif /* HAVE_OPENSSL_ECC */

ssh_key pki_key_dup(const ssh_key key, int demote)
{
    ssh_key new = NULL;
    int rc;

    new = ssh_key_new();
    if (new == NULL) {
        return NULL;
    }

    new->type = key->type;
    new->type_c = key->type_c;
    if (demote) {
        new->flags = SSH_KEY_FLAG_PUBLIC;
    } else {
        new->flags = key->flags;
    }

    switch (key->type) {
    case SSH_KEYTYPE_RSA:
    case SSH_KEYTYPE_RSA1: {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        const BIGNUM *n = NULL, *e = NULL, *d = NULL;
        BIGNUM *nn, *ne, *nd;
        RSA *new_rsa = NULL;
        const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key);
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
#ifdef WITH_PKCS11_URI
        /* Take the PKCS#11 keys as they are */
        if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) {
            rc = EVP_PKEY_up_ref(key->key);
            if (rc != 1) {
                goto fail;
            }
            new->key = key->key;
            return new;
        }
#endif /* WITH_PKCS11_URI */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        new_rsa = RSA_new();
        if (new_rsa == NULL) {
            goto fail;
        }

        /*
         * n    = public modulus
         * e    = public exponent
         * d    = private exponent
         * p    = secret prime factor
         * q    = secret prime factor
         * dmp1 = d mod (p-1)
         * dmq1 = d mod (q-1)
         * iqmp = q^-1 mod p
         */
        RSA_get0_key(key_rsa, &n, &e, &d);
        nn = BN_dup(n);
        ne = BN_dup(e);
        if (nn == NULL || ne == NULL) {
            RSA_free(new_rsa);
            BN_free(nn);
            BN_free(ne);
            goto fail;
        }

        /* Memory management of nn and ne is transferred to RSA object */
        rc = RSA_set0_key(new_rsa, nn, ne, NULL);
        if (rc == 0) {
            RSA_free(new_rsa);
            BN_free(nn);
            BN_free(ne);
            goto fail;
        }

        if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) {
            const BIGNUM *p = NULL, *q = NULL, *dmp1 = NULL,
              *dmq1 = NULL, *iqmp = NULL;
            BIGNUM *np, *nq, *ndmp1, *ndmq1, *niqmp;

            nd = BN_dup(d);
            if (nd == NULL) {
                RSA_free(new_rsa);
                goto fail;
            }

            /* Memory management of nd is transferred to RSA object */
            rc = RSA_set0_key(new_rsa, NULL, NULL, nd);
            if (rc == 0) {
                RSA_free(new_rsa);
                goto fail;
            }

            /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the
             * RSA operations are much faster when these values are available.
             */
            RSA_get0_factors(key_rsa, &p, &q);
            if (p != NULL && q != NULL) { /* need to set both of them */
                np = BN_dup(p);
                nq = BN_dup(q);
                if (np == NULL || nq == NULL) {
                    RSA_free(new_rsa);
                    BN_free(np);
                    BN_free(nq);
                    goto fail;
                }

                /* Memory management of np and nq is transferred to RSA object */
                rc = RSA_set0_factors(new_rsa, np, nq);
                if (rc == 0) {
                    RSA_free(new_rsa);
                    BN_free(np);
                    BN_free(nq);
                    goto fail;
                }
            }

            RSA_get0_crt_params(key_rsa, &dmp1, &dmq1, &iqmp);
            if (dmp1 != NULL || dmq1 != NULL || iqmp != NULL) {
                ndmp1 = BN_dup(dmp1);
                ndmq1 = BN_dup(dmq1);
                niqmp = BN_dup(iqmp);
                if (ndmp1 == NULL || ndmq1 == NULL || niqmp == NULL) {
                    RSA_free(new_rsa);
                    BN_free(ndmp1);
                    BN_free(ndmq1);
                    BN_free(niqmp);
                    goto fail;
                }

                /* Memory management of ndmp1, ndmq1 and niqmp is transferred
                 * to RSA object */
                rc = RSA_set0_crt_params(new_rsa, ndmp1, ndmq1, niqmp);
                if (rc == 0) {
                    RSA_free(new_rsa);
                    BN_free(ndmp1);
                    BN_free(ndmq1);
                    BN_free(niqmp);
                    goto fail;
                }
            }
        }

        new->key = EVP_PKEY_new();
        if (new->key == NULL) {
            RSA_free(new_rsa);
            goto fail;
        }

        rc = EVP_PKEY_assign_RSA(new->key, new_rsa);
        if (rc != 1) {
            EVP_PKEY_free(new->key);
            RSA_free(new_rsa);
            goto fail;
        }

        new_rsa = NULL;
#else
        rc = evp_dup_rsa_pkey(key, new, demote);
        if (rc != SSH_OK) {
            goto fail;
        }
#endif /* OPENSSL_VERSION_NUMBER */
        break;
    }
    case SSH_KEYTYPE_ECDSA_P256:
    case SSH_KEYTYPE_ECDSA_P384:
    case SSH_KEYTYPE_ECDSA_P521:
#ifdef HAVE_OPENSSL_ECC
        new->ecdsa_nid = key->ecdsa_nid;
#ifdef WITH_PKCS11_URI
        /* Take the PKCS#11 keys as they are */
        if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) {
            rc = EVP_PKEY_up_ref(key->key);
            if (rc != 1) {
                goto fail;
            }
            new->key = key->key;
            return new;
        }
#endif /* WITH_PKCS11_URI */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        /* privkey -> pubkey */
        if (demote && ssh_key_is_private(key)) {
            const EC_POINT *p = NULL;
            EC_KEY *new_ecdsa = NULL, *old_ecdsa = NULL;
            int ok;

            new_ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid);
            if (new_ecdsa == NULL) {
                goto fail;
            }

            old_ecdsa = EVP_PKEY_get0_EC_KEY(key->key);
            if (old_ecdsa == NULL) {
                EC_KEY_free(new_ecdsa);
                goto fail;
            }

            p = EC_KEY_get0_public_key(old_ecdsa);
            if (p == NULL) {
                EC_KEY_free(new_ecdsa);
                goto fail;
            }

            ok = EC_KEY_set_public_key(new_ecdsa, p);
            if (ok != 1) {
                EC_KEY_free(new_ecdsa);
                goto fail;
            }

            new->key = EVP_PKEY_new();
            if (new->key == NULL) {
                EC_KEY_free(new_ecdsa);
                goto fail;
            }

            ok = EVP_PKEY_assign_EC_KEY(new->key, new_ecdsa);
            if (ok != 1) {
                EC_KEY_free(new_ecdsa);
                goto fail;
            }
        } else {
            rc = EVP_PKEY_up_ref(key->key);
            if (rc != 1) {
                goto fail;
            }
            new->key = key->key;
        }
#else
        rc = evp_dup_ecdsa_pkey(key, new, demote);
        if (rc != SSH_OK) {
            goto fail;
        }
#endif /* OPENSSL_VERSION_NUMBER */
        break;
#endif /* HAVE_OPENSSL_ECC */
    case SSH_KEYTYPE_ED25519:
        rc = pki_ed25519_key_dup(new, key);
        if (rc != SSH_OK) {
            goto fail;
        }
        break;
    case SSH_KEYTYPE_UNKNOWN:
    default:
        ssh_key_free(new);
        return NULL;
    }

    return new;
fail:
    ssh_key_free(new);
    return NULL;
}

int pki_key_generate_rsa(ssh_key key, int parameter){
	int rc;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
    BIGNUM *e = NULL;
    RSA *key_rsa = NULL;
#else
    OSSL_PARAM params[3];
    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
    unsigned e = 65537;
#endif /* OPENSSL_VERSION_NUMBER */

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    e = BN_new();
    key_rsa = RSA_new();
    if (key_rsa == NULL) {
        return SSH_ERROR;
    }

    BN_set_word(e, 65537);
    rc = RSA_generate_key_ex(key_rsa, parameter, e, NULL);

    BN_free(e);

    if (rc <= 0 || key_rsa == NULL) {
        return SSH_ERROR;
    }

    key->key = EVP_PKEY_new();
    if (key->key == NULL) {
        RSA_free(key_rsa);
        return SSH_ERROR;
    }

    rc = EVP_PKEY_assign_RSA(key->key, key_rsa);
    if (rc != 1) {
        RSA_free(key_rsa);
        EVP_PKEY_free(key->key);
        return SSH_ERROR;
    }

    key_rsa = NULL;
#else
    key->key = NULL;

    rc = EVP_PKEY_keygen_init(pctx);
    if (rc != 1) {
        EVP_PKEY_CTX_free(pctx);
        return SSH_ERROR;
    }

    params[0] = OSSL_PARAM_construct_int("bits", &parameter);
    params[1] = OSSL_PARAM_construct_uint("e", &e);
    params[2] = OSSL_PARAM_construct_end();
    rc = EVP_PKEY_CTX_set_params(pctx, params);
    if (rc != 1) {
        EVP_PKEY_CTX_free(pctx);
        return SSH_ERROR;
    }

    rc = EVP_PKEY_generate(pctx, &(key->key));

    EVP_PKEY_CTX_free(pctx);

    if (rc != 1 || key->key == NULL)
        return SSH_ERROR;
#endif /* OPENSSL_VERSION_NUMBER */
	return SSH_OK;
}

#ifdef HAVE_OPENSSL_ECC
int pki_key_generate_ecdsa(ssh_key key, int parameter)
{
#if OPENSSL_VERSION_NUMBER < 0x30000000L
    EC_KEY *ecdsa = NULL;
    int ok;
#else
    const char *group_name = NULL;
#endif /* OPENSSL_VERSION_NUMBER */
    switch (parameter) {
        case 256:
            key->ecdsa_nid = NID_X9_62_prime256v1;
            key->type = SSH_KEYTYPE_ECDSA_P256;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
            group_name = NISTP256;
#endif /* OPENSSL_VERSION_NUMBER */
            break;
        case 384:
            key->ecdsa_nid = NID_secp384r1;
            key->type = SSH_KEYTYPE_ECDSA_P384;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
            group_name = NISTP384;
#endif /* OPENSSL_VERSION_NUMBER */
            break;
        case 521:
            key->ecdsa_nid = NID_secp521r1;
            key->type = SSH_KEYTYPE_ECDSA_P521;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
            group_name = NISTP521;
#endif /* OPENSSL_VERSION_NUMBER */
            break;
        default:
            SSH_LOG(SSH_LOG_TRACE, "Invalid parameter %d for ECDSA key "
                    "generation", parameter);
            return SSH_ERROR;
    }
#if OPENSSL_VERSION_NUMBER < 0x30000000L
    ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid);
    if (ecdsa == NULL) {
        return SSH_ERROR;
    }
    ok = EC_KEY_generate_key(ecdsa);
    if (!ok) {
        EC_KEY_free(ecdsa);
        return SSH_ERROR;
    }

    EC_KEY_set_asn1_flag(ecdsa, OPENSSL_EC_NAMED_CURVE);

    key->key = EVP_PKEY_new();
    if (key->key == NULL) {
        EC_KEY_free(ecdsa);
        return SSH_ERROR;
    }

    ok = EVP_PKEY_assign_EC_KEY(key->key, ecdsa);
    if (ok != 1) {
        return SSH_ERROR;
    }

#else
    key->key = EVP_EC_gen(group_name);
    if (key->key == NULL) {
        return SSH_ERROR;
    }
#endif /* OPENSSL_VERSION_NUMBER */

    return SSH_OK;
}
#endif /* HAVE_OPENSSL_ECC */

/* With OpenSSL 3.0 and higher the parameter 'what'
 * is ignored and the comparison is done by OpenSSL
 */
int pki_key_compare(const ssh_key k1,
                    const ssh_key k2,
                    enum ssh_keycmp_e what)
{
    int rc;

    (void)what;

    switch (ssh_key_type_plain(k1->type)) {
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
        case SSH_KEYTYPE_SK_ECDSA:
#if OPENSSL_VERSION_NUMBER < 0x30000000L
#ifdef HAVE_OPENSSL_ECC
            {
                const EC_KEY *ec1 = EVP_PKEY_get0_EC_KEY(k1->key);
                const EC_KEY *ec2 = EVP_PKEY_get0_EC_KEY(k2->key);
                const EC_POINT *p1 = NULL;
                const EC_POINT *p2 = NULL;
                const EC_GROUP *g1 = NULL;
                const EC_GROUP *g2 = NULL;

                if (ec1 == NULL || ec2 == NULL) {
                    return 1;
                }

                p1 = EC_KEY_get0_public_key(ec1);
                p2 = EC_KEY_get0_public_key(ec2);
                g1 = EC_KEY_get0_group(ec1);
                g2 = EC_KEY_get0_group(ec2);

                if (p1 == NULL || p2 == NULL || g1 == NULL || g2 == NULL) {
                    return 1;
                }

                if (EC_GROUP_cmp(g1, g2, NULL) != 0) {
                    return 1;
                }

                if (EC_POINT_cmp(g1, p1, p2, NULL) != 0) {
                    return 1;
                }

                if (what == SSH_KEY_CMP_PRIVATE) {
                    if (bignum_cmp(EC_KEY_get0_private_key(ec1),
                                   EC_KEY_get0_private_key(ec2))) {
                        return 1;
                    }
                }
                break;
            }
#endif /* HAVE_OPENSSL_ECC */
#endif /* OPENSSL_VERSION_NUMBER */
        case SSH_KEYTYPE_RSA:
        case SSH_KEYTYPE_RSA1:
            rc = EVP_PKEY_eq(k1->key, k2->key);
            if (rc != 1) {
                return 1;
            }
            break;
        case SSH_KEYTYPE_ED25519:
        case SSH_KEYTYPE_SK_ED25519:
            /* ed25519 keys handled globally */
        case SSH_KEYTYPE_UNKNOWN:
        default:
            return 1;
    }
    return 0;
}

ssh_string pki_private_key_to_pem(const ssh_key key,
                                  const char *passphrase,
                                  ssh_auth_callback auth_fn,
                                  void *auth_data)
{
    ssh_string blob = NULL;
    BUF_MEM *buf = NULL;
    BIO *mem = NULL;
    EVP_PKEY *pkey = NULL;
    int rc;

    mem = BIO_new(BIO_s_mem());
    if (mem == NULL) {
        return NULL;
    }

    switch (key->type) {
        case SSH_KEYTYPE_RSA:
        case SSH_KEYTYPE_RSA1:
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
            rc = EVP_PKEY_up_ref(key->key);
            if (rc != 1) {
                goto err;
            }
            pkey = key->key;

            /* Mark the operation as successful as for the other key types */
            rc = 1;

            break;
        case SSH_KEYTYPE_ED25519:
            /* In OpenSSL, the input is the private key seed only, which means
             * the first half of the SSH private key (the second half is the
             * public key) */
            pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
                    (const uint8_t *)key->ed25519_privkey,
                    ED25519_KEY_LEN);
            if (pkey == NULL) {
                SSH_LOG(SSH_LOG_TRACE,
                        "Failed to create ed25519 EVP_PKEY: %s",
                        ERR_error_string(ERR_get_error(), NULL));
                goto err;
            }

            /* Mark the operation as successful as for the other key types */
            rc = 1;
            break;
        case SSH_KEYTYPE_RSA_CERT01:
        case SSH_KEYTYPE_ECDSA_P256_CERT01:
        case SSH_KEYTYPE_ECDSA_P384_CERT01:
        case SSH_KEYTYPE_ECDSA_P521_CERT01:
        case SSH_KEYTYPE_ED25519_CERT01:
        case SSH_KEYTYPE_UNKNOWN:
        default:
            SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", key->type);
            goto err;
    }
    if (rc != 1) {
        SSH_LOG(SSH_LOG_TRACE, "Failed to initialize EVP_PKEY structure");
        goto err;
    }

    if (passphrase == NULL) {
        struct pem_get_password_struct pgp = { auth_fn, auth_data };

        rc = PEM_write_bio_PrivateKey(mem,
                                      pkey,
                                      NULL, /* cipher */
                                      NULL, /* kstr */
                                      0, /* klen */
                                      pem_get_password,
                                      &pgp);
    } else {
        rc = PEM_write_bio_PrivateKey(mem,
                                      pkey,
                                      EVP_aes_128_cbc(),
                                      NULL, /* kstr */
                                      0, /* klen */
                                      NULL, /* auth_fn */
                                      (void*) passphrase);
    }
    EVP_PKEY_free(pkey);
    pkey = NULL;

    if (rc != 1) {
        SSH_LOG(SSH_LOG_WARNING,
                "Failed to write private key: %s\n",
                ERR_error_string(ERR_get_error(), NULL));
        goto err;
    }

    BIO_get_mem_ptr(mem, &buf);

    blob = ssh_string_new(buf->length);
    if (blob == NULL) {
        goto err;
    }

    rc = ssh_string_fill(blob, buf->data, buf->length);
    if (rc < 0) {
        ssh_string_free(blob);
        goto err;
    }

    BIO_free(mem);

    return blob;

err:
    EVP_PKEY_free(pkey);
    BIO_free(mem);
    return NULL;
}

ssh_key pki_private_key_from_base64(const char *b64_key,
                                    const char *passphrase,
                                    ssh_auth_callback auth_fn,
                                    void *auth_data)
{
    BIO *mem = NULL;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
    EC_KEY *ecdsa = NULL;
#endif /* OPENSSL_VERSION_NUMBER */
    uint8_t *ed25519 = NULL;
    uint8_t *ed25519_pubkey = NULL;
    ssh_key key = NULL;
    enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN;
    EVP_PKEY *pkey = NULL;

    mem = BIO_new_mem_buf((void*)b64_key, -1);

    if (passphrase == NULL) {
        if (auth_fn) {
            struct pem_get_password_struct pgp = { auth_fn, auth_data };

            pkey = PEM_read_bio_PrivateKey(mem, NULL, pem_get_password, &pgp);
        } else {
            /* openssl uses its own callback to get the passphrase here */
            pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, NULL);
        }
    } else {
        pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, (void *) passphrase);
    }

    BIO_free(mem);

    if (pkey == NULL) {
        SSH_LOG(SSH_LOG_TRACE,
                "Error parsing private key: %s",
                ERR_error_string(ERR_get_error(), NULL));
        return NULL;
    }
    switch (EVP_PKEY_base_id(pkey)) {
    case EVP_PKEY_RSA:
        type = SSH_KEYTYPE_RSA;
        break;
    case EVP_PKEY_EC:
#ifdef HAVE_OPENSSL_ECC
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        ecdsa = EVP_PKEY_get0_EC_KEY(pkey);
        if (ecdsa == NULL) {
            SSH_LOG(SSH_LOG_TRACE,
		    "Error parsing private key: %s",
                    ERR_error_string(ERR_get_error(), NULL));
            goto fail;
        }
#endif /* OPENSSL_VERSION_NUMBER */

        /* pki_privatekey_type_from_string always returns P256 for ECDSA
         * keys, so we need to figure out the correct type here */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        type = pki_key_ecdsa_to_key_type(ecdsa);
#else
        type = pki_key_ecdsa_to_key_type(pkey);
#endif /* OPENSSL_VERSION_NUMBER */
        if (type == SSH_KEYTYPE_UNKNOWN) {
            SSH_LOG(SSH_LOG_TRACE, "Invalid private key.");
            goto fail;
        }

        break;
#endif /* HAVE_OPENSSL_ECC */
    case EVP_PKEY_ED25519:
    {
        size_t key_len;
        int evp_rc = 0;

        /* Get the key length */
        evp_rc = EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len);
        if (evp_rc != 1) {
            SSH_LOG(SSH_LOG_TRACE,
                    "Failed to get ed25519 raw private key length:  %s",
                    ERR_error_string(ERR_get_error(), NULL));
            goto fail;
        }

        if (key_len != ED25519_KEY_LEN) {
            goto fail;
        }

        ed25519 = malloc(key_len);
        if (ed25519 == NULL) {
            SSH_LOG(SSH_LOG_TRACE, "Out of memory");
            goto fail;
        }

        evp_rc = EVP_PKEY_get_raw_private_key(pkey, (uint8_t *)ed25519,
                                              &key_len);
        if (evp_rc != 1) {
            SSH_LOG(SSH_LOG_TRACE,
                    "Failed to get ed25519 raw private key:  %s",
                    ERR_error_string(ERR_get_error(), NULL));
            goto fail;
        }

        /* length matches the private key length */
        ed25519_pubkey = malloc(ED25519_KEY_LEN);
        if (ed25519_pubkey == NULL) {
            SSH_LOG(SSH_LOG_TRACE, "Out of memory");
            goto fail;
        }

        evp_rc = EVP_PKEY_get_raw_public_key(pkey, (uint8_t *)ed25519_pubkey,
                                             &key_len);
        if (evp_rc != 1) {
            SSH_LOG(SSH_LOG_TRACE,
                    "Failed to get ed25519 raw public key:  %s",
                    ERR_error_string(ERR_get_error(), NULL));
            goto fail;
        }
        type = SSH_KEYTYPE_ED25519;

    }
    break;
    default:
        SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d",
                EVP_PKEY_base_id(pkey));
        EVP_PKEY_free(pkey);
        return NULL;
    }

    key = ssh_key_new();
    if (key == NULL) {
        goto fail;
    }

    key->type = type;
    key->type_c = ssh_key_type_to_char(type);
    key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC;
    key->key = pkey;
    key->ed25519_privkey = ed25519;
    key->ed25519_pubkey = ed25519_pubkey;
#ifdef HAVE_OPENSSL_ECC
    if (is_ecdsa_key_type(key->type)) {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa);
#else
        key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key);
#endif /* OPENSSL_VERSION_NUMBER */
    }
#endif /* HAVE_OPENSSL_ECC */

    return key;
fail:
    EVP_PKEY_free(pkey);
    ssh_key_free(key);
    SAFE_FREE(ed25519);
    SAFE_FREE(ed25519_pubkey);
    return NULL;
}

int pki_privkey_build_rsa(ssh_key key,
                          ssh_string n,
                          ssh_string e,
                          ssh_string d,
                          ssh_string iqmp,
                          ssh_string p,
                          ssh_string q)
{
    int rc;
    BIGNUM *be = NULL, *bn = NULL, *bd = NULL;
    BIGNUM *biqmp = NULL, *bp = NULL, *bq = NULL;
    BIGNUM *aux = NULL, *d_consttime = NULL;
    BIGNUM *bdmq1 = NULL, *bdmp1 = NULL;
    BN_CTX *ctx = NULL;

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new();
    if (param_bld == NULL) {
        return SSH_ERROR;
    }
#else
    RSA *key_rsa = RSA_new();
    if (key_rsa == NULL) {
        return SSH_ERROR;
    }
#endif /* OPENSSL_VERSION_NUMBER */

    bn = ssh_make_string_bn(n);
    be = ssh_make_string_bn(e);
    bd = ssh_make_string_bn(d);
    biqmp = ssh_make_string_bn(iqmp);
    bp = ssh_make_string_bn(p);
    bq = ssh_make_string_bn(q);
    if (be == NULL || bn == NULL || bd == NULL ||
        /*biqmp == NULL ||*/ bp == NULL || bq == NULL) {
        rc = SSH_ERROR;
        goto fail;
    }

    /* Calculate remaining CRT parameters for OpenSSL to be happy
     * taken from OpenSSH */
    if ((ctx = BN_CTX_new()) == NULL) {
        rc = SSH_ERROR;
        goto fail;
    }
    if ((aux = BN_new()) == NULL ||
        (bdmq1 = BN_new()) == NULL ||
        (bdmp1 = BN_new()) == NULL) {
        rc = SSH_ERROR;
        goto fail;
    }
    if ((d_consttime = BN_dup(bd)) == NULL) {
        rc = SSH_ERROR;
        goto fail;
    }
    BN_set_flags(aux, BN_FLG_CONSTTIME);
    BN_set_flags(d_consttime, BN_FLG_CONSTTIME);

    if ((BN_sub(aux, bq, BN_value_one()) == 0) ||
        (BN_mod(bdmq1, d_consttime, aux, ctx) == 0) ||
        (BN_sub(aux, bp, BN_value_one()) == 0) ||
        (BN_mod(bdmp1, d_consttime, aux, ctx) == 0)) {
        rc = SSH_ERROR;
        goto fail;
    }

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    /* Memory management of be, bn and bd is transferred to RSA object */
    rc = RSA_set0_key(key_rsa, bn, be, bd);
    if (rc == 0) {
        goto fail;
    }

    /* Memory management of bp and bq is transferred to RSA object */
    rc = RSA_set0_factors(key_rsa, bp, bq);
    if (rc == 0) {
        goto fail;
    }

    /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA
     * operations are much faster when these values are available.
     * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html
     * And OpenSSL fails to export these keys to PEM if these are missing:
     * https://github.com/openssl/openssl/issues/21826
     */
    rc = RSA_set0_crt_params(key_rsa, bdmp1, bdmq1, biqmp);
    if (rc == 0) {
        goto fail;
    }
    bignum_safe_free(aux);
    bignum_safe_free(d_consttime);

    key->key = EVP_PKEY_new();
    if (key->key == NULL) {
        goto fail;
    }

    rc = EVP_PKEY_assign_RSA(key->key, key_rsa);
    if (rc != 1) {
        goto fail;
    }

    return SSH_OK;
fail:
    RSA_free(key_rsa);
    EVP_PKEY_free(key->key);
    return SSH_ERROR;
#else
    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }
    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }
    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, bd);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }

    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, bp);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }

    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, bq);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }

    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, bdmp1);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }

    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, bdmq1);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }

    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, biqmp);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }

    rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR);
    if (rc != SSH_OK) {
        SSH_LOG(SSH_LOG_WARNING,
                "Failed to import private key: %s\n",
                ERR_error_string(ERR_get_error(), NULL));
        rc = SSH_ERROR;
        goto fail;
    }

fail:
    OSSL_PARAM_BLD_free(param_bld);
    bignum_safe_free(bn);
    bignum_safe_free(be);
    bignum_safe_free(bd);
    bignum_safe_free(bp);
    bignum_safe_free(bq);
    bignum_safe_free(biqmp);

    bignum_safe_free(aux);
    bignum_safe_free(d_consttime);
    bignum_safe_free(bdmp1);
    bignum_safe_free(bdmq1);
    BN_CTX_free(ctx);
    return rc;
#endif /* OPENSSL_VERSION_NUMBER */
}

int pki_pubkey_build_rsa(ssh_key key,
                         ssh_string e,
                         ssh_string n) {
    int rc;
    BIGNUM *be = NULL, *bn = NULL;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new();
    if (param_bld == NULL) {
        return SSH_ERROR;
    }
#else
    RSA *key_rsa = RSA_new();
    if (key_rsa == NULL) {
        return SSH_ERROR;
    }
#endif /* OPENSSL_VERSION_NUMBER */

    be = ssh_make_string_bn(e);
    bn = ssh_make_string_bn(n);
    if (be == NULL || bn == NULL) {
        rc = SSH_ERROR;
        goto fail;
    }

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    /* Memory management of bn and be is transferred to RSA object */
    rc = RSA_set0_key(key_rsa, bn, be, NULL);
    if (rc == 0) {
        goto fail;
    }

    key->key = EVP_PKEY_new();
    if (key->key == NULL) {
        goto fail;
    }

    rc = EVP_PKEY_assign_RSA(key->key, key_rsa);
    if (rc != 1) {
        goto fail;
    }

    return SSH_OK;
fail:
    EVP_PKEY_free(key->key);
    RSA_free(key_rsa);
    return SSH_ERROR;
#else
    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }
    rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be);
    if (rc != 1) {
        rc = SSH_ERROR;
        goto fail;
    }

    rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY);

fail:
    OSSL_PARAM_BLD_free(param_bld);
    bignum_safe_free(bn);
    bignum_safe_free(be);

    return rc;
#endif /* OPENSSL_VERSION_NUMBER */
}

ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type)
{
    ssh_buffer buffer;
    ssh_string type_s;
    ssh_string str = NULL;
    ssh_string e = NULL;
    ssh_string n = NULL;
    ssh_string p = NULL;
    ssh_string g = NULL;
    ssh_string q = NULL;
    ssh_string d = NULL;
    ssh_string iqmp = NULL;
    int rc;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL,
           *bn = NULL, *be = NULL,
           *bd = NULL, *biqmp = NULL;
    OSSL_PARAM *params = NULL;
#endif /* OPENSSL_VERSION_NUMBER */

    buffer = ssh_buffer_new();
    if (buffer == NULL) {
        return NULL;
    }
    /* The buffer will contain sensitive information. Make sure it is erased */
    ssh_buffer_set_secure(buffer);

    if (key->cert != NULL) {
        rc = ssh_buffer_add_buffer(buffer, key->cert);
        if (rc < 0) {
            SSH_BUFFER_FREE(buffer);
            return NULL;
        }
        goto makestring;
    }

    type_s = ssh_string_from_char(key->type_c);
    if (type_s == NULL) {
        SSH_BUFFER_FREE(buffer);
        return NULL;
    }

    rc = ssh_buffer_add_ssh_string(buffer, type_s);
    SSH_STRING_FREE(type_s);
    if (rc < 0) {
        SSH_BUFFER_FREE(buffer);
        return NULL;
    }

    switch (key->type) {
        case SSH_KEYTYPE_RSA:
        case SSH_KEYTYPE_RSA1: {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
            const BIGNUM *be = NULL, *bn = NULL;
            const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key);
            RSA_get0_key(key_rsa, &bn, &be, NULL);
#else
            const OSSL_PARAM *out_param = NULL;
            rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, &params);
            if (rc != 1) {
                goto fail;
            }
            out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E);
            if (out_param == NULL) {
                SSH_LOG(SSH_LOG_TRACE, "RSA: No param E has been found");
                goto fail;
            }
            rc = OSSL_PARAM_get_BN(out_param, &be);
            if (rc != 1) {
                goto fail;
            }
            out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N);
            if (out_param == NULL) {
                SSH_LOG(SSH_LOG_TRACE, "RSA: No param N has been found");
                goto fail;
            }
            rc = OSSL_PARAM_get_BN(out_param, &bn);
            if (rc != 1) {
                goto fail;
            }
#endif /* OPENSSL_VERSION_NUMBER */
            e = ssh_make_bignum_string((BIGNUM *)be);
            if (e == NULL) {
                goto fail;
            }

            n = ssh_make_bignum_string((BIGNUM *)bn);
            if (n == NULL) {
                goto fail;
            }

            if (type == SSH_KEY_PUBLIC) {
                /* The N and E parts are swapped in the public key export ! */
                rc = ssh_buffer_add_ssh_string(buffer, e);
                if (rc < 0) {
                    goto fail;
                }
                rc = ssh_buffer_add_ssh_string(buffer, n);
                if (rc < 0) {
                    goto fail;
                }
            } else if (type == SSH_KEY_PRIVATE) {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
                const BIGNUM *bd, *biqmp, *bp, *bq;
                RSA_get0_key(key_rsa, NULL, NULL, &bd);
                RSA_get0_factors(key_rsa, &bp, &bq);
                RSA_get0_crt_params(key_rsa, NULL, NULL, &biqmp);
#else
                OSSL_PARAM_free(params);
                rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, &params);
                if (rc != 1) {
                    goto fail;
                }

                out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D);
                if (out_param == NULL) {
                    SSH_LOG(SSH_LOG_TRACE, "RSA: No param D has been found");
                    goto fail;
                }
                rc = OSSL_PARAM_get_BN(out_param, &bd);
                if (rc != 1) {
                    goto fail;
                }

                out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR1);
                if (out_param == NULL) {
                    SSH_LOG(SSH_LOG_TRACE, "RSA: No param P has been found");
                    goto fail;
                }
                rc = OSSL_PARAM_get_BN(out_param, &bp);
                if (rc != 1) {
                    goto fail;
                }

                out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR2);
                if (out_param == NULL) {
                    SSH_LOG(SSH_LOG_TRACE, "RSA: No param Q has been found");
                    goto fail;
                }
                rc = OSSL_PARAM_get_BN(out_param, &bq);
                if (rc != 1) {
                    goto fail;
                }

                out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_COEFFICIENT1);
                if (out_param == NULL) {
                    SSH_LOG(SSH_LOG_TRACE, "RSA: No param IQMP has been found");
                    goto fail;
                }
                rc = OSSL_PARAM_get_BN(out_param, &biqmp);
                if (rc != 1) {
                    goto fail;
                }
#endif /* OPENSSL_VERSION_NUMBER */
                rc = ssh_buffer_add_ssh_string(buffer, n);
                if (rc < 0) {
                    goto fail;
                }
                rc = ssh_buffer_add_ssh_string(buffer, e);
                if (rc < 0) {
                    goto fail;
                }

                d = ssh_make_bignum_string((BIGNUM *)bd);
                if (d == NULL) {
                    goto fail;
                }

                iqmp = ssh_make_bignum_string((BIGNUM *)biqmp);
                if (iqmp == NULL) {
                    goto fail;
                }

                p = ssh_make_bignum_string((BIGNUM *)bp);
                if (p == NULL) {
                    goto fail;
                }

                q = ssh_make_bignum_string((BIGNUM *)bq);
                if (q == NULL) {
                    goto fail;
                }

                rc = ssh_buffer_add_ssh_string(buffer, d);
                if (rc < 0) {
                    goto fail;
                }
                rc = ssh_buffer_add_ssh_string(buffer, iqmp);
                if (rc < 0) {
                    goto fail;
                }
                rc = ssh_buffer_add_ssh_string(buffer, p);
                if (rc < 0) {
                    goto fail;
                }
                rc = ssh_buffer_add_ssh_string(buffer, q);
                if (rc < 0) {
                    goto fail;
                }

                ssh_string_burn(d);
                SSH_STRING_FREE(d);
                d = NULL;
                ssh_string_burn(iqmp);
                SSH_STRING_FREE(iqmp);
                iqmp = NULL;
                ssh_string_burn(p);
                SSH_STRING_FREE(p);
                p = NULL;
                ssh_string_burn(q);
                SSH_STRING_FREE(q);
                q = NULL;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
                bignum_safe_free(bd);
                bignum_safe_free(biqmp);
                bignum_safe_free(bp);
                bignum_safe_free(bq);
#endif /* OPENSSL_VERSION_NUMBER */
            }
            ssh_string_burn(e);
            SSH_STRING_FREE(e);
            e = NULL;
            ssh_string_burn(n);
            SSH_STRING_FREE(n);
            n = NULL;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
            bignum_safe_free(bn);
            bignum_safe_free(be);
            OSSL_PARAM_free(params);
            params = NULL;
#endif /* OPENSSL_VERSION_NUMBER */
            break;
        }
        case SSH_KEYTYPE_ED25519:
        case SSH_KEYTYPE_SK_ED25519:
            if (type == SSH_KEY_PUBLIC) {
                rc = pki_ed25519_public_key_to_blob(buffer, key);
                if (rc == SSH_ERROR){
                    goto fail;
                }
                /* public key can contain certificate sk information */
                if (key->type == SSH_KEYTYPE_SK_ED25519) {
                    rc = ssh_buffer_add_ssh_string(buffer, key->sk_application);
                    if (rc < 0) {
                        goto fail;
                    }
                }
            } else {
                rc = pki_ed25519_private_key_to_blob(buffer, key);
                if (rc == SSH_ERROR){
                    goto fail;
                }
            }
            break;
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
        case SSH_KEYTYPE_SK_ECDSA:
#ifdef HAVE_OPENSSL_ECC
            {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
                EC_GROUP *group = NULL;
                EC_POINT *point = NULL;
                const void *pubkey = NULL;
                size_t pubkey_len;
                OSSL_PARAM *locate_param = NULL;
#else
                const EC_GROUP *group = NULL;
                const EC_POINT *point = NULL;
                const BIGNUM *exp = NULL;
                EC_KEY *ec = NULL;
#endif /* OPENSSL_VERSION_NUMBER */

                type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid));
                if (type_s == NULL) {
                    SSH_BUFFER_FREE(buffer);
                    return NULL;
                }

                rc = ssh_buffer_add_ssh_string(buffer, type_s);
                SSH_STRING_FREE(type_s);
                if (rc < 0) {
                    SSH_BUFFER_FREE(buffer);
                    return NULL;
                }

#if OPENSSL_VERSION_NUMBER < 0x30000000L
                ec = EVP_PKEY_get0_EC_KEY(key->key);
                if (ec == NULL) {
                    goto fail;
                }
#ifdef WITH_PKCS11_URI
                if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(ec)) {
                    SSH_LOG(SSH_LOG_TRACE, "It is mandatory to have separate"
                            " public ECDSA key objects in the PKCS #11 device."
                            " Unlike RSA, ECDSA public keys cannot be derived"
                            " from their private keys.");
                    goto fail;
                }
#endif /* WITH_PKCS11_URI */
                group = EC_KEY_get0_group(ec);
                point = EC_KEY_get0_public_key(ec);
                if (group == NULL || point == NULL) {
                    goto fail;
                }
                e = pki_key_make_ecpoint_string(group, point);
#else
                rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, &params);
                if (rc < 0) {
                    goto fail;
                }

                locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY);
#ifdef WITH_PKCS11_URI
                if (ssh_key_is_private(key) && !locate_param) {
                    SSH_LOG(SSH_LOG_TRACE, "It is mandatory to have separate"
                            " public ECDSA key objects in the PKCS #11 device."
                            " Unlike RSA, ECDSA public keys cannot be derived"
                            " from their private keys.");
                    goto fail;
                }
#endif /* WITH_PKCS11_URI */

                rc = OSSL_PARAM_get_octet_string_ptr(locate_param, &pubkey, &pubkey_len);
                if (rc != 1) {
                    goto fail;
                }
                /* Convert the data to low-level representation */
                group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, key->ecdsa_nid);
                point = EC_POINT_new(group);
                rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL);
                if (group == NULL || point == NULL || rc != 1) {
                    EC_GROUP_free(group);
                    EC_POINT_free(point);
                    goto fail;
                }

                e = pki_key_make_ecpoint_string(group, point);
                EC_GROUP_free(group);
                EC_POINT_free(point);
#endif /* OPENSSL_VERSION_NUMBER */
                if (e == NULL) {
                    SSH_BUFFER_FREE(buffer);
                    return NULL;
                }

                rc = ssh_buffer_add_ssh_string(buffer, e);
                if (rc < 0) {
                    goto fail;
                }

                ssh_string_burn(e);
                SSH_STRING_FREE(e);
                e = NULL;
                if (type == SSH_KEY_PRIVATE) {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
                    OSSL_PARAM_free(params);
                    rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, &params);
                    if (rc < 0) {
                        goto fail;
                    }

                    locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY);
                    rc = OSSL_PARAM_get_BN(locate_param, &bd);
                    if (rc != 1) {
                        goto fail;
                    }
                    d = ssh_make_bignum_string((BIGNUM *)bd);
                    if (d == NULL) {
                        goto fail;
                    }
                    if (ssh_buffer_add_ssh_string(buffer, d) < 0) {
                        goto fail;
                    }
#else
                    exp = EC_KEY_get0_private_key(ec);
                    if (exp == NULL) {
                        goto fail;
                    }
                    d = ssh_make_bignum_string((BIGNUM *)exp);
                    if (d == NULL) {
                        goto fail;
                    }
                    rc = ssh_buffer_add_ssh_string(buffer, d);
                    if (rc < 0) {
                        goto fail;
                    }
#endif /* OPENSSL_VERSION_NUMBER */
                    ssh_string_burn(d);
                    SSH_STRING_FREE(d);
                    d = NULL;
                } else if (key->type == SSH_KEYTYPE_SK_ECDSA) {
                    /* public key can contain certificate sk information */
                    rc = ssh_buffer_add_ssh_string(buffer, key->sk_application);
                    if (rc < 0) {
                        goto fail;
                    }
                }
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
                bignum_safe_free(bd);
                OSSL_PARAM_free(params);
                params = NULL;
#endif /* OPENSSL_VERSION_NUMBER */
                break;
            }
#endif /* HAVE_OPENSSL_ECC */
        case SSH_KEYTYPE_UNKNOWN:
        default:
            goto fail;
    }

makestring:
    str = ssh_string_new(ssh_buffer_get_len(buffer));
    if (str == NULL) {
        goto fail;
    }

    rc = ssh_string_fill(str, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer));
    if (rc < 0) {
        goto fail;
    }
    SSH_BUFFER_FREE(buffer);

    return str;
fail:
    SSH_BUFFER_FREE(buffer);
    ssh_string_burn(str);
    SSH_STRING_FREE(str);
    ssh_string_burn(e);
    SSH_STRING_FREE(e);
    ssh_string_burn(p);
    SSH_STRING_FREE(p);
    ssh_string_burn(g);
    SSH_STRING_FREE(g);
    ssh_string_burn(q);
    SSH_STRING_FREE(q);
    ssh_string_burn(n);
    SSH_STRING_FREE(n);
    ssh_string_burn(d);
    SSH_STRING_FREE(d);
    ssh_string_burn(iqmp);
    SSH_STRING_FREE(iqmp);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    bignum_safe_free(bp);
    bignum_safe_free(bq);
    bignum_safe_free(bg);
    bignum_safe_free(bpub_key);
    bignum_safe_free(bn);
    bignum_safe_free(be);
    bignum_safe_free(bd);
    bignum_safe_free(biqmp);
    OSSL_PARAM_free(params);
#endif /* OPENSSL_VERSION_NUMBER */

    return NULL;
}

static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig)
{
    ssh_string r = NULL;
    ssh_string s = NULL;

    ssh_buffer buf = NULL;
    ssh_string sig_blob = NULL;

    const BIGNUM *pr = NULL, *ps = NULL;

    const unsigned char *raw_sig_data = NULL;
    size_t raw_sig_len;

    ECDSA_SIG *ecdsa_sig = NULL;

    int rc;

    if (sig == NULL || sig->raw_sig == NULL) {
        return NULL;
    }
    raw_sig_data = ssh_string_data(sig->raw_sig);
    if (raw_sig_data == NULL) {
        return NULL;
    }
    raw_sig_len = ssh_string_len(sig->raw_sig);

    ecdsa_sig = d2i_ECDSA_SIG(NULL, &raw_sig_data, raw_sig_len);
    if (ecdsa_sig == NULL) {
        return NULL;
    }

    ECDSA_SIG_get0(ecdsa_sig, &pr, &ps);
    if (pr == NULL || ps == NULL) {
        goto error;
    }

    r = ssh_make_bignum_string((BIGNUM *)pr);
    if (r == NULL) {
        goto error;
    }

    s = ssh_make_bignum_string((BIGNUM *)ps);
    if (s == NULL) {
        goto error;
    }

    buf = ssh_buffer_new();
    if (buf == NULL) {
        goto error;
    }

    rc = ssh_buffer_add_ssh_string(buf, r);
    if (rc < 0) {
        goto error;
    }

    rc = ssh_buffer_add_ssh_string(buf, s);
    if (rc < 0) {
        goto error;
    }

    sig_blob = ssh_string_new(ssh_buffer_get_len(buf));
    if (sig_blob == NULL) {
        goto error;
    }

    rc = ssh_string_fill(sig_blob, ssh_buffer_get(buf), ssh_buffer_get_len(buf));
    if (rc < 0) {
        goto error;
    }

    SSH_STRING_FREE(r);
    SSH_STRING_FREE(s);
    ECDSA_SIG_free(ecdsa_sig);
    SSH_BUFFER_FREE(buf);

    return sig_blob;

error:
    SSH_STRING_FREE(sig_blob);
    SSH_STRING_FREE(r);
    SSH_STRING_FREE(s);
    ECDSA_SIG_free(ecdsa_sig);
    SSH_BUFFER_FREE(buf);
    return NULL;
}

ssh_string pki_signature_to_blob(const ssh_signature sig)
{
    ssh_string sig_blob = NULL;

    switch(sig->type) {
        case SSH_KEYTYPE_RSA:
        case SSH_KEYTYPE_RSA1:
            sig_blob = ssh_string_copy(sig->raw_sig);
            break;
        case SSH_KEYTYPE_ED25519:
            sig_blob = pki_ed25519_signature_to_blob(sig);
            break;
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
#ifdef HAVE_OPENSSL_ECC
            sig_blob = pki_ecdsa_signature_to_blob(sig);
            break;
#endif /* HAVE_OPENSSL_ECC */
        default:
        case SSH_KEYTYPE_UNKNOWN:
            SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c);
            return NULL;
    }

    return sig_blob;
}

static int pki_signature_from_rsa_blob(const ssh_key pubkey,
                                       const ssh_string sig_blob,
                                       ssh_signature sig)
{
    uint32_t pad_len = 0;
    char *blob_orig = NULL;
    char *blob_padded_data = NULL;
    ssh_string sig_blob_padded = NULL;

    size_t rsalen = 0;
    size_t len = ssh_string_len(sig_blob);

#if OPENSSL_VERSION_NUMBER < 0x30000000L
    const RSA *rsa = EVP_PKEY_get0_RSA(pubkey->key);

    if (rsa == NULL) {
        SSH_LOG(SSH_LOG_TRACE, "RSA field NULL");
        goto errout;
    }

    rsalen = RSA_size(rsa);
#else
    if (EVP_PKEY_get_base_id(pubkey->key) != EVP_PKEY_RSA) {
        SSH_LOG(SSH_LOG_TRACE, "Key has no RSA pubkey");
        goto errout;
    }

    rsalen = EVP_PKEY_size(pubkey->key);
#endif /* OPENSSL_VERSION_NUMBER */
    if (len > rsalen) {
        SSH_LOG(SSH_LOG_TRACE,
                "Signature is too big: %lu > %lu",
                (unsigned long)len,
                (unsigned long)rsalen);
        goto errout;
    }

#ifdef DEBUG_CRYPTO
    SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len);
    ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len);
#endif /* DEBUG_CRYPTO */

    if (len == rsalen) {
        sig->raw_sig = ssh_string_copy(sig_blob);
    } else {
        /* pad the blob to the expected rsalen size */
        SSH_LOG(SSH_LOG_DEBUG,
                "RSA signature len %lu < %lu",
                (unsigned long)len,
                (unsigned long)rsalen);

        pad_len = rsalen - len;

        sig_blob_padded = ssh_string_new(rsalen);
        if (sig_blob_padded == NULL) {
            goto errout;
        }

        blob_padded_data = (char *) ssh_string_data(sig_blob_padded);
        blob_orig = (char *) ssh_string_data(sig_blob);

        if (blob_padded_data == NULL || blob_orig == NULL) {
            goto errout;
        }

        /* front-pad the buffer with zeroes */
        explicit_bzero(blob_padded_data, pad_len);
        /* fill the rest with the actual signature blob */
        memcpy(blob_padded_data + pad_len, blob_orig, len);

        sig->raw_sig = sig_blob_padded;
    }

    return SSH_OK;

errout:
    SSH_STRING_FREE(sig_blob_padded);
    return SSH_ERROR;
}

static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey),
                                         const ssh_string sig_blob,
                                         ssh_signature sig)
{
    ECDSA_SIG *ecdsa_sig = NULL;
    BIGNUM *pr = NULL, *ps = NULL;

    ssh_string r = NULL;
    ssh_string s = NULL;

    ssh_buffer buf = NULL;
    uint32_t rlen;

    unsigned char *raw_sig_data = NULL;
    unsigned char *temp_raw_sig = NULL;
    size_t raw_sig_len = 0;

    int rc;

    /* build ecdsa signature */
    buf = ssh_buffer_new();
    if (buf == NULL) {
        return SSH_ERROR;
    }

    /* The buffer will contain sensitive information. Make sure it is erased */
    ssh_buffer_set_secure(buf);

    rc = ssh_buffer_add_data(buf,
                             ssh_string_data(sig_blob),
                             ssh_string_len(sig_blob));
    if (rc < 0) {
        goto error;
    }

    r = ssh_buffer_get_ssh_string(buf);
    if (r == NULL) {
        goto error;
    }

#ifdef DEBUG_CRYPTO
    ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r));
#endif

    pr = ssh_make_string_bn(r);
    ssh_string_burn(r);
    SSH_STRING_FREE(r);
    if (pr == NULL) {
        goto error;
    }

    s = ssh_buffer_get_ssh_string(buf);
    rlen = ssh_buffer_get_len(buf);
    SSH_BUFFER_FREE(buf);
    if (s == NULL) {
        goto error;
    }

    if (rlen != 0) {
        ssh_string_burn(s);
        SSH_STRING_FREE(s);
        SSH_LOG(SSH_LOG_TRACE,
                "Signature has remaining bytes in inner "
                "sigblob: %lu",
                (unsigned long)rlen);
        goto error;
    }

#ifdef DEBUG_CRYPTO
    ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s));
#endif

    ps = ssh_make_string_bn(s);
    ssh_string_burn(s);
    SSH_STRING_FREE(s);
    if (ps == NULL) {
        goto error;
    }

    ecdsa_sig = ECDSA_SIG_new();
    if (ecdsa_sig == NULL) {
        goto error;
    }

    /* Memory management of pr and ps is transferred to
     * ECDSA signature object */
    rc = ECDSA_SIG_set0(ecdsa_sig, pr, ps);
    if (rc == 0) {
        goto error;
    }
    pr = NULL;
    ps = NULL;

    /* Get the expected size of the buffer */
    rc = i2d_ECDSA_SIG(ecdsa_sig, NULL);
    if (rc <= 0) {
        goto error;
    }
    raw_sig_len = rc;

    raw_sig_data = (unsigned char *)calloc(1, raw_sig_len);
    if (raw_sig_data == NULL) {
        goto error;
    }
    temp_raw_sig = raw_sig_data;

    /* It is necessary to use a temporary pointer as i2d_* "advances" the
     * pointer */
    rc = i2d_ECDSA_SIG(ecdsa_sig, &temp_raw_sig);
    if (rc <= 0) {
        goto error;
    }

    sig->raw_sig = ssh_string_new(raw_sig_len);
    if (sig->raw_sig == NULL) {
        explicit_bzero(raw_sig_data, raw_sig_len);
        goto error;
    }

    rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len);
    if (rc < 0) {
        explicit_bzero(raw_sig_data, raw_sig_len);
        goto error;
    }

    explicit_bzero(raw_sig_data, raw_sig_len);
    SAFE_FREE(raw_sig_data);
    ECDSA_SIG_free(ecdsa_sig);
    return SSH_OK;

error:
    SSH_BUFFER_FREE(buf);
    bignum_safe_free(ps);
    bignum_safe_free(pr);
    SAFE_FREE(raw_sig_data);
    if (ecdsa_sig != NULL) {
        ECDSA_SIG_free(ecdsa_sig);
    }
    return SSH_ERROR;
}

ssh_signature pki_signature_from_blob(const ssh_key pubkey,
                                      const ssh_string sig_blob,
                                      enum ssh_keytypes_e type,
                                      enum ssh_digest_e hash_type)
{
    ssh_signature sig;
    int rc;

    if (ssh_key_type_plain(pubkey->type) != type) {
        SSH_LOG(SSH_LOG_TRACE,
                "Incompatible public key provided (%d) expecting (%d)",
                type,
                pubkey->type);
        return NULL;
    }

    sig = ssh_signature_new();
    if (sig == NULL) {
        return NULL;
    }

    sig->type = type;
    sig->type_c = ssh_key_signature_to_char(type, hash_type);
    sig->hash_type = hash_type;

    switch(type) {
        case SSH_KEYTYPE_RSA:
        case SSH_KEYTYPE_RSA1:
            rc = pki_signature_from_rsa_blob(pubkey, sig_blob, sig);
            if (rc != SSH_OK) {
                goto error;
            }
            break;
        case SSH_KEYTYPE_ED25519:
        case SSH_KEYTYPE_SK_ED25519:
            rc = pki_signature_from_ed25519_blob(sig, sig_blob);
            if (rc != SSH_OK){
                goto error;
            }
            break;
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
        case SSH_KEYTYPE_ECDSA_P256_CERT01:
        case SSH_KEYTYPE_ECDSA_P384_CERT01:
        case SSH_KEYTYPE_ECDSA_P521_CERT01:
        case SSH_KEYTYPE_SK_ECDSA:
        case SSH_KEYTYPE_SK_ECDSA_CERT01:
#ifdef HAVE_OPENSSL_ECC
            rc = pki_signature_from_ecdsa_blob(pubkey, sig_blob, sig);
            if (rc != SSH_OK) {
                goto error;
            }
            break;
#endif
        default:
        case SSH_KEYTYPE_UNKNOWN:
            SSH_LOG(SSH_LOG_TRACE, "Unknown signature type");
            goto error;
    }

    return sig;

error:
    ssh_signature_free(sig);
    return NULL;
}

static const EVP_MD *pki_digest_to_md(enum ssh_digest_e hash_type)
{
    const EVP_MD *md = NULL;

    switch (hash_type) {
    case SSH_DIGEST_SHA256:
        md = EVP_sha256();
        break;
    case SSH_DIGEST_SHA384:
        md = EVP_sha384();
        break;
    case SSH_DIGEST_SHA512:
        md = EVP_sha512();
        break;
    case SSH_DIGEST_SHA1:
        md = EVP_sha1();
        break;
    case SSH_DIGEST_AUTO:
        md = NULL;
        break;
    default:
        SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d",
                hash_type);
        return NULL;
    }

    return md;
}

static EVP_PKEY *pki_key_to_pkey(ssh_key key)
{
    EVP_PKEY *pkey = NULL;
    int rc = 0;

    switch (key->type) {
    case SSH_KEYTYPE_RSA:
    case SSH_KEYTYPE_RSA1:
    case SSH_KEYTYPE_RSA_CERT01:
    case SSH_KEYTYPE_ECDSA_P256:
    case SSH_KEYTYPE_ECDSA_P384:
    case SSH_KEYTYPE_ECDSA_P521:
    case SSH_KEYTYPE_ECDSA_P256_CERT01:
    case SSH_KEYTYPE_ECDSA_P384_CERT01:
    case SSH_KEYTYPE_ECDSA_P521_CERT01:
    case SSH_KEYTYPE_SK_ECDSA:
    case SSH_KEYTYPE_SK_ECDSA_CERT01:
        if (key->key == NULL) {
            SSH_LOG(SSH_LOG_TRACE, "NULL key->key");
            goto error;
        }
        rc = EVP_PKEY_up_ref(key->key);
        if (rc != 1) {
            SSH_LOG(SSH_LOG_TRACE, "Failed to reference EVP_PKEY");
            return NULL;
        }
        pkey = key->key;
        break;
    case SSH_KEYTYPE_ED25519:
    case SSH_KEYTYPE_ED25519_CERT01:
    case SSH_KEYTYPE_SK_ED25519:
    case SSH_KEYTYPE_SK_ED25519_CERT01:
        if (ssh_key_is_private(key)) {
            if (key->ed25519_privkey == NULL) {
                SSH_LOG(SSH_LOG_TRACE, "NULL key->ed25519_privkey");
                goto error;
            }
            /* In OpenSSL, the input is the private key seed only, which means
             * the first half of the SSH private key (the second half is the
             * public key). Both keys have the same length (32 bytes) */
            pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
                    (const uint8_t *)key->ed25519_privkey,
                    ED25519_KEY_LEN);
        } else {
            if (key->ed25519_pubkey == NULL) {
                SSH_LOG(SSH_LOG_TRACE, "NULL key->ed25519_pubkey");
                goto error;
            }
            pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
                    (const uint8_t *)key->ed25519_pubkey,
                    ED25519_KEY_LEN);
        }
        if (pkey == NULL) {
            SSH_LOG(SSH_LOG_TRACE,
                    "Failed to create ed25519 EVP_PKEY: %s",
                    ERR_error_string(ERR_get_error(), NULL));
            return NULL;
        }
        break;
    case SSH_KEYTYPE_UNKNOWN:
    default:
        SSH_LOG(SSH_LOG_TRACE, "Unknown private key algorithm for type: %d",
                key->type);
        goto error;
    }

    return pkey;

error:
    EVP_PKEY_free(pkey);
    return NULL;
}

/**
 * @internal
 *
 * @brief Sign the given input data. The digest to be signed is calculated
 * internally as necessary.
 *
 * @param[in]   privkey     The private key to be used for signing.
 * @param[in]   hash_type   The digest algorithm to be used.
 * @param[in]   input       The data to be signed.
 * @param[in]   input_len   The length of the data to be signed.
 *
 * @return  a newly allocated ssh_signature or NULL on error.
 */
ssh_signature pki_sign_data(const ssh_key privkey,
                            enum ssh_digest_e hash_type,
                            const unsigned char *input,
                            size_t input_len)
{
    const EVP_MD *md = NULL;
    EVP_MD_CTX *ctx = NULL;
    EVP_PKEY *pkey = NULL;

    unsigned char *raw_sig_data = NULL;
    size_t raw_sig_len;

    ssh_signature sig = NULL;

    int rc;

    if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) {
        SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to "
                               "pki_sign_data()");
        return NULL;
    }

    /* Check if public key and hash type are compatible */
    rc = pki_key_check_hash_compatible(privkey, hash_type);
    if (rc != SSH_OK) {
        return NULL;
    }

    /* Set hash algorithm to be used */
    md = pki_digest_to_md(hash_type);
    if (md == NULL) {
        if (hash_type != SSH_DIGEST_AUTO) {
            return NULL;
        }
    }

    /* Setup private key EVP_PKEY */
    pkey = pki_key_to_pkey(privkey);
    if (pkey == NULL) {
        return NULL;
    }

    /* Allocate buffer for signature */
    raw_sig_len = (size_t)EVP_PKEY_size(pkey);
    raw_sig_data = (unsigned char *)malloc(raw_sig_len);
    if (raw_sig_data == NULL) {
        SSH_LOG(SSH_LOG_TRACE, "Out of memory");
        goto out;
    }

    /* Create the context */
    ctx = EVP_MD_CTX_new();
    if (ctx == NULL) {
        SSH_LOG(SSH_LOG_TRACE, "Out of memory");
        goto out;
    }

    /* Sign the data */
    rc = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey);
    if (rc != 1){
        SSH_LOG(SSH_LOG_TRACE,
                "EVP_DigestSignInit() failed: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto out;
    }

    rc = EVP_DigestSign(ctx, raw_sig_data, &raw_sig_len, input, input_len);
    if (rc != 1) {
        SSH_LOG(SSH_LOG_TRACE,
                "EVP_DigestSign() failed: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto out;
    }

#ifdef DEBUG_CRYPTO
        ssh_log_hexdump("Generated signature", raw_sig_data, raw_sig_len);
#endif

    /* Allocate and fill output signature */
    sig = ssh_signature_new();
    if (sig == NULL) {
        goto out;
    }

    sig->raw_sig = ssh_string_new(raw_sig_len);
    if (sig->raw_sig == NULL) {
        ssh_signature_free(sig);
        sig = NULL;
        goto out;
    }

    rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len);
    if (rc < 0) {
        ssh_signature_free(sig);
        sig = NULL;
        goto out;
    }

    sig->type = privkey->type;
    sig->hash_type = hash_type;
    sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type);

out:
    if (ctx != NULL) {
        EVP_MD_CTX_free(ctx);
    }
    if (raw_sig_data != NULL) {
        explicit_bzero(raw_sig_data, raw_sig_len);
    }
    SAFE_FREE(raw_sig_data);
    EVP_PKEY_free(pkey);
    return sig;
}

/**
 * @internal
 *
 * @brief Verify the signature of a given input. The digest of the input is
 * calculated internally as necessary.
 *
 * @param[in]   signature   The signature to be verified.
 * @param[in]   pubkey      The public key used to verify the signature.
 * @param[in]   input       The signed data.
 * @param[in]   input_len   The length of the signed data.
 *
 * @return  SSH_OK if the signature is valid; SSH_ERROR otherwise.
 */
int pki_verify_data_signature(ssh_signature signature,
                              const ssh_key pubkey,
                              const unsigned char *input,
                              size_t input_len)
{
    const EVP_MD *md = NULL;
    EVP_MD_CTX *ctx = NULL;
    EVP_PKEY *pkey = NULL;

    unsigned char *raw_sig_data = NULL;
    unsigned int raw_sig_len;

    /* Function return code
     * Do not change this variable throughout the function until the signature
     * is successfully verified!
     */
    int rc = SSH_ERROR;
    int ok;

    if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL ||
        signature == NULL || signature->raw_sig == NULL)
    {
        SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to "
                               "pki_verify_data_signature()");
        return SSH_ERROR;
    }

    /* Check if public key and hash type are compatible */
    ok = pki_key_check_hash_compatible(pubkey, signature->hash_type);
    if (ok != SSH_OK) {
        return SSH_ERROR;
    }

    /* Get the signature to be verified */
    raw_sig_data = ssh_string_data(signature->raw_sig);
    raw_sig_len = ssh_string_len(signature->raw_sig);
    if (raw_sig_data == NULL) {
        return SSH_ERROR;
    }

    /* Set hash algorithm to be used */
    md = pki_digest_to_md(signature->hash_type);
    if (md == NULL) {
        if (signature->hash_type != SSH_DIGEST_AUTO) {
            return SSH_ERROR;
        }
    }

    /* Setup public key EVP_PKEY */
    pkey = pki_key_to_pkey(pubkey);
    if (pkey == NULL) {
        return SSH_ERROR;
    }

    /* Create the context */
    ctx = EVP_MD_CTX_new();
    if (ctx == NULL) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to create EVP_MD_CTX: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto out;
    }

    /* Verify the signature */
    ok = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey);
    if (ok != 1){
        SSH_LOG(SSH_LOG_TRACE,
                "EVP_DigestVerifyInit() failed: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto out;
    }

    ok = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len);
    if (ok != 1) {
        SSH_LOG(SSH_LOG_TRACE,
                "Signature invalid: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto out;
    }

    SSH_LOG(SSH_LOG_TRACE, "Signature valid");
    rc = SSH_OK;

out:
    EVP_MD_CTX_free(ctx);
    EVP_PKEY_free(pkey);
    return rc;
}

int ssh_key_size(ssh_key key)
{
    int bits = 0;
    EVP_PKEY *pkey = NULL;

    switch (key->type) {
    case SSH_KEYTYPE_RSA:
    case SSH_KEYTYPE_RSA_CERT01:
    case SSH_KEYTYPE_RSA1:
    case SSH_KEYTYPE_ECDSA_P256:
    case SSH_KEYTYPE_ECDSA_P256_CERT01:
    case SSH_KEYTYPE_ECDSA_P384:
    case SSH_KEYTYPE_ECDSA_P384_CERT01:
    case SSH_KEYTYPE_ECDSA_P521:
    case SSH_KEYTYPE_ECDSA_P521_CERT01:
    case SSH_KEYTYPE_SK_ECDSA:
    case SSH_KEYTYPE_SK_ECDSA_CERT01:
        pkey = pki_key_to_pkey(key);
        if (pkey == NULL) {
            return SSH_ERROR;
        }
        bits = EVP_PKEY_bits(pkey);
        EVP_PKEY_free(pkey);
        return bits;
    case SSH_KEYTYPE_ED25519:
    case SSH_KEYTYPE_ED25519_CERT01:
    case SSH_KEYTYPE_SK_ED25519:
    case SSH_KEYTYPE_SK_ED25519_CERT01:
        /* ed25519 keys have fixed size */
        return 255;
    case SSH_KEYTYPE_DSS:   /* deprecated */
    case SSH_KEYTYPE_DSS_CERT01:    /* deprecated */
    case SSH_KEYTYPE_UNKNOWN:
    default:
        return SSH_ERROR;
    }
}

int pki_key_generate_ed25519(ssh_key key)
{
    int evp_rc;
    EVP_PKEY_CTX *pctx = NULL;
    EVP_PKEY *pkey = NULL;
    size_t privkey_len = ED25519_KEY_LEN;
    size_t pubkey_len = ED25519_KEY_LEN;

    if (key == NULL) {
        return SSH_ERROR;
    }

    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
    if (pctx == NULL) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to create ed25519 EVP_PKEY_CTX: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto error;
    }

    evp_rc = EVP_PKEY_keygen_init(pctx);
    if (evp_rc != 1) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to initialize ed25519 key generation: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto error;
    }

    evp_rc = EVP_PKEY_keygen(pctx, &pkey);
    if (evp_rc != 1) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to generate ed25519 key: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto error;
    }

    key->ed25519_privkey = malloc(ED25519_KEY_LEN);
    if (key->ed25519_privkey == NULL) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to allocate memory for ed25519 private key");
        goto error;
    }

    key->ed25519_pubkey = malloc(ED25519_KEY_LEN);
    if (key->ed25519_pubkey == NULL) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to allocate memory for ed25519 public key");
        goto error;
    }

    evp_rc = EVP_PKEY_get_raw_private_key(pkey, (uint8_t *)key->ed25519_privkey,
                                          &privkey_len);
    if (evp_rc != 1) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to get ed25519 raw private key: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto error;
    }

    evp_rc = EVP_PKEY_get_raw_public_key(pkey, (uint8_t *)key->ed25519_pubkey,
                                         &pubkey_len);
    if (evp_rc != 1) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to get ed25519 raw public key: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto error;
    }

    EVP_PKEY_CTX_free(pctx);
    EVP_PKEY_free(pkey);
    return SSH_OK;

error:
    if (pctx != NULL) {
        EVP_PKEY_CTX_free(pctx);
    }
    if (pkey != NULL) {
        EVP_PKEY_free(pkey);
    }
    SAFE_FREE(key->ed25519_privkey);
    SAFE_FREE(key->ed25519_pubkey);

    return SSH_ERROR;
}

#ifdef WITH_PKCS11_URI

/**
 * @internal
 *
 * @brief Populate the public/private ssh_key from the engine/provider with
 * PKCS#11 URIs as the look up.
 *
 * @param[in]   uri_name    The PKCS#11 URI
 * @param[in]   nkey        The ssh-key context for
 *                          the key loaded from the engine/provider.
 * @param[in]   key_type    The type of the key used. Public/Private.
 *
 * @return  SSH_OK if ssh-key is valid; SSH_ERROR otherwise.
 */
int pki_uri_import(const char *uri_name,
                     ssh_key *nkey,
                     enum ssh_key_e key_type)
{
    EVP_PKEY *pkey = NULL;
    ssh_key key = NULL;
    enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN;
#if OPENSSL_VERSION_NUMBER < 0x30000000L && HAVE_OPENSSL_ECC
    EC_KEY *ecdsa = NULL;
#endif
#ifndef WITH_PKCS11_PROVIDER
    ENGINE *engine = NULL;

    /* Do the init only once */
    engine = pki_get_engine();
    if (engine == NULL) {
        SSH_LOG(SSH_LOG_TRACE, "Failed to initialize engine");
        goto fail;
    }

    switch (key_type) {
    case SSH_KEY_PRIVATE:
        pkey = ENGINE_load_private_key(engine, uri_name, NULL, NULL);
        if (pkey == NULL) {
            SSH_LOG(SSH_LOG_TRACE,
                    "Could not load key: %s",
                    ERR_error_string(ERR_get_error(), NULL));
            goto fail;
        }
        break;
    case SSH_KEY_PUBLIC:
        pkey = ENGINE_load_public_key(engine, uri_name, NULL, NULL);
        if (pkey == NULL) {
            SSH_LOG(SSH_LOG_TRACE,
                    "Could not load key: %s",
                    ERR_error_string(ERR_get_error(), NULL));
            goto fail;
        }
        break;
    default:
        SSH_LOG(SSH_LOG_TRACE,
                "Invalid key type: %d", key_type);
        goto fail;
    }
#else /* WITH_PKCS11_PROVIDER */
    OSSL_STORE_CTX *store = NULL;
    OSSL_STORE_INFO *info = NULL;
    int rv, expect_type = OSSL_STORE_INFO_PKEY;

    /* The provider can be either configured in openssl.cnf or dynamically
     * loaded, assuming it does not need any special configuration */
    rv = pki_load_pkcs11_provider();
    if (rv != SSH_OK) {
        SSH_LOG(SSH_LOG_TRACE, "Failed to load or initialize pkcs11 provider");
        goto fail;
    }

    store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL);
    if (store == NULL) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to open OpenSSL store: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto fail;
    }
    if (key_type == SSH_KEY_PUBLIC) {
        expect_type = OSSL_STORE_INFO_PUBKEY;
    }
    rv = OSSL_STORE_expect(store, expect_type);
    if (rv != 1) {
        SSH_LOG(SSH_LOG_TRACE,
                "Failed to set the store preference. Ignoring the error: %s",
                ERR_error_string(ERR_get_error(), NULL));
    }

    for (info = OSSL_STORE_load(store);
         info != NULL;
         info = OSSL_STORE_load(store)) {
        int ossl_type = OSSL_STORE_INFO_get_type(info);

        if (ossl_type == OSSL_STORE_INFO_PUBKEY && key_type == SSH_KEY_PUBLIC) {
            pkey = OSSL_STORE_INFO_get1_PUBKEY(info);
        } else if (ossl_type == OSSL_STORE_INFO_PKEY &&
                   key_type == SSH_KEY_PRIVATE) {
            pkey = OSSL_STORE_INFO_get1_PKEY(info);
        } else {
            SSH_LOG(SSH_LOG_TRACE,
                    "Ignoring object not matching our type: %d",
                    ossl_type);
            OSSL_STORE_INFO_free(info);
            continue;
        }
        OSSL_STORE_INFO_free(info);
        break;
    }
    OSSL_STORE_close(store);
    if (pkey == NULL) {
        SSH_LOG(SSH_LOG_TRACE,
                "No key found in the pkcs11 store: %s",
                ERR_error_string(ERR_get_error(), NULL));
        goto fail;
    }

#endif /* WITH_PKCS11_PROVIDER */

    key = ssh_key_new();
    if (key == NULL) {
        goto fail;
    }

    switch (EVP_PKEY_base_id(pkey)) {
    case EVP_PKEY_RSA:
        type = SSH_KEYTYPE_RSA;
        break;
    case EVP_PKEY_EC:
#ifdef HAVE_OPENSSL_ECC
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        ecdsa = EVP_PKEY_get0_EC_KEY(pkey);
        if (ecdsa == NULL) {
            SSH_LOG(SSH_LOG_TRACE,
                    "Parsing pub key: %s",
                    ERR_error_string(ERR_get_error(), NULL));
            goto fail;
        }

        /* pki_privatekey_type_from_string always returns P256 for ECDSA
         * keys, so we need to figure out the correct type here */
        type = pki_key_ecdsa_to_key_type(ecdsa);
#else
        type = pki_key_ecdsa_to_key_type(pkey);
#endif /* OPENSSL_VERSION_NUMBER */
        if (type == SSH_KEYTYPE_UNKNOWN) {
            SSH_LOG(SSH_LOG_TRACE, "Invalid pub key.");
            goto fail;
        }

        break;
#endif
    default:
        SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid public key type %d",
                EVP_PKEY_base_id(pkey));
        goto fail;
    }

    key->key = pkey;
    key->type = type;
    key->type_c = ssh_key_type_to_char(type);
    key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI;
    if (key_type == SSH_KEY_PRIVATE) {
        key->flags |= SSH_KEY_FLAG_PRIVATE;
    }
#ifdef HAVE_OPENSSL_ECC
    if (is_ecdsa_key_type(key->type)) {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
        key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa);
#else
        key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key);
#endif /* OPENSSL_VERSION_NUMBER */
    }
#endif

    *nkey = key;

    return SSH_OK;

fail:
    EVP_PKEY_free(pkey);
    ssh_key_free(key);

    return SSH_ERROR;
}
#endif /* WITH_PKCS11_URI */

#endif /* _PKI_CRYPTO_H */
